Decision 0005 — Job sandboxing: the `enforcing` tier via nono
Job sandboxing via nono's enforcing tier, so a job is denied every powerful local socket.
Status
Accepted · 2026-08-21
Context
Jobs execute through an ExecutorDriver interface with two tiers designed
(execution):
disabled— pinned tools in the image, subprocess boundary, per-job tmpfs, separate executor UID. The only topology bootstrap and DR require.container— per-job sibling containers, deferred post-v1.
The gap between them is real. Under disabled, a playbook — arbitrary code from a git repo —
shares a namespace with the mlocked DEK, the per-job ssh-agent, and the API socket. The
container tier closes that, but it costs exocompd a mounted podman socket (effectively
host-user power: the sensitive capability moves rather than disappears) and it puts
registry-pulled execution-environment images on the restore path.
Decision
Adopt nono (Apache-2.0, Rust; Landlock on Linux, Seatbelt on macOS, seccomp user-notification network filtering, unprivileged self-restriction — so it works under rootless podman) as a middle tier.
enforcing is the default executor on Linux, secure-by-default: where the kernel
cannot enforce it, serve and doctor fail closed rather than run the fleet's jobs
unsandboxed (the opportunistic tier is the opt-in that degrades loudly instead).
Every job subprocess runs under nono run --profile job-<kind> with default-deny
filesystem:
| Paths | |
|---|---|
| Allowed | the job tmpfs, the repo checkout, the artifact dir, tool paths, provider and collection caches |
| Denied | exocomp.db, host.key, the keyring, other jobs' tmpfs, the exocomp API socket, and any podman socket |
Network is scoped per runbook kind: Ansible gets SSH to inventory hosts; OpenTofu gets
provider endpoints plus the 127.0.0.1 state backend.
Version floor ≥ 0.55.0, sha256-pinned like restic.
Consequences
-
Most of the
containerdriver's privilege-separation prize is captured — arbitrary playbook code can no longer read the database or the keys — with no podman socket handed toexocompdand no registry on the DR path. Landlock is local, so sandboxing applies even during a restore. -
The
containerdriver's remaining justification narrows to per-project execution environment versioning and hard per-job resource caps. It stays deferred. -
The CVE lesson is the load-bearing part. nono's CVE-2026-47128 was an escape via the user systemd D-Bus socket, fixed in 0.55.0. The escape class generalizes to "a sandboxed process reaches a powerful local unix socket." Exocomp ships such sockets itself, so job profiles grant no socket, and
exocomp doctorasserts the denial rather than assuming it — it attempts socket connects from inside a probe sandbox and expects failure. container-selinux's socket confinement is treated as a second, independent enforcement of the same rule.The assertion found that half of it cannot hold. Landlock mediates path opening, and
connect(2)on a pathnameAF_UNIXsocket opens nothing, so no Landlock ABI shipped so far refuses one; abstract sockets, which Landlock scopes, are refused. The denial for pathname sockets is therefore enforced by placement, not policy: exocomp ships no unix socket for a job to name, its own surface is loopback TCP, and no podman socket is mounted into the container.doctorprints the gap rather than implying a denial it cannot make. Recorded in log.md; the requirement to assert instead of assume is what surfaced it, and stands. -
doctorprobes the Landlock ABI (network rules need ABI v4+, kernel ≥ 6.7); under theopportunistictier it degrades loudly to plaindisabledwith a warning on older kernels — never silently — while the defaultenforcingtier fails closed instead. -
Landlock stacks with SELinux (stackable LSM; policies AND together). This is asserted in the enforcing CI job, not assumed.
-
Fallback seam if the dependency sours: in-process
go-landlockself-restriction applied between fork and exec — no new binary, filesystem-only, weaker network story, same profiles. -
nono's agent-oriented features (AGENTS.md signing, snapshot rollback, credential injection) are unused.
-
Cost: profiles plus doctor probes ≈ 0.5 FTE-week inside the v1.0 hardening line; the bill of materials gains one pinned ~10 MB binary.