Firecracker and Cloud Hypervisor are the strongest isolation primitives most teams will ever deploy. They are not sandboxes. A sandbox is a property: the worst-case outcome of running adversarial code is bounded, predictable, and contained. A microVM is a wall, and a necessary one, but it closes two of the three dimensions that make a sandbox hold. The third dimension has three gaps of its own, and they require separate controls.

Scope

This is a defensive read aimed at teams running untrusted workloads, including LLM agent tasks, in microVM-based environments. The escapes described are well-documented; the goal is naming which isolation dimension each one lives in and what the correct control is. No weaponized payload here.

Three dimensions, not one

Isolation is not a single property. It spans three independent dimensions, and a microVM scores differently across each of them.

DimensionWhat a microVM gives youResidual gap
ProcessA separate guest kernel and process tree. No shared PID or mount namespace with the host. The host kernel's syscall surface is removed from the guest entirely.Negligible at the host boundary. The guest kernel's own syscall surface is a separate question.
FilesystemA separate rootfs on a separate virtual block device. The host filesystem is never mounted into the guest by default.Negligible at the host boundary. What is mounted inside the guest, and whether it is read-only, is a configuration question.
NetworkA virtual NIC with a route to the host, because the workload has to fetch dependencies and do useful work.Wide open by design. The guest can address any host service it can route to.

The first two are why people reach for microVMs, and they earn the reputation. The third dimension travels with them silently. A guest that cannot touch the host's memory or disk can still talk to everything the host is listening on. On a build host or an agent host, something important is almost always listening.

Inside the process dimension: the guest's own kernel

The microVM puts a hardware boundary between the guest and the host kernel. It does not reduce the guest kernel's own attack surface. An agent process running as root inside the guest, against an unfiltered syscall interface, has the full Linux system call table available to it. Most of those calls are harmless for a typical workload. A handful are not, and a compromised or adversarial agent can use them to escalate from agent process to guest root, which matters when the agent is not supposed to have persistent write access or the ability to reconfigure the network stack.

Two controls address this, used together:

seccomp-bpf. A BPF filter attached to the agent process that intercepts every system call before the kernel handles it. Calls on the allowlist proceed; everything else returns EPERM or kills the process. A minimal allowlist for a language runtime running a task is roughly 50 to 70 syscalls. ptrace, process_vm_writev, kexec_load, mount, and clone with certain flags have no place on it. This is the same filter Chromium's renderer and most container runtimes apply; it is not experimental infrastructure.

Linux capabilities. The kernel's 40-odd capability bits decompose root privilege into distinct authorities: CAP_NET_ADMIN to reconfigure the network, CAP_SYS_ADMIN to mount filesystems, CAP_DAC_OVERRIDE to bypass file permission checks. A process started with a full capability set and then dropped to the specific bits it needs is substantially harder to pivot from than one that retains them all. Most agent workloads need zero capabilities. Drop the full set at startup and add back only what the task explicitly requires.

Landlock. A Linux Security Module added in kernel 5.13 that lets an unprivileged process impose and permanently lock a filesystem access whitelist on itself. Three syscalls (landlock_create_ruleset(), landlock_add_rule(), landlock_restrict_self()) and the policy is irrevocable. Unlike DAC (file permissions) or seccomp (syscall numbers), Landlock restricts which paths the process can reach at all, regardless of permission bits or user identity. Applied before execve, the policy survives the exec boundary: the agent binary is born inside the whitelist with no escape path, including from code injected at runtime. The microVM already removed the host filesystem from the guest's view; Landlock constrains what the agent can access within the guest filesystem itself.

Why this matters specifically for LLM agents

A traditional CI task has a fixed, known instruction set. An LLM agent generates its own instructions at runtime in response to a prompt. An adversarial prompt can instruct the agent to attempt privilege escalation within the guest, scan the virtual network, or write to paths outside the task's intended scope. seccomp and capability stripping close those paths at the kernel level before the agent's intent matters.

Inside the network dimension: what the host is listening on

The virtual NIC the guest gets is the mechanism; the attack surface is everything on the host that the guest can address. Enumerate the open ports on the host from the guest's routing perspective and you have the sandbox's residual network attack surface. Most are harmless. One, on a typical build or agent host, is not.

Build hosts frequently expose the Docker daemon over TCP so that remote jobs and docker-in-docker workflows can drive it: plaintext on port 2375, TLS on 2376. An unauthenticated Docker daemon accessible from the guest is not a foothold. It is root on the host.

Why unauthenticated Docker is root-equivalent

The Docker API will create a privileged container that mounts the host's root filesystem. From inside that container the caller reads and writes the host as root, reads /etc/shadow, writes to /etc/cron.d, injects into the host's running processes via /proc. Docker is designed for trusted local administrators talking to a unix socket. Put a plaintext TCP listener on it and route a guest to it and you have handed root to whatever can reach that port. The microVM blocked the direct process and filesystem paths cleanly. The guest routed around both walls in one API call.

The iximiuz sandbox breakout challenge makes this concrete: the guest kernel boundary is intact throughout; the exit is the listener the host left open for an unrelated purpose.

Closing the inbound path to the host

Shutting that door takes a layered answer, not a single control:

ControlWhat it doesTradeoff
Unix socket only for DockerDrop the TCP listener. The daemon answers only to the local unix socket, reachable by trusted uid/gid on the host, not over any routed path.Best default. Clients that assumed TCP must move to the socket or an SSH-tunneled socket.
Loopback bindIf TCP is required, bind 127.0.0.1 instead of 0.0.0.0, so the guest's bridge route does not reach it.Weaker than the socket option; only safe if the guest provably has no path to host loopback.
Mutual TLSRequire client certificate authentication so reaching the port is not sufficient.Correct. Adds a CA, cert rotation, and client configuration overhead.
Packet filterAn nftables or iptables rule that DROPs traffic from the sandbox interface or subnet to the daemon port, while the trusted CI source address is still allowed.Surgical and host-transparent. Get the interface match wrong and you lock out CI itself.
Network namespace segmentationPut the guest on an isolated namespace with no route to host management interfaces at all. Egress only to an explicit allowlist.Strongest and most work. Defends every host listener at once, not just Docker.

The packet filter row handles the immediate brief: the CI host keeps building because the trusted path is still allowed, while the sandbox subnet is dropped before it reaches the daemon. Put the unix socket default under it and you have belt and suspenders. Network namespace segmentation is the right long-term answer for any environment where the threat model extends beyond Docker to internal network access generally.

Inside the network dimension: egress policy

Closing the path inward from the guest to the host is half the problem. The path outward matters equally. A guest that can make arbitrary outbound connections, to the public internet, to internal services, or to other guests on the same host network, has not been confined. It has been given a clean environment to exfiltrate from or to pivot through.

Egress policy should be a strict allowlist, not a denylist. The default posture is DROP; traffic is permitted only to endpoints the workload explicitly requires. For an agent task that needs to call an API and nothing else, the allowlist is one entry. For a build that needs package repositories, it is the package registry hostnames. Everything else, including the metadata endpoints, the instance identity services, and the adjacent guest addresses, goes nowhere.

This is not an exotic requirement. It is the posture every cloud security team recommends for production workloads. The gap is that sandbox products often do not implement it because the workloads they are designed for, CI builds and development environments, have legitimately broad network needs, and the friction of an allowlist is real. For agent workloads where the task scope is narrow and the instruction source is partially untrusted, the friction is worth it.

The complete picture

A sandbox is not a microVM. A sandbox is the assembled result of controls across all three isolation dimensions, each one closing what the others leave open.

What can go wrongDimensionControl
Agent process exploits guest kernel syscall surfaceProcess (guest-internal)seccomp-bpf profile on the agent process
Agent process retains elevated privilege inside guestProcess (guest-internal)Linux capability stripping at process start
Guest exploits host kernel via hypervisorProcess (host boundary)microVM (hardware isolation)
Guest reads persistent secrets from guest filesystemFilesystemRead-only rootfs; per-task secret injection, not at-rest storage
Agent writes persist beyond task scopeFilesystemtmpfs scratch; no writable path that survives teardown except approved output
Guest reaches host services (Docker, metadata endpoints)Network (inbound to host)unix socket default; packet filter on daemon ports; mTLS where TCP is required
Guest exfiltrates data or reaches internal services outboundNetwork (egress)Strict outbound allowlist; logged proxied egress for any permitted destination

A microVM contributes one row to this table. It is the most important row, and without it the table has a hole nothing else can fill. With it, but without the others, you have strong kernel isolation and an open path to everything the host owns.

A structural alternative: interpose the kernel

The controls above layer policy on top of a standard Linux kernel inside the guest. gVisor takes a different structural position: a user-space kernel (the sentry, written in Go) interposes between guest processes and the host kernel. Guest syscalls never reach the host kernel at all. They are caught by the sentry, which reimplements the Linux syscall interface. A process calling uname() inside a gVisor container gets back 4.19.0-gvisor. The /proc tree it sees is synthesised by Go code.

The implication for the process dimension gap is structural rather than additive. With seccomp-bpf, the host kernel's syscall surface is filtered; a zero-day in an unfiltered path can still reach the host kernel directly. With gVisor, that path does not exist. An exploit that escapes the guest sentry still needs to escape the sentry process on the host before reaching the host kernel, and the sentry runs with a stripped capability set. The host kernel never saw the guest syscall.

gVisor runs in ptrace mode (no root, works inside VMs) or KVM mode (lower overhead, requires /dev/kvm). Google Cloud Run and Fly.io run production workloads on it. The overhead is real: all guest syscalls route through the sentry rather than the kernel path. For environments where the guest kernel surface is the primary concern, it is the stronger structural answer. For most deployments, the seccomp + capabilities + Landlock combination is sufficient and cheaper. The full isolation stack demo covers both with runnable code.

The microVM is the hardest wall to build. The walls it does not build are the ones that get used.

A microVM (Firecracker, Cloud Hypervisor) is the strongest isolation most teams will deploy, and still not a sandbox. A sandbox is a property: the worst case of running hostile code is bounded and contained. Isolation spans three independent dimensions, and a microVM closes only two cleanly.

Process and filesystem are sealed: the guest gets its own kernel and rootfs on a separate block device, so the host's syscall surface and filesystem leave its view. Network is the gap: the guest's NIC routes to the host, so it can address anything the host is listening on.

A microVM blocks the direct process and filesystem paths. The guest routes around both walls over the network in one API call.

The guest's own kernel is still a target

The hardware boundary sits between guest and host kernel; it does nothing for the guest kernel's own surface. An agent as root against an unfiltered syscall table can escalate to guest root. This bites LLM agents hardest: they write instructions at runtime, and a hostile prompt can request exactly that. Three controls, together:

  • seccomp-bpf: a syscall allowlist on the agent process (50 to 70 calls for a runtime); ptrace, mount and kexec_load are off it.
  • Linux capabilities: drop the full set at startup, add back only what the task needs. Most agents need none.
  • Landlock: an irrevocable self-imposed filesystem whitelist (kernel 5.13+) surviving execve, so injected code inherits the cage.

Where that surface is the primary threat, gVisor's user-space kernel catches guest syscalls before they reach the host kernel, at real cost; otherwise the three controls suffice.

What the host is listening on

The dangerous network gap is inbound. On a build host the standout is the Docker daemon, often on plaintext TCP (2375) for docker-in-docker. An unauthenticated Docker API is not a foothold, it is root: it starts a privileged container mounting the host rootfs, reading /etc/shadow as host root.

Close it without breaking the daemon: a unix-socket-only default, an nftables rule dropping the sandbox subnet while trusted CI still passes, or an isolated network namespace with no route to host management.

Egress is the other half

Closing the inbound path is half the job. A guest that can dial out anywhere is not confined, just handed a clean room to exfiltrate or pivot through. Egress should be a strict allowlist, default DROP, permitting only the endpoints the task names; metadata endpoints and neighbour guests go nowhere.

A sandbox is the assembled result of controls across all three dimensions: the microVM is the most important row, but alone it leaves an open path to everything the host owns. The full isolation stack demo has both in runnable code.