fix(media): block symlink traversal

This commit is contained in:
Peter Steinberger
2025-12-02 18:37:15 +00:00
parent b94b220156
commit 2cf134668c
3 changed files with 31 additions and 10 deletions

View File

@@ -17,24 +17,31 @@ export function attachMediaRoutes(
app.get("/media/:id", async (req, res) => {
const id = req.params.id;
const file = path.resolve(mediaDir, id);
const mediaRoot = path.resolve(mediaDir) + path.sep;
if (!file.startsWith(mediaRoot)) {
res.status(400).send("invalid path");
return;
}
const mediaRoot = (await fs.realpath(mediaDir)) + path.sep;
const file = path.resolve(mediaRoot, id);
try {
const stat = await fs.stat(file);
const lstat = await fs.lstat(file);
if (lstat.isSymbolicLink()) {
res.status(400).send("invalid path");
return;
}
const realPath = await fs.realpath(file);
if (!realPath.startsWith(mediaRoot)) {
res.status(400).send("invalid path");
return;
}
const stat = await fs.stat(realPath);
if (Date.now() - stat.mtimeMs > ttlMs) {
await fs.rm(file).catch(() => {});
await fs.rm(realPath).catch(() => {});
res.status(410).send("expired");
return;
}
res.sendFile(file);
res.sendFile(realPath);
// best-effort single-use cleanup after response ends
res.on("finish", () => {
setTimeout(() => {
fs.rm(file).catch(() => {});
fs.rm(realPath).catch(() => {});
}, 500);
});
} catch {