Guide · Isolation for agent-run code · Sources read on 29 October 2026
AI agent sandbox: pick the boundary by what can go wrong.
Short answer. An AI agent sandbox limits what code run by the agent can touch. There are four layers. An OS process sandbox (Seatbelt, bubblewrap, seccomp) is what Codex and Claude Code use. A container shares the host kernel. gVisor answers system calls in user space. A microVM such as Firecracker runs its own kernel, and E2B and Vercel Sandbox are built on it. Choose the layer by how untrusted the code is and what sits on the host. Then close the two settings most setups leave open whatever the layer: outbound network and the secrets inside the sandbox.
What does an agent sandbox need to stop?
Agent code fails in two ways: the model makes a mistake, or someone steers it through indirect prompt injection. Either way the sandbox has the same six jobs:
- Destructive writes outside the task: the home directory, other repositories, system files.
- Secret reads: SSH keys, cloud credentials, tokens in environment variables.
- Exfiltration over the network, including DNS and any host on an allowlist.
- Persistence: files that run later outside the sandbox, such as git hooks, shell startup files, editor settings and agent configuration.
- Kernel escape: an exploit that turns code execution in the sandbox into code execution on the host.
- Resource abuse: fork bombs, crypto mining, filling the disk.
Which isolation layer stops what?
| Layer | How it isolates | Shares the host kernel | Stops | Leaves open | Examples |
|---|---|---|---|---|---|
| Process sandbox | Seatbelt on macOS; bubblewrap namespaces, seccomp and Landlock on Linux | Yes, and the same user account | Writes outside allowed paths, network outside a proxy allowlist | Kernel exploits; reads unless denied; anything the agent runs outside the sandbox | Codex, Claude Code, sandbox-runtime |
| Container | Namespaces, cgroups, capabilities and a seccomp profile | Yes | Host filesystem and processes, when hardened | Kernel exploits; anything a mounted socket or volume exposes | Docker, Podman |
| User-space kernel | gVisor's Sentry answers system calls itself instead of passing them to the host kernel | No direct access: a small, seccomp-restricted host surface | Most kernel attack surface | Some syscalls and /proc files are not implemented; slower on syscall-heavy work | gVisor (runsc), Modal's gvisor runtime |
| MicroVM | Hardware virtualization through KVM, with a minimal device model and its own guest kernel | No | Guest kernel exploits reaching the host, short of a hypervisor escape | Nothing at the boundary itself; network and secrets are still yours to close | Firecracker, Kata Containers, E2B, Vercel Sandbox, Cloudflare Containers |
Every layer can block writes and persistence if you configure it, and containers and VMs add resource limits. Kernel escape is the one that separates them. Untrusted code that shares your kernel is one kernel bug away from the host. That is acceptable on a developer laptop running the developer's own agent, and not on a service that runs code for many customers.
How do process sandboxes work in Codex and Claude Code?
Both coding agents wrap each shell command in an OS sandbox and leave the agent process itself outside it. Codex uses Seatbelt on macOS, bubblewrap with seccomp on Linux and WSL2, and MXC or a dedicated sandbox user on Windows. Claude Code's sandbox is off by default and is built on the open-source sandbox-runtime, which you can also use on its own to wrap any process, a local MCP server included:
~/.srt-settings.jsonsandbox-runtime{
"network": {
"allowedDomains": ["github.com", "*.github.com", "registry.npmjs.org"],
"deniedDomains": []
},
"filesystem": {
"denyRead": ["~/.ssh", "~/.aws"],
"allowWrite": [".", "/tmp"],
"denyWrite": [".env"]
}
}sandbox-runtime denies all network by default and routes allowed traffic through HTTP and SOCKS5 proxies on the host. On Linux the sandboxed process gets no network of its own, so the proxies on the host are the only way out. Without a settings file, reads stay unrestricted, so add denyRead entries for credential folders. On Linux, socket blocking relies on seccomp (x64 and arm64), and if seccomp is missing sockets stay open with a warning. Windows support is alpha.
A process sandbox shares your kernel and your user account. Whatever the agent starts outside it also runs with your full access: hooks, MCP servers, language servers. Run the whole agent in a dev container or VM when the repository is not yours.
Is a container enough for agent-generated code?
A container is a process with namespaces, cgroups, dropped capabilities and a seccomp filter. Docker's default seccomp profile disables around 44 system calls out of more than 300, and --security-opt seccomp=unconfined removes it. The rest of the kernel stays reachable. For an agent's own commands on a machine you control, a hardened container is a solid boundary. These flags remove most of what a default docker run leaves open:
docker runhardened agent runnerdocker run --rm -it \ --user 1000:1000 \ --cap-drop ALL \ --security-opt no-new-privileges \ --read-only --tmpfs /tmp \ --network none \ --pids-limit 256 --memory 2g --cpus 2 \ -v "$PWD":/work -w /work \ agent-runner:latest
- Never mount
/var/run/docker.sock. Access to the Docker socket is root on the host. - Run as a non-root user and drop every capability. Add back only what a tool proves it needs.
- Keep the root filesystem read-only. Only the workspace and
/tmpare writable. - Start with
--network noneand add an egress proxy rather than the default bridge. - Mount the workspace only. A mounted home directory brings its keys with it.
What does gVisor add?
gVisor is an application kernel written in Go that runs in user space. Its main component, the Sentry, services the application's system calls and does not pass them through to the host kernel. File access goes through a separate Gofer process, and the Sentry itself runs under a restrictive seccomp filter without direct filesystem access. It ships as an OCI runtime, runsc, so it works with Docker and Kubernetes: register it, then run the same container with --runtime=runsc.
The gVisor documentation states the cost. Workloads heavy on system calls can run slowly, and not every system call, /proc or /sys file is implemented, so some tools fail. Test the agent's real toolchain under runsc before you rely on it. Modal's sandboxes offer gVisor as one of two runtimes, and GPU sandboxes there use gVisor only.
When do you need a microVM?
Use one when the code is untrusted, the host is shared, or the agent needs privileged tools. Firecracker, built by AWS for Lambda, uses KVM to start microVMs in under 125 ms with under 5 MiB of overhead each. It exposes only five emulated devices, and a companion jailer adds a second layer of Linux isolation in case the virtualization barrier fails. Kata Containers packages lightweight VMs that behave like containers, on QEMU, Cloud Hypervisor or Firecracker.
Most hosted sandboxes for agent code use one of these designs:
| Service | Isolation | Where it is stated |
|---|---|---|
| E2B | Firecracker microVMs | Listed as a Firecracker adopter on the Firecracker project site; infrastructure code in e2b-dev/infra. |
| Vercel Sandbox | Firecracker microVM per sandbox, own filesystem and network | Vercel Sandbox documentation. |
| Cloudflare Sandboxes (Containers) | MicroVM with its own kernel and network per instance; internet access set by your Worker (enableInternet) | Cloudflare Sandboxes documentation. |
| Modal Sandboxes | runtime="gvisor" or runtime="vm" (own kernel); GPU sandboxes use gVisor only | Modal Sandboxes guide. |
| Daytona | Dedicated kernel, filesystem and network stack per sandbox, per its documentation | Daytona documentation. |
A microVM boundary does not make the session safe by itself. A microVM with open internet and your API key inside is a well-isolated place to leak that key.
Why do egress and secrets matter more than the boundary?
Exfiltration needs no kernel exploit, only an outbound request. Misuse needs only a credential the agent can read. Three rules apply at every layer:
- Deny outbound by default, then allow by domain. sandbox-runtime and Claude Code start with an empty allowlist. Codex keeps network off in
workspace-writeand offers a domain proxy. Cloudflare's example starts containers withenableInternet: false. Keep the list to package registries and the APIs the task needs. Every allowed host is a possible exit, especially one that accepts uploads or user content. - Keep long-lived secrets out of the sandbox. Pass short-lived, narrowly scoped tokens, or inject credentials at the proxy so the code never holds them. Claude Code's sandbox can mask credentials and substitute the real value only on requests to the hosts you name. Cloudflare's guidance is to keep credentials in the Worker and decide which services a sandbox reaches.
- Block the metadata endpoint and the internal network. A sandbox on a cloud VM that can reach
169.254.169.254or private ranges can pick up the instance's role credentials or talk to internal services. Codex blocks local and private destinations by default (allow_local_binding = false).
How do you choose an agent sandbox?
| Situation | Pick |
|---|---|
| Coding agent on a developer laptop | The built-in process sandbox in strict mode, plus a dev container for repositories you did not write. |
| Agent in CI running code from pull requests | A fresh hardened container or gVisor per job, no network or an allowlist, no long-lived secrets in the job. |
| Product that runs code written by an agent for many users | One microVM, or one gVisor sandbox, per session or per tenant. Never a shared container. |
| Agent that needs Docker, FUSE or a VPN inside | A microVM, where those privileged processes stay inside a guest kernel. |
Whatever the layer, make each sandbox ephemeral, one per task or session. Do not let state carry over unless you chose to keep it: a snapshot or persistent volume carries a planted file into the next run as well.
How do you test that the sandbox holds?
Run these commands inside the sandbox, the way the agent would, and keep the output as evidence. Each line should fail, print nothing, or be refused by the proxy.
escape-test.shrun inside the sandbox# Run inside the sandbox, as the agent would. Every line should fail or print nothing. touch ~/escape-test # write outside the workspace cat ~/.ssh/id_ed25519 ~/.aws/credentials # read credentials env | grep -i -E 'key|token|secret' # secrets in the environment curl -sS https://example.org # host outside the allowlist curl -sS -m 3 http://169.254.169.254/ # cloud metadata endpoint ls -l /var/run/docker.sock # container runtime socket echo 'echo pwned' >> .git/hooks/pre-commit # code that runs later, outside
Then test from the other side. Check the proxy or firewall log for the refused requests, confirm a resource limit stops a runaway process, and make sure the sandbox is gone after the task ends. A test that only reads the agent's final answer will miss most of what matters here.
