A microVM is the strongest sandbox primitive most teams will ever deploy, and on its own it is still not a sandbox. It is one wall of three. A hardware-virtualized guest gives you a separate kernel and a separate disk, which closes process and filesystem isolation almost completely. Then it hands the guest a network interface, a route to the host, and a quiet assumption that the first two walls were the whole building. The third wall, network isolation, is where untrusted workloads walk out. The canonical exit is a door the host left open on purpose: the local Docker daemon.

Scope

This is a defensive read of a well-known sandbox-escape path on CI hosts. The escape itself is old news; the point is naming the dimension of isolation that microVMs do not cover, and fixing it without breaking the host that has to keep doing real work. No weaponized payload here.

Isolation has three dimensions

"Sandboxed" is not one property. It is three, and a microVM scores very differently across them.

DimensionWhat a microVM gives youThe gap
ProcessA separate guest kernel and process tree. No shared PID namespace, no syscall surface into the host kernel.Essentially none worth worrying about.
FilesystemA separate rootfs on a separate block device. The host filesystem is never mounted into the guest.Essentially none worth worrying about.
NetworkA virtual NIC with a route to the host, because the workload has to fetch dependencies and actually do its job.Wide open. The guest can reach any host service listening on an address it can route to.

The first two are why people reach for microVMs in the first place, and they earn the reputation. The third is the one that quietly travels with them and gets assumed away.

microVM guest untrusted workload isolation walls process ✓ filesystem ✓ network: open host dockerd :2375 API = root two walls hold; the workload routes around them
The microVM blocks the process and filesystem paths cleanly. The network path is left open by design, and on a CI host it usually leads to a service that is root-equivalent if you can talk to it.

The sandbox can still phone the host

The guest is isolated from the host's memory and disk, and it still shares a network path with it. Traffic from the guest reaches the host across a tap or bridge interface, which is the whole reason the workload can install packages and clone repos. Anything the host binds to 0.0.0.0, or to the bridge address the guest can route to, is in reach of the untrusted code.

So the question that decides whether the sandbox actually holds is not "is the VM isolated." It is:

What is listening on the host, and which of those listeners should never hear a word from an untrusted guest?

Enumerate the open ports on the host and you are really enumerating the attack surface the sandbox did not remove. Most are harmless. One, on a typical build host, is not.

The one that matters: the Docker daemon

CI hosts often expose the Docker daemon over TCP so that remote builds and docker-in-docker workflows can drive it: plaintext on 2375, TLS on 2376. If that listener is reachable from the sandbox and not authenticated, the sandbox has just found the most dangerous door on the box.

Why this is the whole game

Reaching an unauthenticated Docker daemon is not a foothold. It is root on the host. The API will cheerfully create a privileged container that mounts the host's root filesystem, and from inside that container you are reading and writing the host as root. Docker is a root-owned service that trusts anyone who can talk to its socket. Authentication is the only gate, and a plaintext TCP listener has none.

This is not a Docker bug. It is Docker working as designed, exposed in a place it should not be. The daemon was built to be driven by a trusted local administrator over a unix socket. Put a TCP listener on it, route an untrusted guest to that listener, and you have handed the administrator's keys to the workload.

Protect the daemon without killing it

The naive fix is to stop the daemon. That fails the actual requirement: the CI host still has to build other projects, so Docker has to keep running. The fix is therefore at the reachability layer, not the service. You make the daemon answer only to the trusted path and go silent to the sandbox.

ApproachWhat it doesThe catch
Unix socket onlyDrop the TCP listener entirely. The daemon listens on /var/run/docker.sock, reachable by local uid/gid, not over any network.The best default. Any client that relied on TCP must move to the socket or an SSH-tunneled socket.
Bind to loopbackIf TCP is truly required, bind 127.0.0.1 instead of 0.0.0.0, so the guest's route to the host does not reach it.Still unauthenticated, so weaker than the socket. Only safe if the guest has no path to host loopback.
Require mTLSTurn on --tlsverify so only clients presenting a valid client certificate can touch the API.Now reachability alone is not enough; the guest has no cert. You own a CA and a rotation story.
Packet-filter in frontAn nftables / iptables rule that DROPs traffic from the sandbox interface or subnet to the daemon port, on the input side, while the trusted CI path is still allowed.Surgical. Kills the guest's reach without touching the service or the host's own builds. Get the interface and source match right, or you lock out CI too.
Segment the networkPut the sandbox on an isolated namespace or VLAN with egress only to what it needs and no route to the host's management interface at all.Strongest and most work. Defends every host listener at once, not just Docker.

The packet-filter row is the one that answers the brief directly. The CI host keeps building because the trusted path, the local socket or the CI runner's own source address, is still allowed, while packets from the sandbox subnet are dropped before they reach the daemon. Put the unix-socket default underneath it and you have belt and suspenders: the listener the sandbox could reach is gone, and the filter catches anything that tries to reach what is left.

The same boundary mistake, again

This is the network version of a pattern I keep running into. In the password is in another process's memory the boundary people imagine around a password prompt was one word off from the real one. In agent memory is an attack surface the boundary was a memory store that treated attacker text as a believed fact. Here it is the same shape a third time:

The boundary you imagineThe boundary you have
The microVM isolates the workload from the hostThe microVM isolates the workload's process and disk, and still lets it talk to a root-equivalent service on the host

The takeaway

A microVM answers "can the workload touch the host's memory or disk?" with a confident no. It does not answer "can the workload talk to the host," and on a build box the most dangerous listener is usually one you put there on purpose. Confinement is not a primitive you buy. It is a property you assemble across all three dimensions, and the network dimension is the one a microVM leaves for you to finish.

The microVM is the hardest wall on the host. The open port behind it is the one that gets used.


If you run untrusted CI behind a microVM, this is the wall the VM does not build for you. Treat network isolation as your job, not the hypervisor's: keep the Docker daemon on its unix socket, and put a packet filter in front of anything the sandbox can still route to. The microVM is necessary. It was never sufficient.