Tool sandbox (HARNESS-02)

rollout-harness-tool exposes six tools — python_exec, shell, file_read, file_write (Linux-enforced) and http_get, http_post (platform-independent, SSRF-filtered) — behind a best-effort layered Linux sandbox.

The crate README.md is the single source of truth for the matrix below; this chapter summarizes it.

Threat-model boundary (D-TOOL-08)

Tool harnesses defend against accidental damage; they are NOT a security perimeter for actively malicious code.

The sandbox is process-isolated, NOT VM-isolated. It is a real boundary against accidental escape and run-away resource use — a buggy tool, a runaway script, a path-traversal mistake, an SSRF to cloud metadata. It is not a production-grade defense against an adversary actively trying to break out. gVisor / Firecracker microVM isolation is a v1.2+ capability, explicitly out of scope. To run actively-hostile code, run this harness inside a microVM or a throwaway host.

Sandbox-depth matrix

LayerMechanismLinuxmacOSDefeats
Namespacesrustix/unshare user+pid+net+mount+uts+ipcyes (best-effort)stubambient process/network visibility; net ns = default-deny egress for exec tools
Resource limitsrustix::process::setrlimit (CPU/AS/NOFILE/NPROC)yes (always)stubCPU spin, memory blow-up, fd/pid exhaustion
cgroups v2memory.max + pids.max in a delegated subtreeyes, degrade-with-warningstubmemory/pid exhaustion (defense-in-depth over rlimits)
Filesystemlandlock kernel FS allowlistyes, fail-closed on kernel ≥ 5.13stubreads/writes outside the per-invocation tempdir
Syscallsseccompiler deny-default-EPERM curated allowlist, installed LASTyes (always)stubmount/keyctl/bpf/ptrace/socket + clone*/unshare with CLONE_NEWUSER
File-tool FS rootcap-std::fs::Dir rooted at the tempdiryesyespath traversal / TOCTOU for file_read/file_write
HTTP egressSSRF-filtered hyper connector (post-DNS IP filter + IP pin + per-redirect re-filter)yesyesSSRF / DNS-rebinding / redirect-to-IMDS / RFC1918 / loopback

Layer order (exec tools)

After unshare, the child applies, in order: setrlimit → landlock → seccomp LASTexecve. seccomp is last because its own setup syscalls (landlock_*, prctl, setrlimit) must be permitted; landlock precedes seccomp because landlock enforcement needs PR_SET_NO_NEW_PRIVS + landlock_restrict_self.

The curated seccomp allowlist is deny-by-default with action ERRNO(EPERM) (not KILL) so a blocked tool returns a typed error and the harness can emit a seccomp-violation event rather than segfaulting. The allowlist is post-2020 syscall-aware (clone3, openat2, faccessat2, rseq, arch_prctl, the rt_sig* family) — validated on the Linux CI lane against a real strace -c baseline plus the seccomp_python_runs positive witness.

Fail-closed kernel gate (D-TOOL-02)

require_landlock = true by default. On kernel < 5.13 the harness refuses to start: Fatal::ConfigInvalid("landlock requires kernel >= 5.13, found {n}"). RHEL 8 (4.18) / Amazon Linux 2 (5.10) must set require_landlock = false to run with reduced isolation (rlimits + seccomp + namespaces still apply). No silent fallback.

macOS dev stub (D-TOOL-05)

On macOS the exec/file sandbox compiles to a stub; a sandboxed invoke returns Fatal::ConfigInvalid("sandbox unavailable on macOS — dev stub"). Linux is the only enforced surface. The HTTP tools DO run on macOS — their SSRF defense is an in-process hyper connector, not a syscall sandbox, so the http_tool_blocks_* witnesses run on both lanes.

HTTP SSRF connector

http_get / http_post run in-process with a custom hyper connector (not reqwest, whose redirect-follow gives no per-hop injection point). The connector resolves DNS itself, rejects private/link-local/loopback/IMDS IPs, pins the chosen IP for the connection (defeating DNS rebinding), and re-applies the filter on every redirect (defeating redirect-to-IMDS). Exec tools run in a deny-all net namespace with socket blocked — the network surface is deliberately separate.