Contain workspace file ingestion to the bound folder (sec #3)
writeDeliverable guards against path traversal, but recordWorkspaceFile resolved and readFileSync'd its path with no containment check — safe only because today's lone caller hands it the already-sanitized result. Any future caller passing a raw agent path (../../.ssh/id_rsa) could read an arbitrary file into the org's file store. Extract the resolve+reject-".."/absolute guard into one workspace-path helper and route both the writer and the reader through it, so the defense lives on the function rather than the caller.
This commit is contained in:
1 parent
6367512052
commit
867070f575
4 files changed
+61
-15
No files matched your search
@@ -0,0 +1,31 @@
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { resolveWorkspacePath } from "../../src/main/services/files/workspace-path";
|
||||
|
||||
describe("resolveWorkspacePath", () => {
|
||||
const root = join(tmpdir(), "ac-workspace-root");
|
||||
|
||||
test("keeps a normal relative path inside the workspace", () => {
|
||||
const { absolute, relativePosix } = resolveWorkspacePath(root, "plan/business-plan.md");
|
||||
expect(relativePosix).toBe("plan/business-plan.md");
|
||||
expect(absolute.startsWith(resolve(root))).toBe(true);
|
||||
});
|
||||
|
||||
test("rejects paths that escape the workspace via traversal", () => {
|
||||
expect(() => resolveWorkspacePath(root, "../../etc/passwd")).toThrow("escapes the workspace root");
|
||||
expect(() => resolveWorkspacePath(root, "plan/../../../secret.key")).toThrow("escapes the workspace root");
|
||||
});
|
||||
|
||||
test("normalises a leading slash and refuses real drive-absolute paths", () => {
|
||||
// A leading slash is treated as workspace-relative, not a filesystem root.
|
||||
expect(resolveWorkspacePath(root, "/notes.md").relativePosix).toBe("notes.md");
|
||||
if (process.platform === "win32") {
|
||||
expect(() => resolveWorkspacePath(root, "C:\\Windows\\System32")).toThrow("must be relative");
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects an empty path", () => {
|
||||
expect(() => resolveWorkspacePath(root, " ")).toThrow("required");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user