import assert from "node:assert/strict"; import { mkdtemp, mkdir, readFile, readdir, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import test from "node:test"; import { ReviewWriteError, writeSecurityReview } from "./core.ts"; async function project(): Promise { return mkdtemp(join(tmpdir(), "security-review-writer-")); } async function rejectsWithCode(operation: Promise, code: string): Promise { await assert.rejects(operation, (error: unknown) => error instanceof ReviewWriteError && error.code === code); } test("security-review-write-atomic", async () => { const cwd = await project(); const relativePath = join("worker-1", "review.md"); const target = join(cwd, "ai-workspace", "security-reviews", relativePath); const created = await writeSecurityReview({ cwd, path: relativePath, text: "first ä" }); assert.equal(created.replaced, false); assert.equal(await readFile(target, "utf8"), "first ä"); const overwritten = await writeSecurityReview({ cwd, path: resolve(target), text: "second ✓" }); assert.equal(overwritten.replaced, true); assert.equal(await readFile(target, "utf8"), "second ✓"); assert.deepEqual((await readdir(join(cwd, "ai-workspace", "security-reviews", "worker-1"))).sort(), ["review.md"]); }); test("security-review-path-containment", async () => { const cwd = await project(); const outside = join(cwd, "outside.md"); await rejectsWithCode(writeSecurityReview({ cwd, path: outside, text: "no" }), "OUTSIDE_ROOT"); await rejectsWithCode(writeSecurityReview({ cwd, path: join("..", "escape.md"), text: "no" }), "INVALID_PATH"); await rejectsWithCode(writeSecurityReview({ cwd, path: "..\\escape.md", text: "no" }), "INVALID_PATH"); const directory = join(cwd, "ai-workspace", "security-reviews", "directory-target"); await mkdir(directory, { recursive: true }); await rejectsWithCode(writeSecurityReview({ cwd, path: "directory-target", text: "no" }), "TARGET_IS_DIRECTORY"); }); test("security-review-symlink-defense", async () => { const cwd = await project(); await writeSecurityReview({ cwd, path: "seed.md", text: "seed" }); const root = join(cwd, "ai-workspace", "security-reviews"); const outside = await mkdtemp(join(tmpdir(), "security-review-outside-")); const linkedParent = join(root, "linked-parent"); await symlink(outside, linkedParent, process.platform === "win32" ? "junction" : "dir"); await rejectsWithCode(writeSecurityReview({ cwd, path: join("linked-parent", "escaped.md"), text: "no" }), "SYMLINK_ESCAPE"); await rejectsWithCode(writeSecurityReview({ cwd, path: "linked-parent", text: "no" }), "SYMLINK_ESCAPE"); await assert.rejects(readFile(join(outside, "escaped.md"), "utf8"), { code: "ENOENT" }); if (process.platform !== "win32") { const outsideFile = join(outside, "outside.md"); await writeFile(outsideFile, "unchanged", "utf8"); await symlink(outsideFile, join(root, "linked-file.md"), "file"); await rejectsWithCode(writeSecurityReview({ cwd, path: "linked-file.md", text: "no" }), "SYMLINK_ESCAPE"); assert.equal(await readFile(outsideFile, "utf8"), "unchanged"); } });