SPB Git

spb/autoscholar

Public

Policy: deny nested .claude/.git/.ssh/.aws/.config paths in the workspace

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Oct 30, 2026) parent 5ba599e Browse files

2 changed files +5 −3

modified services/research-runner/src/policy.test.ts +2 −0
@@ -77,6 +77,8 @@ describe("platform policy hook", () => {
77 expect(isDeny(await call("Edit", { file_path: join(ws, "CLAUDE.md") }))).toBe(true); 77 expect(isDeny(await call("Edit", { file_path: join(ws, "CLAUDE.md") }))).toBe(true);
78 expect(isDeny(await call("Write", { file_path: join(ws, "input/run.json") }))).toBe(true); 78 expect(isDeny(await call("Write", { file_path: join(ws, "input/run.json") }))).toBe(true);
79 expect(isDeny(await call("Write", { file_path: join(ws, ".claude/settings.json") }))).toBe(true); 79 expect(isDeny(await call("Write", { file_path: join(ws, ".claude/settings.json") }))).toBe(true);
80+ expect(isDeny(await call("Write", { file_path: join(ws, "code/.claude/settings.json") }))).toBe(true);
81+ expect(isDeny(await call("Write", { file_path: join(ws, "code/.git/hooks/pre-commit") }))).toBe(true);
80 expect(isDeny(await call("Write", { file_path: join(ws, "results/link/evil.txt") }))).toBe(true); // symlink to outside 82 expect(isDeny(await call("Write", { file_path: join(ws, "results/link/evil.txt") }))).toBe(true); // symlink to outside
81 }); 83 });
82 it("reviewer sessions may only write under review/", async () => { 84 it("reviewer sessions may only write under review/", async () => {
modified services/research-runner/src/policy.ts +3 −3
@@ -202,10 +202,10 @@ export function createPolicyHook(opts: PolicyOptions): HookCallback {
202 const rel = relativeInWorkspace(opts.workspace, p); 202 const rel = relativeInWorkspace(opts.workspace, p);
203 if (rel === null) return deny(input, "write outside the run workspace", { path: p }); 203 if (rel === null) return deny(input, "write outside the run workspace", { path: p });
204 if ( 204 if (
205− rel.startsWith(".claude/") || 205+ rel.split("/").includes(".claude") ||
206− rel === ".claude" ||
207 rel.startsWith(".autoscholar/") || 206 rel.startsWith(".autoscholar/") ||
208− rel.startsWith("home/") 207+ rel.startsWith("home/") ||
208+ rel.split("/").some((seg) => [".git", ".ssh", ".aws", ".config"].includes(seg))
209 ) 209 )
210 return deny(input, "platform-reserved directory", { path: rel }); 210 return deny(input, "platform-reserved directory", { path: rel });
211 for (const f of opts.frozenPaths) 211 for (const f of opts.frozenPaths)
212 212
Menu

Theme