Status: IMPLEMENTED + Linux-verified (2026-07-02). All 4 phases landed and
run green end-to-end on the Ubuntu EC2 (tests/test_posix_isolation.py, 6/6).
macOS remains port-by-symmetry (LOCAL_PEERCRED/getpeereid), code-review-only.
In-process (single-process, all-agents) is still the default everywhere (gated by
ATN_WORKER_ISOLATION, off by default); isolation now works on POSIX when on.
This was the sprint to make isolation + vault OS-agnostic.
- P0: portable
_identity(pid): Linux/proc/<pid>/statstarttime (field 22), WindowsGetProcessTimes, psutil fallback for other POSIX;psutilmoved to core deps.pyragealready core. - P1: POSIX containment. Primary is a direct kill of the tracked worker
PID (
_posix_signal_pid), with a process-group sweep (_posix_signal_group, only when we own the pgid) as the descendant backstop. No cgroups/Job Object on POSIX.setsid-detach residual confirmed + accepted. - P2: POSIX broker IPC.
AF_UNIXtransport inbroker_client.py(client + value-push listener) andvault_broker.py(server), peer PID viaSO_PEERCRED(==GetNamedPipeClientProcessId). Value-push socket uses a short runtime dir (_short_unix_sock_path) to stay under the sun_path limit. - P3: hardening + setup. Broker calls
PR_SET_DUMPABLE(0);atn-vault-setupPOSIX branch tightens keystore 0700 / age-key 0600, emits a systemd unit template +RUNBOOK_POSIX.md(separatevault-svcuid, Yamaptrace_scope). - P4: fail-closed fallback. A granted execution whose worker can't spawn
now finalizes FAILED instead of silently downgrading to un-isolated in-process
(
_has_pending_grant/_fail_closed_no_isolation). Committed POSIX integration tests formalize the EC2 verification.
Already OS-agnostic, no work:
- Daemon↔worker RPC channel (
atn/runtime/worker_rpc.py):multiprocessing.Connection(send_bytes/recv_bytes); a socketpair on POSIX. The isolation control plane already crosses platforms. - Secret store (
atn/_vendor/kevin/keystore.py): age/pyrage+ stdlib file I/O. - PID-reuse defense in the supervisor (
agent_supervisor._probe_identity):psutil.create_time()/cmdline(), cross-platform. (Caveat:psutilships in core.)
Windows-only, needs porting (the two hard parts):
- Kill-tree containment (
worker_manager.py): Win32 Job Objects (kill-on-close tree reap). POSIX today has onlystart_new_session(a grandchild cansetsid-escape). - Broker IPC + PID authentication (
broker_client.py,vault_broker.py): Windows named pipes +GetNamedPipeClientProcessId(kernel-authenticated peer PID). Also_identity(pid)viaGetProcessTimes(create-time reuse guard) and the value-push listener.
- Portable
_identity(pid)in the broker: Linux/proc/<pid>/statstarttime,psutilelsewhere, replacingGetProcessTimes. Keeps the create-time PID-reuse guard. psutil→ core deps (or a/procfallback so isolation works off-Windows without pulling it).
- Approach: track every spawned PID in the supervisor and kill each directly
(
os.kill), not by process group. We already record our own tree (_children); the bridge node-grandchild just needs adding to that tracking. It is portable, needing neither Job Objects nor cgroups. On Windows we keep the Job Object as a belt-and-suspenders backstop, but the per-PID kill is the primary, portable path. - The only thing per-PID kill can't reach is a descendant we never saw (an
agent that shells out and the grandchild double-forks/
setsidto detach before we record its PID). That residual is equal on every OS (Job Objects catch it on Windows; nothing does on plain POSIX) and is accepted: it's inherent to tracking-based containment, not a Linux-specific gap.
- Add an
AF_UNIXtransport alongside the named-pipe one, in bothbroker_client.py(client) andvault_broker.py(server). - Peer PID+uid via
SO_PEERCRED(Linux) /LOCAL_PEERCRED/getpeereid(macOS): the exact kernel-authenticated analog ofGetNamedPipeClientProcessId(and it yields uid too). - Port the value-push listener (
_ValuePushListener) toAF_UNIX. Socket dir owned by the broker uid,0700.
- Broker runs as a separate uid (analog of the Windows
vault-svcaccount); age-key0600owned by that uid so a same-daemon-uid agent can't read it. - ptrace hardening: Yama
ptrace_scope/PR_SET_DUMPABLE(0), the analog of "deny SeDebugPrivilege." atn-vault-setupPOSIX branch: create the broker user, set perms, emit a systemd unit + a POSIX RUNBOOK.
- Make the in-process fallback (used when
ensure_workerfails) fail-closed for a granted execution: today it silently downgrades out of isolation. - POSIX integration tests for tree-kill and PID-auth (both deferred in the Windows build).
Kill by tracked PID, not process group: the supervisor already knows every process it spawned, and killing a known PID is trivial and portable on every OS. So there is no cgroup/systemd dependency and no Windows/Linux/macOS strength split for our own tree. The one residual (a descendant that detaches before we record it) is identical across platforms and accepted. This removes the only architecture decision that was blocking Phase 1.
Scoped on a Windows box; the POSIX paths (AF_UNIX + SO_PEERCRED, tracked-PID
kill) can't run on Windows. Linux verification target: the Ubuntu EC2 (ssh via
ZeroTier; see the ec2_smoke_setup memory). P1/P2/P4 get real end-to-end runs
there. macOS (LOCAL_PEERCRED/getpeereid) stays code-review-only unless a
Mac is available: verify Linux, port-by-symmetry for macOS, flag it as untested.