Decision 0011
bubblewrap/Landlock/seccomp, microVMs, and a kernel-enforced egress proxy
Architecture decision record 0011: bubblewrap/Landlock/seccomp, microVMs, and a kernel-enforced egress proxy
Context
Tools spawn processes and parse hostile content. Network policy enforced inside an application is not enforcement.
The three plans
- Grok: bubblewrap or Landlock + seccomp for tool processes; net kill via rfkill + nftables with a badge.
- Fable: systemd hardening plus bubblewrap; a per-domain egress allowlist in policy.
- Codex: a sandbox executor zone; microVMs for risky code; prompt-injection boundaries.
Decision
Spawned tools run under bubblewrap + Landlock + seccomp with scoped mounts, limits and no network
unless policy grants it. Every daemon carries systemd hardening. High-risk code runs in a microVM
(microvm.nix). Parsers of untrusted content (PDF, archives, images, web) run in session
sandboxes, never in tacidd. Network policy is enforced in the kernel rather than in the
process being confined, and tacid-egress enforces the allowlist, injects credentials, writes the
ledger and implements one-command airgap with a badge (Fable’s allowlist made real, after review).
The kernel enforcement as built is not the nftables rules this record describes, and is
stronger than them. RestrictAddressFamilies = [ "AF_UNIX" ] on every Tacid unit but the proxy is a
seccomp filter on socket(2): an agent process does not get an IP socket it may not use, it does
not get the socket. IPAddressDeny=any — a cgroup eBPF program — stays beside it as a second
mechanism.
The skuid rules were never built, and building them turned out to be the wrong thing:
tacid-egress is the only crate in this tree that opens a TCP connection, so what was described as
narrowing the daemon’s route to the network was really the discovery that it needs no route at all.
IPAddressAllow=localhost was the hole — every local port, and so any other listener as a relay —
and it is closed by removal rather than by a filter. The stem’s nftables.profile operation went
with it. See
ADR-0084, which also records why a rule
matching a session by cgroup could not have been checked before it shipped.
Consequences
- One more process; one fewer lie.
- Cloud API keys are never in
tacidd’s memory. - Airgap is a reflex, not a setting.
Alternatives rejected
- Allowlist enforced by the HTTP client: bypassed by any tool with a socket.
- Containers as the sandbox: heavier, and not a security boundary without the same primitives.