fix(security): harden browser trace/download temp path handling

This commit is contained in:
Peter Steinberger
2026-02-26 01:01:17 +01:00
parent e56b0cf1a0
commit 496a76c03b
8 changed files with 322 additions and 40 deletions

View File

@@ -7,6 +7,7 @@ import {
resolvePathsWithinRoot,
resolvePathWithinRoot,
resolveStrictExistingPathsWithinRoot,
resolveWritablePathWithinRoot,
} from "./paths.js";
async function createFixtureRoot(): Promise<{ baseDir: string; uploadsDir: string }> {
@@ -245,6 +246,45 @@ describe("resolvePathWithinRoot", () => {
});
});
describe("resolveWritablePathWithinRoot", () => {
it("accepts a writable path under root when parent is a real directory", async () => {
await withFixtureRoot(async ({ uploadsDir }) => {
const result = await resolveWritablePathWithinRoot({
rootDir: uploadsDir,
requestedPath: "safe.txt",
scopeLabel: "uploads directory",
});
expect(result).toEqual({
ok: true,
path: path.resolve(uploadsDir, "safe.txt"),
});
});
});
it.runIf(process.platform !== "win32")(
"rejects write paths routed through a symlinked parent directory",
async () => {
await withFixtureRoot(async ({ baseDir, uploadsDir }) => {
const outsideDir = path.join(baseDir, "outside");
await fs.mkdir(outsideDir, { recursive: true });
const symlinkDir = path.join(uploadsDir, "escape-link");
await fs.symlink(outsideDir, symlinkDir);
const result = await resolveWritablePathWithinRoot({
rootDir: uploadsDir,
requestedPath: "escape-link/pwned.txt",
scopeLabel: "uploads directory",
});
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error).toContain("must stay within uploads directory");
}
});
},
);
});
describe("resolvePathsWithinRoot", () => {
it("resolves all valid in-root paths", () => {
const result = resolvePathsWithinRoot({