fix(root): align pinned fallback path boundary checks
This commit is contained in:
parent
ecefc5bd53
commit
36dd0888a3
@ -8,7 +8,7 @@ const LINE_BUDGETS = new Map([
|
||||
["src/pinned-python.ts", 655],
|
||||
["src/root-impl.ts", 1750],
|
||||
["src/root-path.ts", 862],
|
||||
["test/api-coverage.test.ts", 982],
|
||||
["test/api-coverage.test.ts", 983],
|
||||
["test/new-primitives.test.ts", 1500],
|
||||
]);
|
||||
|
||||
|
||||
@ -1334,10 +1334,11 @@ async function resolvePinnedOperationPathInRoot(
|
||||
if ((relativeResolved === "" || relativeResolved === ".") && params.allowRoot === true) {
|
||||
return { rootReal: resolved.rootReal, resolved: resolved.canonicalPath, relativePosix: "" };
|
||||
}
|
||||
const firstSegment = relativeResolved.split(path.sep)[0];
|
||||
if (
|
||||
relativeResolved === "" ||
|
||||
relativeResolved === "." ||
|
||||
relativeResolved.startsWith("..") ||
|
||||
firstSegment === ".." ||
|
||||
path.isAbsolute(relativeResolved)
|
||||
) {
|
||||
throw new FsSafeError("outside-workspace", "file is outside workspace root");
|
||||
|
||||
@ -100,10 +100,15 @@ export async function makeTempLayout(
|
||||
return { outside, outsideFile, root };
|
||||
}
|
||||
|
||||
export function expectFsSafeCode(error: unknown, codes: readonly string[]): void {
|
||||
export function expectFsSafeCode(
|
||||
error: unknown,
|
||||
codes: readonly string[],
|
||||
opts: { allowUnsupportedPlatformOnWindows?: boolean } = {},
|
||||
): void {
|
||||
expect(error).toBeInstanceOf(FsSafeError);
|
||||
const accepted =
|
||||
process.platform === "win32" ? [...codes, "unsupported-platform"] : codes;
|
||||
const accepted = process.platform === "win32" && opts.allowUnsupportedPlatformOnWindows
|
||||
? [...codes, "unsupported-platform"]
|
||||
: codes;
|
||||
expect(accepted).toContain((error as FsSafeError).code);
|
||||
}
|
||||
|
||||
|
||||
@ -72,11 +72,15 @@ describe("read boundary bypass attempts", () => {
|
||||
return true;
|
||||
});
|
||||
await expect(safeRoot.stat("../secret.txt")).rejects.toSatisfy((error: unknown) => {
|
||||
expectFsSafeCode(error, ["outside-workspace", "invalid-path", "path-alias"]);
|
||||
expectFsSafeCode(error, ["outside-workspace", "invalid-path", "path-alias"], {
|
||||
allowUnsupportedPlatformOnWindows: true,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
await expect(safeRoot.list(".." as string)).rejects.toSatisfy((error: unknown) => {
|
||||
expectFsSafeCode(error, ["outside-workspace", "invalid-path", "path-alias"]);
|
||||
expectFsSafeCode(error, ["outside-workspace", "invalid-path", "path-alias"], {
|
||||
allowUnsupportedPlatformOnWindows: true,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
await expect(scope.files(["../secret.txt"])).resolves.toMatchObject({ ok: false });
|
||||
@ -96,11 +100,15 @@ describe("read boundary bypass attempts", () => {
|
||||
return true;
|
||||
});
|
||||
await expect(safeRoot.stat("link/secret.txt")).rejects.toSatisfy((error: unknown) => {
|
||||
expectFsSafeCode(error, ["outside-workspace", "path-alias", "symlink"]);
|
||||
expectFsSafeCode(error, ["outside-workspace", "path-alias", "symlink"], {
|
||||
allowUnsupportedPlatformOnWindows: true,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
await expect(safeRoot.list("link")).rejects.toSatisfy((error: unknown) => {
|
||||
expectFsSafeCode(error, ["outside-workspace", "path-alias", "symlink"]);
|
||||
expectFsSafeCode(error, ["outside-workspace", "path-alias", "symlink"], {
|
||||
allowUnsupportedPlatformOnWindows: true,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
});
|
||||
@ -120,7 +128,9 @@ describe("read boundary bypass attempts", () => {
|
||||
return true;
|
||||
});
|
||||
await expect(safeRoot.stat("secret-link.txt")).rejects.toSatisfy((error: unknown) => {
|
||||
expectFsSafeCode(error, ["outside-workspace", "path-alias", "symlink"]);
|
||||
expectFsSafeCode(error, ["outside-workspace", "path-alias", "symlink"], {
|
||||
allowUnsupportedPlatformOnWindows: true,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
@ -168,7 +178,9 @@ describe("read boundary bypass attempts", () => {
|
||||
return true;
|
||||
});
|
||||
await expect(safeRoot.stat(layout.outsideFile)).rejects.toSatisfy((error: unknown) => {
|
||||
expectFsSafeCode(error, ["outside-workspace", "path-alias", "invalid-path"]);
|
||||
expectFsSafeCode(error, ["outside-workspace", "path-alias", "invalid-path"], {
|
||||
allowUnsupportedPlatformOnWindows: true,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import path from "node:path";
|
||||
import { Readable } from "node:stream";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { fileStore, fileStoreSync } from "../src/file-store.js";
|
||||
import { root as openRoot } from "../src/index.js";
|
||||
import { configureFsSafePython, root as openRoot } from "../src/index.js";
|
||||
import {
|
||||
ESCAPING_DIRECTORY_PAYLOADS,
|
||||
ESCAPING_WRITE_PAYLOADS,
|
||||
@ -24,6 +24,7 @@ async function makeTempLayout(prefix: string) {
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
configureFsSafePython({ mode: "auto", pythonPath: undefined });
|
||||
await Promise.all(tempDirs.splice(0).map((dir) => fsp.rm(dir, { force: true, recursive: true })));
|
||||
});
|
||||
|
||||
@ -242,4 +243,18 @@ describe("write, move, and delete boundary bypass attempts", () => {
|
||||
}
|
||||
await expectNoOutsideWrite(layout);
|
||||
}, 15000);
|
||||
|
||||
it.runIf(process.platform !== "win32")("keeps literal '..'-prefixed paths available when the helper is disabled", async () => {
|
||||
configureFsSafePython({ mode: "off" });
|
||||
const layout = await makeTempLayout("fs-safe-write-helper-off-literal");
|
||||
const safeRoot = await openRoot(layout.root);
|
||||
|
||||
await safeRoot.write("..%2fpwned.txt", "literal");
|
||||
await expect(safeRoot.stat("..%2fpwned.txt")).resolves.toMatchObject({ isFile: true });
|
||||
await safeRoot.remove("..%2fpwned.txt");
|
||||
await expect(fsp.stat(path.join(layout.root, "..%2fpwned.txt"))).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
await expectNoOutsideWrite(layout);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user