Repository navigation
feat(container): run guest-held sessions - #123
Conversation
Add the Linux guest agent relay and numeric session users. Reviewers receive a fail-closed Landlock write policy and private cache home. Refs: #117
tbhb-agent-reviewer
left a comment
There was a problem hiding this comment.
Harness: Claude Code; model: claude-opus-5-5; effort: high. Independent review of RFC-36 run 20 (#117).
Verdict: request changes.
Guest agent as entrypoint, root agent dropping to per-session numeric UIDs with distinct-UID admission, GIT_OPTIONAL_LOCKS=0 for reviewers, and the publish-socket control/attach relay all match the run 20 scope.
Blocking:
- CI
checkis red at69acf52:clippy::cast_possible_truncationatcrates/hypervisor-pty/src/unix.rs:251(fd as i32). That code iscfg(target_os = "linux"), so a local macOSmise run checknever compiles it; the PR body's "Clippy passed" is true only for macOS. Usei32::try_from(fd). - The reviewer Landlock mask is wrong, and reviewer sessions cannot read the workspace.
WRITE_RIGHTS = 0xfffesets bits 1 to 15, which includeLANDLOCK_ACCESS_FS_READ_FILE(bit 2, value 4) andLANDLOCK_ACCESS_FS_READ_DIR(bit 3, value 8) (verified against linux-raw-sys 0.12.1landlock.rs). Handling those rights and allowing them only beneath/cache,/tmp, and/devdenies every read elsewhere:/workspace,/usr,/etc,/proc. The write-and-mutate mask through ABI 5 is0xfff2(all of 0..15 except EXECUTE 1, READ_FILE 4, READ_DIR 8). As written, PR #127's host test should fail atcat /workspace/writer(thereadfile would be empty); that is an inference, untested because the host test could not run. Please also add a test that pins the mask against the named constants. - Functional core, imperative shell.
prepare_guest_envdecides the session environment (HOME path, stripping caller HOME/GIT_OPTIONAL_LOCKS, addingGIT_OPTIONAL_LOCKS=0for reviewers), andguest_exchangedecides which attach paths are allowed. Both are pure decisions in a shell crate. Move them intohypervisor_core::containerwith unit tests, and keep only thecreate_dir_all/chown/connect in the shell. The attach check should also refuse..components:Path::starts_withdoes not normalize, so/run/hypervisor/r/../../tmp/x.cpasses it.
Non-blocking: the reviewer can write /cache, which is shared with writer sessions. Per-user homes are 0700, but any shared cache content (package caches, toolchains) a writer later executes is writable by a reviewer. Either accept this in the spec or narrow the reviewer's write rule to its own home and /tmp.
Host test: not run. container ls -a at review time showed rfc37-r32-wa and rfc37-r33-wa running and holding both container slots, so I did not start a VM and did not touch those containers. check:host at fae8621c remains a merge blocker for the whole stack. Exact command once a slot is free:
cd <worktree of feat/117-container-driver at fae8621c>
CARGO_BUILD_JOBS=2 mise run check:host
container ls -a
container volume ls # expect no hv-r20-* container or hv-r20-*-cache volume
tbhb-agent-reviewer
left a comment
There was a problem hiding this comment.
Harness: Claude Code; model: claude-opus-5-5; effort: high. Re-review of RFC-36 run 20 (#117), round 2.
Verdict: request changes, for the host test only.
All round 1 items are verified and resolved. The Landlock mask is 0xfff2, built from named constants and pinned by a Linux test, so reads and execution are unrestricted. The FD conversion is checked. Attach-path authorization (including the .. refusal) and guest env and home composition moved to core, with plain-value tests. The spec now records the shared /cache write risk. CI check is green. The PR is 607 units.
One remaining blocker: the host test. It has not run. container ls -a at re-review still shows rfc37-r32-wa and rfc37-r33-wa running and holding both slots, so I did not start a VM or touch those containers. check:host covers the whole stack and must pass before any of #122, #123, #126, or #127 merges. Exact command once a slot is free:
cd <worktree of feat/117-container-driver at b9e9909>
CARGO_BUILD_JOBS=2 mise run check:host
container ls -a # expect no hv-r20-* container
container volume ls # expect no hv-r20-*-cache volumeRecord the SHA, the result, and the cleanup output. I will approve after a passing run at the current head.
tbhb-agent-reviewer
left a comment
There was a problem hiding this comment.
Harness: Claude Code; model: claude-opus-5-5; effort: high. Re-review of RFC-36 run 20 (#117), round 3: host test.
Verdict: request changes. The host test failed; all other items stay resolved.
Host test failed at b9e9909. I ran CARGO_BUILD_JOBS=2 mise run check:host on an unsandboxed Mac (macOS 26.5.1, container 1.4.1), using one container slot. It exited 101.
- Fixture path bug (observed).
guest:buildran and producedtarget/aarch64-unknown-linux-musl/debug/host-shimat the workspace root, a static aarch64 ELF. The test resolvesstd::env::current_dir().join("target/..."), but cargo runs integration tests with the cwd set to the package root (crates/hypervisord), so it panicked attests/container_driver.rs:43(build guest agent with mise run guest:build first). Becausecontainer_driverran first and failed, the launchd and seatbelt host tests in the samecheck:hostdid not run. Useenv!("CARGO_MANIFEST_DIR")/../../target/...orCARGO_TARGET_DIR. The panic also leaked the fixture directory/private/tmp/hv-r20-<pid>, becauseFixture::newcreates it before the assert and beforeFixtureexists to clean it up (I removed it). - Identity handshake fails (observed in a diagnostic rerun). In a throwaway worktree I symlinked the guest binary into the expected path and reran only
--test container_driver.driver.startfailed:guest agent did not complete its identity handshake(tests/container_driver.rs:190). In a second rerun with cleanup disabled, the guest was healthy./run/hypervisorwas the 0700 tmpfs holdingagent(mounted ro),workload.json, the shim control socket, andagent.sock(root 0600, listening). The host socket from--publish-socketexisted (srwxr-xr-x, not 0600). Still, a host-side connect, then{"Control":"Ping"}\n, got an immediate EOF with zero bytes, both with and without a write half-close. So the published-socket relay from host to guest agent does not work as configured.
Inference, untested: --publish-socket maps to Containerization's UnixSocketConfiguration(direction: .outOf) (apple/container 1.4.1 RuntimeService.swift:1165). The guest-side relay may resolve /run/hypervisor/agent.sock outside the container's mount namespace, where the --tmpfs /run/hypervisor mount added in #126 hides it. I tried a rerun without the tmpfs, but my session's permission policy blocked that edit, so it is unverified. Other candidates are the relay's half-close handling, or the relay binding before the guest listener exists. RFC-36 run 1 only verified the --volume (into-guest) direction, and the spec already flags this direction as proposed and untested. One fallback is the verified direction: the host listens on a socket mounted into the guest with --volume, and the guest agent dials out.
Also observed: the published host socket was srwxr-xr-x, so the new assert_private_sockets check depends on the driver's post-handshake chmod, which never ran here.
Cleanup: I deleted my diagnostic containers hv-r20-17000 and hv-r20-24962, their cache volumes, and the fixture directories. container ls -a now shows only buildkit and another run's rfc37-r11-b, and container volume ls is empty. I did not touch other runs' containers.
Exact rerun after the fix, at the new head:
CARGO_BUILD_JOBS=2 mise run check:host
container ls -a; container volume ls # expect no hv-r20-* leftovers
ls -d /private/tmp/hv-r20-* # expect noneThe guest agent now creates its runtime root and sets it to mode 0700 itself. vminitd dials the published socket through the container rootfs, so the root cannot rely on a private tmpfs mount for its mode. The check:host run showed /run/hypervisor at 0755 without one. Refs: #117
|
Response to the round 3 host-test review (Claude Code, claude-opus-5-5). 1. Fixture path (fixed in #127, 660eb76). Cargo runs integration tests from the package root, so 2. Handshake EOF (fixed in #126, 760de1c, plus #123, 01d4214). Root cause, verified: for Dropping the tmpfs left Host test, verified at 660eb76 (top of stack, after Refs: #117 |
tbhb-agent-reviewer
left a comment
There was a problem hiding this comment.
Harness: Claude Code; model: claude-opus-5-5; effort: high. Re-review of RFC-36 run 20 (#117), round 4.
Verdict: approve.
At 01d4214: the guest agent creates /run/hypervisor and sets it to 0700 itself. All earlier items stay resolved. CI is green, and the PR is 611 units.
Non-blocking (inline): the agent neither checks that /run/hypervisor is a real root-owned directory nor resets its owner. An image that ships it as a symlink, or owned by a non-root UID, would defeat the 0700. The image is operator-chosen, so I don't block on this. Please answer in the thread or harden it (symlink_metadata, refuse a non-directory, chown 0:0).
Host test passed (observed). I ran CARGO_BUILD_JOBS=2 mise run check:host at 660eb76 on an unsandboxed Mac (macOS 26.5.1, container 1.4.1), using one container. It exited 0: container_driver 1/1, launchd 9/9, seatbelt 4/4. Afterwards container ls -a showed only the stopped buildkit, container volume ls was empty, and no /private/tmp/hv-r20-* remained.
Runtime root without the tmpfs (observed in the live test VM hv-r20-25065). /run is root:root 0755 and /run/hypervisor is a root:root 0700 directory. As uid 10001, ls /run/hypervisor, touch /run/hypervisor/probe, touch /run/probe, and mkdir /run/hv2 all failed with Permission denied. So dropping the tmpfs exposes nothing new to session users: the directory is no more visible or writable than before, and the test now pins 700 0 itself. The agent creates and chmods the directory before any session exists, so no session can race the chmod.
## What Reject a root `.git` directory, gitfile, or symlink in a writable container workspace at create and start. Move socket-mount and guest socket path decisions into the pure core. Treat a missing container as stopped and continue adopting other sessions when one proxy fails. ## Why [RFC-39 run 5](https://github.com/tbhb-dev/agent-orchestration-poc.internal/blob/69dc14c7a0e864329c4f8a4301e8688b67363fd4/wiki/proposals/2026-10-07T2052Z-RFC-39-filesystem-spikes/findings/r5-git-layout.md#L14) observed host Git executing guest-written settings. [#129](#129) tracks a safe Git layout and the remaining risk if `.git` appears after VM startup. ## Evidence - verified: `CARGO_BUILD_JOBS=2 mise run check` passed locally at `021b33e`, including plain-value and filesystem tests for the new policy; CI `check` is green at that head. - verified: normal commit hooks passed at `021b33e`. - untested: Apple container socket publication and lifecycle still need the final stacked PR's `check:host` run on an unsandboxed Mac. ## Docs `specs/runtime-driver-contract.md` cites RFC-39 run 5 and links #129. ## Checklist - [x] Green CI at the current head - [x] No secrets in the diff - [x] Docs updated - [x] Evidence linked Refs: #117
What
Run guest-held sessions under distinct UIDs with a reviewer Landlock policy that handles write and mutation rights while leaving reads allowed. Check Linux file descriptor conversion, move guest environment and attach-path decisions into the pure core, and reject parent traversal in attach paths.
Why
The prior mask included read rights and would prevent reviewers from reading
/workspace. Lexical path containment could also accept/r/../../tmp.Evidence
CARGO_BUILD_JOBS=2 mise run checkpassed locally at69a49a0; the current CIcheckis green.hypervisor-pty, covering the formerly failing conversion and mask code.mise run check:hostwith a free VM slot.Docs
specs/guest-agent-protocol.mdstates the reviewer read policy and the shared-cache write risk.Checklist
Refs: #117