fix(sandbox): --sandbox read-only was not read-only on Linux - #256
Conversation
Review — approve, no changesThis is the best kind of finding: two halves that each pass their own tests, and a bug that only exists between them. The framing is also right: Checked:
|
`buildLinuxBwrapArgs` ended with an unconditional `--bind <cwd> <cwd>`. bwrap applies binds in order and the last one wins, so under `read-only` the `--ro-bind-try` that `sandboxConfigForMode` had correctly asked for was overwritten a few arguments later. The mode resolved right, the profile said the right thing, and a command could still write to the workspace. macOS never had this: `buildMacOsProfile` grants writes only from `allowWrite`, which read-only leaves empty. #226 introduced the mode axis and live-verified it on macOS — where it caught the mirror-image bug, a profile that denied reads of the project directory. This is the half nobody looked at, which is exactly what THREE_WAY_REVIEW recorded under 仍未做. Found by writing the missing test rather than by reading the code: the bwrap integration suite only ever exercised the legacy `enabled: true` shape, so no test had spawned bwrap in a named mode to see what a command could actually do. The mode resolution was unit-tested and the arguments were argument-tested, and between those two correct halves the behaviour was wrong. An absent mode keeps the read-write bind, so callers on the legacy shape are unaffected. Linux CI now sets DC_REQUIRE_BWRAP=1. These are the only tests that observe what the sandbox does rather than what it builds, and they self-skip — a green suite that skipped every real enforcement check is how this survived. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7c12cee to
3902beb
Compare
Closes the 仍未做 item in
docs/THREE_WAY_REVIEW.md§7: "Linux (bwrap) 侧只共享了 sandbox 模式解析,没有在 Linux 主机上做过 #226 那样的实测。"Writing the missing test found a real bug on the first run.
The bug
buildLinuxBwrapArgsends with:unconditionally, after the
allowReadloop. bwrap applies binds in order and the last one wins, so underread-only:sandboxConfigForModesetsallowWrite: []and puts cwd inallowRead— correct;--ro-bind-try <cwd> <cwd>— correct;--bind <cwd> <cwd>, overriding it.The mode resolved correctly. The arguments were generated correctly by their own tests. Between two correct halves,
deepcode --sandbox read-onlylet a command write to the workspace on Linux.macOS never had this —
buildMacOsProfilegrants writes only fromallowWrite, which read-only leaves empty.Why it survived
#226 introduced the
--sandboxaxis and live-verified it on macOS, where it caught the mirror-image bug: a(deny default)profile with no cwd rule, so an enabled sandbox could not read the project directory. Linux got the shared mode resolution and nothing else.The existing
bwrap-integration.test.tsonly ever exercised the legacy{ enabled: true }shape. No test had spawned bwrap in a named mode to see what a command could actually do — and that is the only kind of test that could have caught this, since both halves pass in isolation.Change
read-onlyro-binds cwd instead of rw-binding it. An absent mode keeps the read-write bind, so callers on the legacyenabled: trueshape are unaffected.danger-full-accessproduces no bwrap at all,defaultModeapplies when config names none, and a library caller with no mode anywhere stays unsandboxed.No silent skips
Linux CI sets
DC_REQUIRE_BWRAP=1, turning a missing binary into a failure. These are the only tests that observe what the sandbox does rather than what it builds, and they self-skip — a green suite that skipped every real enforcement check is how this survived in the first place.Verification
typecheck, lint, format, docs clean; full suite green through the pre-commit hook. The bwrap integration tests cannot run on macOS, so the behavioural half is verified by this PR's Linux CI job, not locally — the argument-level assertions are what I could run here, and they fail against the old code.
🤖 Generated with Claude Code