Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
# Role sessions confined to their own files (2026-10-12)
|
||||
|
||||
Status: written by Sage, lead. It follows `docs/plans/BRIEF-TEMPLATE.md`.
|
||||
It answers Vikunja P19 #232 (security, priority 5), which the stack's
|
||||
own reviewer session found in Jason's first test and Mos checked at
|
||||
9438880e (sage-0102). Gitea #1586. It comes before every other row from
|
||||
that test.
|
||||
|
||||
## Role sessions confined to their own files
|
||||
|
||||
### Problem
|
||||
|
||||
The tool gate checks the paths of Read, Write, Edit, Grep and Glob
|
||||
(`packages/harness/src/gate.mjs:22-25`) and nothing else. Bash, which
|
||||
the coder, cto, reviewer and researcher roles have (`roles/*.json`
|
||||
`tools`), runs as the host's user with only a user and PID namespace
|
||||
(`packages/seat/src/session.mjs:39`). It sees the whole filesystem. In
|
||||
the test the reviewer's Grep of the coder's workspace was refused and
|
||||
its Bash `ls` of the same directory ran. The same shell can read
|
||||
`~/.config/mosaic-dev/secrets`, `~/.mosaic`, `~/.ssh`,
|
||||
`~/.git-credentials`, every other workspace and run directory, and the
|
||||
canonical checkout. The harness README says so (`README.md:162-173`, "The
|
||||
gate is not a sandbox"); slice 1 accepted that limit (S0 line 5), and
|
||||
Jason's test shows the stack can't run role sessions on its own source
|
||||
with it.
|
||||
|
||||
### Owner and reviewers
|
||||
|
||||
Owner: Darkwing, parking row 63 (in progress) until this lands.
|
||||
Reviewers: Filbert and Dewey, both required, since this sets a security
|
||||
boundary.
|
||||
|
||||
### Files owned
|
||||
|
||||
- `packages/seat/src/session.mjs`, the spawn and its arguments;
|
||||
`packages/seat/src/sandbox.mjs`, new
|
||||
- `packages/cli/src/launcher.mjs`, only where it passes the paths a
|
||||
session needs
|
||||
- `packages/harness/src/gate.mjs`, the header comment only
|
||||
- `packages/harness/README.md` "Limits", `packages/seat/README.md`
|
||||
- tests under `packages/seat/tests/`, `packages/cli/tests/` and
|
||||
`packages/harness/tests/`
|
||||
- `agents/darkwing/work/queue-<row>/`
|
||||
|
||||
### What ships
|
||||
|
||||
- Every role session's process tree runs inside a bubblewrap
|
||||
(`/usr/bin/bwrap`, 0.12.0 on the host) mount namespace built from an
|
||||
empty root. It sees, read-only: the system directories the harness and
|
||||
its tools need (`/usr`, `/etc` and the like), the pinned harness
|
||||
binaries, and the repository files the runner loads. It sees,
|
||||
read-write: its own workspace and its own run directory. It gets a
|
||||
private `/tmp` and an empty `HOME`, plus whatever the harness needs
|
||||
there, created inside the run directory. It sees nothing else: no
|
||||
data root, no other workspace or run directory, no `~/.config`, no
|
||||
`~/.mosaic`, no `~/.ssh`, no checkout outside what the runner loads.
|
||||
The PID namespace and kill-on-parent-exit stay.
|
||||
- The packet lists every bind with its source, mode and why the session
|
||||
needs it. A bind the reviewers can't tie to a need comes out.
|
||||
- A missing or failing `bwrap` refuses the launch with
|
||||
`sandbox-unavailable`, through the broker's `refuse()`. There is no
|
||||
unconfined fallback.
|
||||
- The network stays as it is. The harness needs its model endpoint, and
|
||||
the README keeps saying the network isn't confined.
|
||||
- Tests run a real confined session's shell and prove these refuse:
|
||||
reading a file under a stand-in `~/.config/mosaic-dev/secrets`, under a
|
||||
stand-in `~/.mosaic`, in another instance's workspace, in another run
|
||||
directory, in the data root's store, and in the canonical checkout
|
||||
outside the runner's files; writing outside the workspace and run
|
||||
directory. They prove the session can still read and write its
|
||||
workspace, run its harness and reach its tool socket. A test kills a
|
||||
bind from the list and shows the matching check fail, so the checks
|
||||
can't pass on an unconfined session.
|
||||
- The harness README's "Limits" section says what is confined now and
|
||||
what isn't (network, abstract sockets, same UID outside the namespace).
|
||||
|
||||
Suites: the seat, cli, harness, bus and webui node suites, and every
|
||||
`scripts/test-*.sh`. The packet also shows one real coder launch on the
|
||||
host with Claude Code, reading a stand-in secret from its Bash and
|
||||
getting a refusal.
|
||||
|
||||
### Out of scope
|
||||
|
||||
Network confinement. Different UIDs per role. Containers (rows 86 and
|
||||
87, I1). Removing Bash from any role. The tool gate's own checks.
|
||||
|
||||
### Gate
|
||||
|
||||
Filbert and Dewey approve on the row's issue, naming the candidate
|
||||
manifest. The suites above pass on Sage's gate rerun. The launcher runs
|
||||
in the bus host, so the change is live at the next bus restart, which
|
||||
ends every running session (decision 109 notices to Mos). Until it is
|
||||
live, whether bash-capable sessions run is Jason's call.
|
||||
Reference in New Issue
Block a user