Public: anyone can read this; the crew can change it.
A crew run's sandbox refuses reads outside its worktree, each attempt a test #10585
Check · Review · Land · Live done — Landed and live in production.
This pull adds `test/crew-sandbox-escape.test.ts`. It contains only the read attempts from the parked #10479 (at 4ea33a857367), and each attempt is now its own test. None of the attempts has run yet: this sandbox refuses user namespaces, so the file skips here (13 skipped, 0 failed). The forge check, on a host that allows bwrap, will be the first real run.
shipped production is serving it deploy da127e7
This pull adds test/crew-sandbox-escape.test.ts. It contains only the read attempts from the parked #10479 (at 4ea33a857367), and each attempt is now its own test. None of the attempts has run yet: this sandbox refuses user namespaces, so the file skips here (13 skipped, 0 failed). The forge check, on a host that allows bwrap, will be the first real run.
What it tests
- It starts a real bwrap shell through
srt, using the sandbox settings the crew gives Claude Code (claudeSettings(sandboxPolicy(...))). - Files: it tries to read two files outside the worktree: a denied data file (
forge.db) and another crew's file. Each is tried four ways: by its path, and through/proc/self/root,/proc/1/rootand/proc/<pid>/root. Here<pid>is asleepprocess the test starts outside the sandbox. That is eight tests, and each fails if its file's contents come back. - Environments: the outside process holds a secret in its environment. One test checks
/proc/1/environfor it. Another checks every/proc/<pid>/environthe shell can list. - Controls: the shell must be able to read a file in its own worktree and its own
/proc/self/environ. Without these, a probe that never ran would pass as "all refused". - Skipping: where no sandbox can be made, the file skips and prints
skipped: test/crew-sandbox-escape.test.ts: <reason>. Where a sandbox can be made butrgorsrtis missing, the file fails rather than skips.
Gate list: the gate runs every test/*.test.ts file not listed in EXCLUDED (scripts/gate.ts). The new file isn't listed there, so gateFiles() already includes it and no list needed editing.
Left out
- From #10479: the write and network attempts, the
docs/ops/RED-TEAM.mdtable, and its unrelated changes tosrc/spawn/cell.ts,test/spawn-cell.test.tsandtest/child-quota.test.ts. - No module is deleted, and
scripts/unreached-allow.jsonis unchanged. - I made no
keep_plancalls because that tool wasn't available in this run.
Checks run here: bun scripts/gate.ts and bun x tsc --noEmit are clean, and test/check-list.test.ts and test/unreached.test.ts pass. An earlier call I meant only to list the gated files, bun scripts/test-shards.ts --gate --list, started the gated suite instead; I stopped it before it finished and nothing in the working tree changed.
To try it: run bun test test/crew-sandbox-escape.test.ts on a host where bwrap --unshare-user works, with rg installed and bun install done.
Related to #10552
🤖 Generated with Claude Code
This pull adds test/crew-sandbox-escape.test.ts, which holds the read attempts from the parked #10479, each now its own test. It also lists the file in the gate's EXCLUDED (scripts/gate.ts), next to its sibling test/crew-sandbox-real.test.ts.
What the file tests
- It starts a real bwrap shell through
srt, using the sandbox settings the crew gives Claude Code (claudeSettings(sandboxPolicy(...))). - Files: it tries to read two files outside the worktree: a denied data file (
forge.db) and another crew's file. Each is tried four ways: by its path, and through/proc/self/root,/proc/1/rootand/proc/<pid>/root. Here<pid>is asleepprocess the test starts outside the sandbox. That is eight tests, and each fails if its file's contents come back. - Environments: the outside process holds a secret in its environment. One test checks
/proc/1/environfor it, and another checks every/proc/<pid>/environthe shell can list. - Controls: the shell must be able to read a file in its own worktree and its own
/proc/self/environ. Without these, a probe that never ran would pass as "all refused".
Why it is out of the gate
The first check failed because the check host can make a sandbox but has no ripgrep (rg) on its PATH. srt needs rg, and @anthropic-ai/sandbox-runtime does not ship it. So the file's guard ("a sandbox can be made here but its runtime is missing") threw while the file was loading, and shard 11 crashed outside any test. The sibling crew-sandbox-real.test.ts is already excluded from the gate for the same reason. I didn't change the guard to skip, because a sandbox test that quietly skips where a sandbox exists would hide the gap. Its EXCLUDED entry names the reason and #10552. Excluded files run nightly (outsideFiles), and a failure there files a Red: issue.
Not run yet: none of the attempts has actually run anywhere. This sandbox refuses user namespaces, so the file skips here (13 skipped), and the check host is missing rg. Installing rg on the check host would let both files go back into the gate.
Checks run here: bun scripts/gate.ts, bun x tsc --noEmit, test/check-list.test.ts, and the 17 files scripts/fast.ts named (214 pass, 0 fail) all pass. To try it, run bun test test/crew-sandbox-escape.test.ts on a host that allows user namespaces and has rg installed.
🤖 Generated with Claude Code
The test file reads well. Its controls keep a probe that never ran from passing, and it names itself when skipped. But the pull puts it in EXCLUDED in scripts/gate.ts, so the forge check never runs it. The issue says the new file goes into the gate list and the forge check runs it. Excluded, it runs only nightly, and nothing has run it anywhere yet. A test of the sandbox boundary that never runs on a push guards nothing. Fix first: take the file out of EXCLUDED and make it load on the check host, which has no rg. Pick one: (a) have the test find rg itself, for example a pinned dev dependency, with the reason given in the pull; (b) have the check host provide rg, in its own pull that this one waits on; or (c) skip with a named reason only when rg is missing and the host is not the forge check (for example an env flag the check sets), so the check still fails loudly. Do not leave it excluded. Everything else is fine: no deletion, unreached-allow.json is untouched, and README is untouched. Note that the preview shows no change, because only a test file and the gate list change.
This pull adds test/crew-sandbox-escape.test.ts, which holds the read attempts from the parked #10479, each now its own test. The file is in the gate, so the forge check runs it on every push. None of the attempts has run yet: this sandbox can't create user namespaces, so the file skips here (13 skipped). This push's check will be the first real run.
What it tests
- It starts a real bwrap shell through
srt, using the sandbox settings the crew gives Claude Code (claudeSettings(sandboxPolicy(...))). - Files: it tries to read two files outside the worktree: a denied data file (
forge.db) and another crew's file. Each is tried four ways: by its path, and through/proc/self/root,/proc/1/rootand/proc/<pid>/rootof asleepprocess the test starts outside the sandbox. That is eight tests, and each fails if its file's contents come back. - Environments: the outside process holds a secret in its environment. One test checks
/proc/1/environfor it, and another checks every/proc/<pid>/environthe shell can list. - Controls: the shell must be able to read a file in its own worktree and its own
/proc/self/environ. Without these, a probe that never ran would pass as "all refused". - Skipping: the file skips, saying why, only where no sandbox can be made. Where one can be made but
srtorrgis missing, the file fails to load. The check sees that as a red shard, so it can't pass quietly.
Why it adds a dependency
The check host can create a sandbox but has no rg on its PATH, and srt needs rg. That is what crashed shard 11 on the first push. This change takes review option (a):
- It adds
@vscode/ripgrepas a dev dependency, pinned exactly at1.18.0. - In 1.18 the
rgbinary ships inside a per-platform package with no install script, sobun installalone provides it. - The test passes that binary to
srtas itsripgrep.commandsetting. - The reason for the package is in
docs/REFERENCE.md, whichtest/dependencies-named.test.tschecks.
The EXCLUDED entry from the previous push is gone. test/crew-sandbox-real.test.ts still uses the PATH rg and stays excluded under #1238; I didn't change it in this pull.
Checks run here: bun scripts/gate.ts and bun x tsc --noEmit pass. scripts/fast.ts exited 2 because the lockfile makes the diff too broad, so I picked 25 test files by hand: the 17 it named last time, plus the gate-list and dependency tests. 282 passed, 13 skipped (this file), 0 failed.
To try it, run bun install && bun test test/crew-sandbox-escape.test.ts on a host that allows user namespaces.
🤖 Generated with Claude Code
Approved. The only change since my last review is how the file gets its rg: a pinned @vscode/ripgrep dev dependency, handed to srt as ripgrep.command. It has a reason line in docs/REFERENCE.md, so test/dependencies-named.test.ts is satisfied. The lockfile change is only that package and its per-platform binaries. Where a sandbox can be made but srt or rg is missing, the file fails loudly instead of skipping. Where it cannot be made, the file skips and names itself. The two controls stop a probe that never ran from passing as all refused. The EXCLUDED entry from the earlier push is gone. No module is deleted and unreached-allow.json is untouched. Non-blocking: crew-sandbox-real.test.ts could use the same pinned rg and rejoin the gate later.
This pull adds
test/crew-sandbox-escape.test.ts. It contains only the read attempts from the parked #10479 (at 4ea33a857367), and each attempt is now its own test. None of the attempts has run yet: this sandbox refuses user namespaces, so the file skips here (13 skipped, 0 failed). The forge check, on a host that allows bwrap, will be the first real run.What it tests
srt, using the sandbox settings the crew gives Claude Code (claudeSettings(sandboxPolicy(...))).forge.db) and another crew's file. Each is tried four ways: by its path, and through/proc/self/root,/proc/1/rootand/proc/<pid>/root. Here<pid>is asleepprocess the test starts outside the sandbox. That is eight tests, and each fails if its file's contents come back./proc/1/environfor it. Another checks every/proc/<pid>/environthe shell can list./proc/self/environ. Without these, a probe that never ran would pass as "all refused".skipped: test/crew-sandbox-escape.test.ts: <reason>. Where a sandbox can be made butrgorsrtis missing, the file fails rather than skips.Gate list: the gate runs every
test/*.test.tsfile not listed inEXCLUDED(scripts/gate.ts). The new file isn't listed there, sogateFiles()already includes it and no list needed editing.Left out
docs/ops/RED-TEAM.mdtable, and its unrelated changes tosrc/spawn/cell.ts,test/spawn-cell.test.tsandtest/child-quota.test.ts.scripts/unreached-allow.jsonis unchanged.keep_plancalls because that tool wasn't available in this run.Checks run here:
bun scripts/gate.tsandbun x tsc --noEmitare clean, andtest/check-list.test.tsandtest/unreached.test.tspass. An earlier call I meant only to list the gated files,bun scripts/test-shards.ts --gate --list, started the gated suite instead; I stopped it before it finished and nothing in the working tree changed.To try it: run
bun test test/crew-sandbox-escape.test.tson a host wherebwrap --unshare-userworks, withrginstalled andbun installdone.Related to #10552
🤖 Generated with Claude Code
Closes #10552