Files
AgentCompany/AgentCompany-New/tests/unit/workspace-path.test.ts
T
ZacharyZhang-NY 867070f575 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.
2026-06-26 18:04:13 -04:00

32 lines
1.4 KiB
TypeScript

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");
});
});