OverlayNet transparent interception¶
Implemented VM driver¶
libkrun VM Attempts now use vm-smoltcp when [overlaynet].mode = "auto".
The guest virtio-net device connects to pVisor over libkrun's length-prefixed
UnixStream Ethernet transport. pVisor serves DHCP (192.0.2.1 router,
192.0.2.2 guest), synthetic DNS (198.18.0.0/15, stable per Attempt), and
IPv4 TCP. A SYN remains paused in smoltcp until hostname/IP, resolved address
or scoped host connector alias, port, and injected Control policy all authorize
and the host connection succeeds. TSI stays disabled, so there is no guest
path around this data plane.
Some host DNS/TUN connectors return an opaque 198.18.0.0/15 fake IP for an
authorized hostname. The VM connector accepts that result only after the
logical hostname and port pass policy and Control authorization; guest IP
literals in the same range remain blocked. Because the connector hides the
real final address, IP/CIDR policy cannot inspect the endpoint behind that
alias. Deployments that require final-address policy should use a resolver
that exposes concrete addresses.
The MVP intentionally fails closed for general UDP, IPv6, ICMP, QUIC, inbound connections, virtual/link-local/multicast/broadcast destinations, and exhausted flow/DNS capacity. Explicit Gateway capture is an internal virtual-router route; all ordinary egress shares the same policy and bandwidth registry. Host and container transparent interception described below remains future work.
Status: the libkrun VM driver is implemented on Linux and Apple Silicon macOS. The host-process transparent drivers described later in this document remain an accepted design. Host/container selective policy still uses the explicit proxy; host deny-all keeps its existing platform sandbox behavior.
Problem¶
OverlayNet's host/container data plane is an explicit HTTP/HTTPS proxy. pVisor
injects proxy environment variables and, for known Agent CLIs, proxy
configuration arguments. Coverage is therefore opt-in: any child process that
ignores proxy environment variables — a static Go binary, a raw socket, a
subprocess that scrubs its environment — talks to the network directly. This
is why the host ProcessExecutor cannot claim enforcement and refuses
PolicyMode::Enforce for network capabilities.
The goal of this remaining host-driver design is complete interception with a lightweight footprint: every byte the Agent process tree sends must pass through a pVisor-owned choke point, regardless of language runtime, linkage, or syscall discipline — without a VM, a root daemon, or persistent elevated privileges.
The key move is to relocate the interception point from convention (environment variables the child may ignore) to a layer the child cannot choose to bypass.
Design A (primary): unprivileged network namespace + in-process userspace network stack¶
This is the default driver on capable Linux hosts. It mirrors the design of pVisor's filesystem path:
filesystem: pVisor embeds a FUSE server and IS the child's filesystem
network: pVisor embeds a userspace TCP/IP stack and IS the child's network
Mechanism¶
- The Attempt child is spawned with
CLONE_NEWUSER | CLONE_NEWNET. Creating a network namespace inside a fresh user namespace requires no privileges; the namespace owner holdsCAP_NET_ADMINwithin it. - Inside the namespace, setup code creates a
tundevice, assigns a link-local subnet, and installs a default route pointing at it. Loopback is brought up so Run-local services keep working. - The
tunfile descriptor is passed back to the pVisor parent over asocketpairbeforeexec. From that point pVisor owns the only egress path of the entire process tree. - pVisor runs a
smoltcp-based userspace stack on thetunfd. Inbound TCP flows terminate in the stack and are re-originated on the host side after passing thepersisting-agentctlpolicy gate. The existing OverlayNet proxy / Gateway sink remains the LLM capture path, unchanged. - DNS: the stack answers a virtual resolver address advertised via the
namespace's
resolv.conf. Queries are resolved host-side, giving a domain-level policy point before any connection exists.
Properties¶
- Topologically complete. libc interposition, static binaries, raw syscalls, and forked grandchildren are all inside the namespace; there is no second path out. No cooperation from the child is needed or assumed.
- Zero privilege at runtime. No root, no setuid helper, no daemon. The
only host prerequisite is unprivileged user namespaces
(
kernel.unprivileged_userns_clone/ distro equivalent). - In-process. Consistent with the embedded FUSE decision: pVisor does not
spawn
passt/slirp4netns-style helpers.
Policy evaluation points¶
| Layer | Signal | Notes |
|---|---|---|
| DNS | queried name | virtual resolver; cheapest allowlist point |
| L4 | destination IP:port | last resort for literal-IP traffic |
| TLS | SNI from ClientHello | parsed passively, no MITM, no injected CA |
| QUIC | SNI from Initial packet, or blocked | default: refuse UDP/443 to force TCP fallback |
Failure and probing¶
Driver availability is probed at Attempt prepare time. If user namespaces are unavailable, behavior depends on the requested policy mode:
PolicyMode::Observe: fall back to the explicit proxy driver and record the downgrade in the implant plan notes.PolicyMode::Enforce: fail the Run preparation. A downgrade under Enforce must never be silent.
Design B (restricted fallback): seccomp user-notify + socket broker¶
For hosts where unprivileged user namespaces are disabled (hardened distros, some container runtimes), a second driver can enforce a deliberately smaller socket surface without a namespace. It is not considered equivalent to the netns driver unless unsupported channels are denied.
Mechanism¶
- The Attempt child installs a seccomp filter routing
socket,connect,sendto, andsendmsgtoSECCOMP_RET_USER_NOTIF.io_uring_setup, raw packet sockets, namespace changes, and unmediated descriptor passing are denied while this driver claims enforcement. socketis brokered: pVisor creates the socket, retains a duplicate of the same open file description, and injects the child descriptor withSECCOMP_IOCTL_NOTIF_ADDFD.- On
connect, pVisor copies the socket address once, revalidates the notification cookie, evaluates policy, and performsconnectthrough its retained descriptor. It then returns the real result without allowing the child's original pointer-bearing syscall to continue. This avoids a check-then-CONTINUETOCTOU window. - An initial seccomp driver is TCP-only. Unconnected UDP is denied until
OverlayNet can safely copy and broker each datagram. DNS must use a
pVisor-provided resolver path; otherwise a domain allowlist cannot be
reconstructed from the destination IP observed by
connect.
Properties and caveats¶
- Covers static binaries and raw syscalls for the explicitly brokered socket
families.
AF_UNIXgets a separate path policy; it is not blanket-allowed. - No namespace, no tun, no userspace stack — but descriptor provenance,
SCM_RIGHTS, UDP, DNS, andio_uringmust all be closed or mediated before the driver is described as non-bypassable. - Seccomp sees an IP address at
connect, not the hostname the application originally resolved. Domain allowlists require mediated DNS, explicit proxy traffic, or SNI correlation; IP/CIDR policy can be enforced directly. - Chosen per Attempt; Design A remains preferred when both are available.
Enforcement vs capture are separate layers¶
Transparent interception provides enforcement (deny / allowlist) and flow accounting. It deliberately does not decrypt:
- Enforcement needs no MITM CA: mediated DNS names and authorized destination addresses are sufficient for the VM MVP; a future host netns driver may add passive SNI parsing.
- Capture of LLM payloads stays on the existing explicit-proxy path: Gateway injects proxy configuration into known Agent CLIs and sees plaintext. Under a non-bypassable driver, non-cooperating traffic cannot leave the allowlist but is not decrypted.
Known erosion: Encrypted ClientHello will eventually hide SNI. When that matters, deployments choose between an opt-in MITM CA for capture-grade visibility or falling back to DNS/IP-level enforcement. This is an industry constraint, not specific to either driver.
Capability reporting¶
Per-Attempt selection determines whether a non-bypassable driver is attached;
the runtime capability catalog separately advertises VM network support.
Every Run records an
InterceptionProfile describing driver, strength, and protocol coverage:
enforcewhen VM smoltcp, netns, or seccomp is active;observewhen only the explicit proxy is available.
The explicit proxy foundation already emits this profile as cooperative and
publishes intercepted/allowed/denied/CONNECT/HTTP/sink/failure counters. These
counters prove what reached OverlayNet; they do not estimate bypassed traffic.
The honesty invariant is preserved: the host ProcessExecutor still never
claims network enforcement by itself; the claim is made by the active
OverlayNet driver, and PolicyMode::Enforce is satisfiable only while such a
driver is attached.
Configuration¶
The implemented public mode selector has three values:
[overlaynet]
mode = "auto" # auto | off | proxy
policy = "allowlist"
[[overlaynet.rules]]
host = "api.openai.com"
ports = [443]
transports = ["tcp_tunnel"]
For a libkrun VM, auto selects vm-smoltcp; off leaves the VM offline and
proxy is rejected because it is a host/container-only cooperative driver.
For host/container runs, explicit network flags select proxy; the accepted
netns and seccomp host drivers remain future internal candidates rather
than exposed configuration values. run.json records the driver actually
attached so pvisor status reports the real enforcement level.
Non-goals¶
- macOS transparent selective interception (Network Extension, pf-based UID routing). Selective policy remains observe-grade; deny-all is a separate Seatbelt-enforced boundary and is reported as such.
- An eBPF (
cgroup/connect4) driver. Elegant, but requires CAP_BPF/root and a setup-host deployment model; out of scope for now. - TLS decryption by default. MITM stays an explicit opt-in, if ever.
Delivery plan and acceptance gates¶
The VM milestone described at the start of this document is complete. The remaining plan below applies to transparent host/container interception.
- Explicit proxy foundation (implemented): honest cooperative profile, interception counters, strict CONNECT parsing, connect-before-200, streaming forwarding, dynamic hop-header stripping, redirect revalidation, no implicit loopback or Gateway-upstream egress trust, structured host/IP/CIDR + port + transport rules, post-DNS address authorization, and pinned authorized destinations to close policy/connector DNS races.
- Driver probe and selection: extend the implemented public
off | proxy | autoselector with internal netns/seccomp probes; record the selected profile before child exec.Enforcefails closed if no non-bypassable profile is available. - Netns TCP + DNS minimum: spawn plumbing, tun handoff, TCP relay, mediated resolver, DNS/IP allowlist, process-tree and namespace escape tests. Do not claim enforcement until raw syscalls and environment-scrubbed grandchildren are demonstrably contained.
- Protocol closure: SNI policy, literal-IP behavior, UDP policy, blocked
QUIC fallback,
AF_UNIX, raw/netlink sockets,SCM_RIGHTS, namespace changes, andio_uringconformance cases. - Restricted seccomp fallback: broker TCP sockets first; deny uncovered channels. Add UDP/DNS only after descriptor and datagram semantics have dedicated tests.
- Operations: persist final counters and downgrade reasons in
run.json, expose them throughpvisor status, and benchmark proxy/netns/seccomp modes separately on Python, Node, Rust, static Go, and forked grandchildren.