(sandbox): refuse extra binds at or under .claude and .git (#385) - #406
Conversation
An extra bind inside a .claude or .git was mounted read-write after, and over, the read-only protection of the same path. The wrapper now refuses such a bind (as spelled and by real path), refusedScheduleBinds applies the same rule and compares real paths, the worktree search's limits are documented, and resolveScheduleSandbox's path.resolve is pinned by a test. Refs #385
|
Reviewing |
|
Adversarial review at |
The working directory was exempt from the .claude/.git refusal, so a session in <proj>/.claude with <proj> bound got a read-write bind over the read-only one. It is now refused except below .claude/worktrees. The $HOME/parent check compared text, so $HOME/ or a link to $HOME passed; it now compares the normalised and real forms, and a path that cannot be resolved is refused. A schedule's relative add-dirs are resolved against the schedule's directory, and the skip message names the reason. Refs #385
|
Reviewing |
|
Re-review at |
# Conflicts: # CHANGELOG.md
Refs #385 (items 1, 4 and 5; items 2 and 3 are not changed, see below).
What changed
scripts/claude-sandbox.shrefuses (status 125, names the path) an extra bind (SWITCHBOARD_SANDBOX_BINDS: Additional Directories, a worktree's project root) that is at or inside a.claudeor.gitdirectory. The path is judged both normalised as spelled (realpath -m -s:.., trailing slash) and by real path (readlink -m: links), so a link to a.claude, or a.claudethat is itself a link, does not get through. The working directory gets the same rule, except below.claude/worktrees. The$HOMEcheck compares the same two forms, so$HOME/,$HOME/.and a link to it are refused. Relativeadd-dirsof a schedule resolve against the schedule's directory, and the skip message names the reason. A missing path under a.claudeor.gitis refused too; a path that cannot be resolved at all is refused.refusedScheduleBindsinschedule-runner.jsapplies the same rule before the project check, anywhere (inside a registered project, outside$HOME), and now compares real paths for the$HOME/ known-project check (newcanonicalPath: realpath of the nearest existing ancestor plus the rest).docs/sandbox.md: new "Additional directories" section; the-xdev/node_modulesgap of the worktree search is documented under "Git" (item 4)..ai/contexts/schedule-runner.mdupdated. CHANGELOG entry under Unreleased.Tests
test/sandbox-wrapper.test.js: "refuses an extra bind at or under a .claude or .git, however the path is spelled" (11 refused spellings, 6 allowed controls such as.claude-notes,x.git,.claude/..).test/schedule-project-provenance.test.js: add-dir under.claude/.gitrefused inside a project and outside$HOME; links judged by target and a.claudelink judged as spelled (skipped where links cannot be created); a relative cwd forresolveScheduleSandbox(item 5).readlink -mform, without therealpath -m -sform, without the.gitpattern, without the.claudepattern;refusedScheduleBindswithout the lexical check, without the real-path check, withoutcanonicalPath, without the.gitname;resolveScheduleSandboxwithoutpath.resolve(cwd)(item 5). One mutation initially survived (lexical check); the.claude-is-a-link case was added and kills it.Not done
project:setting. Recommendation: the second.open-terminalregisters the renderer'sprojectPath): read, not changed. The path comes from the sidebar's folder metadata (derived from transcripts) and registration needs the user to start a session in that project; a planted folder is only registered by that act. Worth re-checking when (triggers): let a session open sister sessions and follow their state #380 shares the helper.Not verified
No real bwrap run of the new refusal (it exits before bwrap).
task checkon Windows: lint 0 errors; 3 failures, none in touched files: twomeasureUntrackedLocaltests ingit-changes-runner.test.js(fail identically on the untouched main checkout at 4608df0) andviewer-file-watch(the known libuv_wcsnicmpassertion).