Row 89 landed on packages/harness/README.md. The patch applies cleanly; gate 14/14 green and 34/34 mutants killed on the new base. Co-Authored-By: Claude Opus 5.5 <[email protected]>
159 lines
8.3 KiB
Markdown
159 lines
8.3 KiB
Markdown
# 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 `ac37cd17`. Round 1 was built on `af01465c`. Row 89 landed at
|
|
`ce104b27` while this packet was going out, and it edits
|
|
`packages/harness/README.md`, one of the 13 paths (Dewey's N5). So I
|
|
applied the patch to a detached worktree of `ac37cd17` (it applied
|
|
cleanly), reran the gate and the mutants there, and rebuilt the patch and
|
|
manifest. Commit `4643911a` holds the same round 2 on `af01465c`; this
|
|
packet replaces it. Only the harness README's digest differs between the
|
|
two manifests. Its added and removed lines are the same 57 either way, and row 89's
|
|
lines in that file sit outside them.
|
|
|
|
`build.patch` is `git diff --cached --binary ac37cd17` of the 13 paths.
|
|
Apply it with `git apply --index` to a detached worktree of `ac37cd17`,
|
|
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
|
|
|
|
1. **The private `/proc`, `/dev` and IPC namespace.** Four rows in
|
|
`checks()`:
|
|
|
|
| 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/shm` | yes |
|
|
| see a SysV segment the host made | `awk '$2 == <id> …' /proc/sysvipc/shm` | no |
|
|
|
|
`standIns` now takes `t`. It writes the `/dev/shm` file (0600) and makes
|
|
a 4096-byte segment with `ipcmk -M`. `t.after` removes 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/sysvipc` is 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. The `awk` over
|
|
`/proc/sysvipc/shm` exits 1 in a private IPC namespace and 0 in a shared
|
|
one (probe below).
|
|
2. **pi runs in the sandbox.** A row runs
|
|
`<REPO>/node_modules/.bin/pi --version` and needs rc 0. The table's
|
|
session is pi, and so is the direct `binds()` sandbox in the second
|
|
test.
|
|
3. **The launcher's `sessionExit` call.** "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: reason `killed`, exit code null, signal
|
|
`SIGKILL`.
|
|
4. **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_CREDENTIALS` variables is left in the env, and launches the PM
|
|
(pi).
|
|
5. **cli README refusal row.** It names all three sets now.
|
|
6. **pi and `~/.pi/agent`.** The cli Sessions paragraph says a Pi session
|
|
logs in only through the variables `ENV_ALLOW` passes, 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 `Bash` or Pi's `bash`, 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 landed first, so this round is rebased onto it (above), and
|
|
the gate below ran on that tree.
|
|
|
|
## 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-ipc` printed "id … not found" and
|
|
exited 0, so it can't be the check.
|
|
- `awk '$2 == <id> { f = 1 } END { exit !f }' /proc/sysvipc/shm` exited 1
|
|
inside `--unshare-ipc`, 0 in the same sandbox without it, and 0 on the
|
|
host.
|
|
- Under bwrap's `--dev`, `/dev/shm` exists 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). It ran twice, with no other seat's gate or
|
|
mutant run going: on `af01465c` from 21:53:34Z to 21:58:15Z, and on
|
|
`ac37cd17` from 22:08:34Z to 22:12:57Z (`mutants.txt` is the second).
|
|
**34 of 34 killed** both times, each by the same test; only the timings
|
|
differ. 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/wt3` (`ac37cd17`
|
|
plus this patch, `node_modules` symlinked), 22:05:10Z to 22:08:27Z, 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 65, bus 108, webui 86 (row 89 and the rows before it added
|
|
harness, bus and webui tests since `af01465c`);
|
|
- scripts: auth 15, conductor 17, config 24, discord 66,
|
|
extension-package 18, foundation 44, queue 27, release 14, task 26
|
|
(`DOCKER_HOST` at a dead socket).
|
|
|
|
The seat count doesn't move because the five new rows run inside the two
|
|
existing sandbox tests. The run on `af01465c` (21:58:30Z to 22:01:56Z)
|
|
gave the same results with harness 59, bus 102 and webui 84. Each
|
|
test-release left an empty `<dir>_default` network of mine, and I removed
|
|
both.
|