Sandboxing in AI Agents
The moment your agent can run shell commands, you have a security problem. The agent decides what to run based on whatever's in its context: a user message, a retrieved document, the output of a previous tool call. Any of that can be attacker-controlled. The resulting command runs on your infrastructure with the agent process's permissions, and no human reviews it before it runs.
This isn't only a coding-agent problem. It's any agent that touches a shell: a research agent running grep, a data agent running Python against a CSV. The shell is the attack surface no matter what the agent's job is.
Why sanitization doesn't work
The first instinct is to filter dangerous commands. Block rm -rf, block network calls, block file writes outside a directory. This breaks down fast.
NVIDIA's red team demonstrated this with an AI analytics pipeline. The attack chain: evade guardrails through prompt injection, coerce the model into encoding terminal commands in base64, then exploit trusted library functions to decode and execute them. No filter caught it because the dangerous payload never appeared as plaintext in the generated code.
A doc snippet retrieved from the agent's RAG store. Looks harmless.
The injected instruction sits inside a retrieved document. The agent treats it as authoritative.
Attackers can hide commands in base64, hex, or unicode escapes. They can use legitimate library functions that happen to have side effects. They can manipulate runtime behavior in ways that static analysis can't predict. OWASP's 2026 Top 10 for Agents lists unexpected code execution as a critical risk, and NVIDIA's research is explicit: sanitization alone is insufficient.
You need containment. If the code does something malicious, the damage should be limited to a disposable environment that gets destroyed after the command finishes.
Containers share a kernel. That's the problem.
Docker is the obvious first choice. Spin up a container, run the command inside it, tear it down. Fast, familiar, well-tooled.
The problem is what containers share with the host: the kernel. Every program running on Linux talks to the kernel through syscalls (open a file, write to disk, send a network packet). The kernel has about 40 million lines of C and exposes ~350 syscalls on amd64. Each one is a potential exploit path. A bug in any of them can let code inside a container escape into the kernel itself, and from there onto the host. Every container on the machine shares that same attack surface.
This isn't theoretical. In January 2024, CVE-2024-21626 ("Leaky Vessels") let attackers break out of runc containers and access the host filesystem. In 2025, multiple runc CVEs followed: CVE-2025-52565 (CVSS 7.3) abused /dev/console bind-mounts to read host files, and CVE-2025-52881 bypassed Linux Security Modules to crash the host or achieve full breakout. A separate NVIDIA Container Toolkit escape, NVIDIAScape (CVE-2025-23266), scored CVSS 9.0.
You can harden containers further with Linux's built-in features: limit which syscalls the container is allowed to make, restrict which files it can touch, drop privileges, give it a fake root user. These help. But they're all software conventions enforced by the same shared kernel. A kernel bug breaks all of them at once. That's why most cloud and sandbox providers moved to VM-level isolation for higher-risk multi-tenant workloads.
MicroVMs: hardware isolation that boots in milliseconds
Containers
One kernel, shared by all
shared kernel = shared blast radius
MicroVMs
Each VM ships its own kernel
each VM has its own kernel
Traditional VMs (QEMU, VirtualBox) give you a separate kernel per workload but boot in seconds to minutes and use hundreds of megabytes of memory each. Too slow and too heavy for an agent that needs to run a command and get output in under a few seconds.
MicroVMs take a different approach: strip out everything a traditional VM doesn't strictly need to run a Linux process and keep only the minimum. The result boots in about 125ms with less than 5MB of memory overhead. With snapshots (boot a VM once, save its state, restore from that state for each new sandbox), startup drops to a few tens of milliseconds, and many sandboxes can share the same base snapshot in memory so overhead stays small even with hundreds running concurrently.
Firecracker is the most widely used microVM monitor. AWS built it in Rust for Lambda and Fargate. It emulates exactly 5 devices (compared to QEMU's hundreds): virtio-net for networking, virtio-block for storage, virtio-vsock for host-guest communication, a serial console, and a keyboard controller used only for shutdown. The entire codebase is roughly 50 to 80K lines of Rust. QEMU is well over a million lines of C.
Each Firecracker VM gets its own Linux kernel, its own root filesystem, its own memory space. A kernel exploit inside the VM hits the guest kernel, not the host. The only path from guest to host is the virtio device layer, which is a narrow, well-audited boundary. VM escapes are so rare they command $250K to $500K bounties on the exploit market.
Talking to the VM: vsock
Once you have a VM running, you need a way to send commands to it and read back the output. The obvious choice is TCP networking, but that means the VM needs a network stack and a virtual network interface. More code inside the VM, more attack surface, more setup on the host.
Vsock is a Linux feature built specifically for host-VM communication. The host can talk directly to a process inside the VM with no networking involved at all. The VM doesn't even need a network interface. For the specific use case of "host sends a command, VM runs it, VM streams output back," vsock is simpler and safer than TCP.
The execution pattern
The typical setup is a small agent process running inside the VM. It listens on a vsock port, accepts connections, runs commands in a shell, and streams output back over a simple protocol. The host side connects, sends the command, reads output frames until it gets an exit code.
Output gets base64-encoded because terminal output can contain arbitrary bytes (ANSI escape codes, binary data, anything). The client receives Server-Sent Events, decodes each chunk, and renders it in a terminal emulator.
The agent inside the VM allocates a pseudo-terminal (PTY) for each command. Without a PTY, programs detect they're not in a terminal and change behavior: ls drops colors, grep stops highlighting, top refuses to start. With a PTY, the command thinks it's in a real terminal, and colors, formatting, and interactive programs all work as expected.
The alternatives
Firecracker isn't the only option. Isolation runs on a spectrum: hardware virtualization is strongest, user-space kernels sit in the middle, and namespace-based containment is the weakest.
gVisor
Google's approach. A user-space kernel written in Go that intercepts syscalls before they reach the host kernel. The guest program thinks it's talking to Linux, but gVisor re-implements ~274 syscalls in user space and only makes ~53 actual host syscalls. The attack surface shrinks dramatically.
The tradeoff is performance. Simple syscalls are ~2.2x slower than native. File operations are 10x+ slower on disk-heavy workloads. CPU-bound work has zero overhead since the code runs directly on the CPU. Startup is 50 to 100ms.
OpenAI's Code Interpreter reportedly uses gVisor (per public reverse-engineering). Modal uses it for their serverless platform. Google Cloud Run runs on it. Use it when containers aren't enough but a full VM is overkill.
nsjail
A lightweight Linux sandbox. Each command runs in its own bubble with its own filesystem view, its own user list, restricted syscalls, and no network access by default. Sub-20ms startup, no daemon required. Figma uses it for their render server.
The limitation: still shares the host kernel. A kernel exploit bypasses everything. Good for isolating trusted-ish code where the threat model is "prevent accidents," not "prevent adversarial attack."
The security hierarchy
From strongest to weakest: hardware virtualization (Firecracker, QEMU) stops kernel exploits at the hardware boundary. User-space kernels (gVisor) shrink the syscall surface to a small allowlist. Namespace/seccomp (nsjail, hardened Docker) are software conventions on a shared kernel. Each step down trades security for speed.
MicroVM
Firecrackerown kernelHardware boundary. Own kernel. Stripped-down devices.
Stops
Kernel exploits hit the guest, not the host. VM escapes are rare.
In production
Startup
~125ms cold, ~tens of ms warm
Overhead
<5MB memory per VM
Who's building this
You don't have to build sandbox infrastructure yourself. Several companies sell it as a service.
E2B runs Firecracker microVMs. Same-region sandboxes start in about 80ms. Python and TypeScript SDKs, integrations with LangChain, OpenAI, Anthropic, and the Vercel AI SDK. The developer experience is their main selling point.
Modal uses gVisor and focuses on GPU workloads. Sub-second cold starts, scales to 20,000 concurrent containers. Python-first.
Fly.io runs everything on Firecracker. Their Machines API gives you on-demand microVMs with persistent storage. Checkpoint/restore in ~300ms. They also have Sprites (in early access) specifically for AI agent sandboxes.
AWS Bedrock AgentCore gives each agent session its own Firecracker microVM that lives for up to 8 hours, with a 15-minute idle timeout. When the session ends, the entire VM is destroyed and all data deleted.
When you need what
Containers are fine for running your own trusted code. Internal services, CI/CD pipelines, development environments. The threat model is "prevent accidents," and containers with basic hardening handle that.
The moment the code is untrusted, you need stronger isolation. AI-generated code is untrusted by definition. So is any code from users, third-party plugins, or retrieved documents that get executed. For these, microVMs or gVisor are the minimum.
If you're building an agent that runs code, start with a managed sandbox service like E2B or Modal. It's the fastest path to production and someone else handles the infrastructure.
The rule is simple: never run AI-generated code on the same kernel as anything you care about.
Sources
- How Code Execution Drives Key Risks in Agentic AI Systems by NVIDIA
- How AWS's Firecracker Virtual Machines Work by Amazon Science
- Sandboxing and Workload Isolation by Fly.io
- Your Containers Aren't Isolated by Northflank
- Firecracker Internals by Tal Hoffman
- Claude Code Sandboxing by Anthropic
crafted by bart stefanski