Filbert's six changes and Dewey's N1-N3: five table rows, two launcher tests, README sentences. 34/34 mutants killed, 14 suites green. Co-Authored-By: Claude Opus 5.5 <[email protected]>
7.5 KiB
Row 92, role sessions confined to their own files, round 2 build (Darkwing)
Issue #1586. Round 1 packet: agents/darkwing/work/queue-92/ (candidate
manifest bc132570…). Round 1 verdicts: Dewey approved with notes N1 to
N5 (comment 27942, rev 771). Filbert asked for six changes (comment 27951,
rev 772).
Base af01465c, as in round 1. No commit from the base to the packet's
commit touches the 13 paths. build.patch is
git diff --cached --binary af01465c of the same 13 paths. Apply it with
git apply --index to a detached worktree of the base, and
sha256sum -c candidate-manifest.sha256 then gives 13/13 OK.
What changed since round 1
No source file moved. sandbox.mjs, session.mjs, launcher.mjs,
gate.mjs, the unit and the other test files are byte for byte round 1's.
Five files moved:
| File | What changed | For |
|---|---|---|
packages/seat/tests/sandbox.test.mjs |
five rows in the confined-shell table; standIns makes a host /dev/shm file and a SysV segment |
Filbert 1 and 2 |
packages/cli/tests/launcher.test.mjs |
two tests: a pi launch with no credential, a runner killed from the host | Filbert 3 and 4 |
packages/cli/README.md |
the refusal table row names the proxy set; a paragraph on how a Pi session logs in; the shell clause covers Claude Code's Bash |
Filbert 5 and 6, Dewey N1 and N2 |
packages/seat/README.md |
one sentence: Pi logs in only through ENV_ALLOW's variables |
Filbert 6, Dewey N1 |
packages/harness/README.md |
Limits: the credential variable is readable from bash; /proc/net/unix lists the host's socket names |
Dewey N2 and N3 |
Filbert's changes
-
The private
/proc,/devand IPC namespace. Four rows inchecks():Row Command May read a stand-in in the host's /dev/shm cat /dev/shm/<the test's scratch name>no see the test's own pid in /proc test -e /proc/<the test's pid>no read its own IPC namespace's segment list test -r /proc/sysvipc/shmyes see a SysV segment the host made awk '$2 == <id> …' /proc/sysvipc/shmno standInsnow takest. It writes the/dev/shmfile (0600) and makes a 4096-byte segment withipcmk -M.t.afterremoves both.session()checks on the host that the file, the test's own/proc/<pid>and the segment all exist before any row runs, so a refusal inside means the sandbox hid them. The readable row stops the segment row from passing just because/proc/sysvipcis missing.I tried
ipcs -m -i <id>first. It exits 0 even when it prints "not found", so it can't tell the namespaces apart. Theawkover/proc/sysvipc/shmexits 1 in a private IPC namespace and 0 in a shared one (probe below). -
pi runs in the sandbox. A row runs
<REPO>/node_modules/.bin/pi --versionand needs rc 0. The table's session is pi, and so is the directbinds()sandbox in the second test. -
The launcher's
sessionExitcall. "a runner killed from the host ends its launch as killed, not failed" launches the PM, waits for its claim, sends SIGKILL to the runner's host pid (bwrap's child) and reads the launch log's end entry: reasonkilled, exit code null, signalSIGKILL. -
A pi launch with no credential. "a pi launch needs no session credential" drops the fixture's stand-in, checks that none of the
SESSION_CREDENTIALSvariables is left in the env, and launches the PM (pi). -
cli README refusal row. It names all three sets now.
-
pi and
~/.pi/agent. The cli Sessions paragraph says a Pi session logs in only through the variablesENV_ALLOWpasses, never reads the operator's~/.pi/agent, has no launch credential check, and so fails at its first turn on a provider whose login lives only in those files. The seat README's sandbox section says the same in one sentence. No code, as Filbert asked.
Dewey's notes
- N1 is Filbert's change 6, above.
- N2. The cli Sessions paragraph now says a shell the harness opens,
Claude Code's
Bashor Pi'sbash, can read the credential variables. The harness Limits bullet "The gate is not the sandbox" says the same. - N3. The Limits bullet on the network now names
/proc/net/unix: it lists the host's Unix sockets by name, path ones included, and a name gives no access to a path socket the mounts hide. - N4 (seccomp,
--disable-userns) stays outside this row, as Dewey said. - N5. Row 89 still hasn't landed. Whichever of 89 and 92 lands second
reruns its gate on the tree it lands on, and its
packages/harness/README.mddigest will move.
Probes
Run 2026-10-12 on this host, outside the tests, with a 4096-byte segment
from ipcmk -M, removed afterwards with ipcrm -m:
ipcs -m -i <id>inside--unshare-ipcprinted "id … not found" and exited 0, so it can't be the check.awk '$2 == <id> { f = 1 } END { exit !f }' /proc/sysvipc/shmexited 1 inside--unshare-ipc, 0 in the same sandbox without it, and 0 on the host.- Under bwrap's
--dev,/dev/shmexists and is empty.
After the seat suite and after the mutant run, /dev/shm held no
mosaic-sandbox-* file and ipcs -m showed no 4096-byte segment of
ours.
Mutants
mutants.sh here: round 1's 26 unchanged, then 8 more. It ran with a
copy of agents/darkwing/work/queue-59-review/r1/mut.mjs (sha256
a1ed6bca…, round 1's helper) from 21:53:34Z to 21:58:15Z, with no other
seat's gate or mutant run going (mutants.txt). 34 of 34 killed.
Every source file was restored and still matches the manifest.
| Mutant | Filbert's | Killed by |
|---|---|---|
no-ipc-namespace (--unshare-ipc dropped) |
survived | the row "see a SysV segment the host made" |
host-proc (--ro-bind /proc) |
survived | the row "see the test's own pid in /proc" |
host-dev (--dev-bind /dev) |
survived | the row "read a stand-in in the host's /dev/shm" |
pi-no-node-modules |
survived | the row "run pi, the session's harness" |
launcher-raw-exit (resolve({ code, signal })) |
survived | "a runner killed from the host ends its launch as killed, not failed" |
credential-every-harness |
survived | "a pi launch needs no session credential" |
pdeathsig-term |
killed | the hard-death recovery test, as in Filbert's run |
workspace-read-only |
killed | the row "write and read its workspace" |
In each of the four table mutants, both sandbox tests failed on that one
row and no other: the session test and the expose test's session run.
exit-code-not-mapped (round 1) is now also caught by the host-kill test.
Filbert's other five I didn't repeat. no /etc, 0755 home/tmp and
recover on start time alone stay as they noted: the first isn't
demonstrable offline, the second is equivalent, the third is a second
guard. claude bin at its real path only and sandbox check for Claude Code only were already killed in their run.
Gate
gate.sh, run sequentially in ~/darkwing-scratch/r92/wt (the base plus
this patch, node_modules symlinked), 21:58:30Z to 22:01:56Z, after a
check that no other seat's gate or mutant run was going
(gate-summary.txt). All 14 suites exited 0, 0 skipped:
- node: seat 33, cli 120 (round 1's 118 plus the two launcher tests), harness 59, bus 102, webui 84;
- scripts: auth 15, conductor 17, config 24, discord 66,
extension-package 18, foundation 44, queue 27, release 14, task 26
(
DOCKER_HOSTat a dead socket).
The seat count doesn't move because the five new rows run inside the two
existing sandbox tests. test-release left an empty wt_default network,
created at 22:01:44Z, the second test-release started; I removed it.