Agent Execution Sandboxes: Production Architecture for Safe AI-Generated Code
A deep-dive on sandbox architectures for AI agent code execution — container isolation vs gVisor vs Firecracker microVMs, network egress controls, OWASP threat modeling, and production deployment patterns with benchmark data.
Every production agent that generates and executes code faces the same architectural question: how do you give an LLM the freedom to run arbitrary commands while guaranteeing it cannot damage the host, access unauthorized data, or escape its environment?
The answer is defense in depth through layered sandboxing. By mid-2026, the agent sandbox landscape has converged on three isolation tiers — containers, gVisor, and Firecracker microVMs — each with distinct security properties, performance profiles, and operational costs. Choosing the wrong one for your threat model is a production incident waiting to happen.
This post maps the architecture of agent execution sandboxes: the threat models, the isolation technologies, the production deployment patterns, and the benchmark data that separates effective sandboxing from security theater.
The Threat Model: Three Classes of Attack
Before selecting a sandbox technology, you need a clear threat model. OWASP’s Top 10 for Agentic Applications 2026 identifies three attack classes that directly involve code execution [1]:
ASI-05: Unexpected Code Execution — The agent generates or runs code that performs unintended operations. The most common vector is prompt injection embedding shell commands in tool output that the agent incorporates into its execution plan. Parallax research (April 2026) showed that during a single prompt injection incident, attacks propagate to 48% of co-running agents in multi-agent deployments [2].
ASI-06: Memory and Context Poisoning — An attacker poisons the agent’s context or stored state, causing it to generate malicious code in subsequent turns. Unlike ASI-05 (a single injection), this is persistent — the compromised context survives across sessions.
ASI-07: Agent-to-Agent Contamination — In multi-agent systems, a compromised agent propagates malicious instructions to peer agents through shared tools, message buses, or shared memory. This is the agentic equivalent of lateral movement in traditional network security.
The implication for sandbox architecture: your isolation boundary must handle all three attack classes, not just the naive “agent runs bad code” scenario. A sandbox that prevents code execution but allows an agent to poison shared memory has failed at the architectural level.
The Three Isolation Tiers
Every sandbox approach falls into one of three isolation tiers. The choice determines your security ceiling and your performance floor.
| Tier | Technology | Isolation Boundary | Boot Time | CPU Overhead | Kernel Exposure |
|---|---|---|---|---|---|
| 1 — Container | Docker/Podman | Namespace isolation (same kernel) | 200–500ms | ~1–3% | Shared host kernel |
| 2 — Secured container | gVisor / Kata Containers | User-space kernel (intercepts syscalls) | 500ms–2s | ~5–15% | gVisor kernel (no host kernel access) |
| 3 — MicroVM | Firecracker / QEMU | Hardware-level (separate kernel per sandbox) | 2–6s | ~3–8% | Isolated guest kernel |
The critical distinction: Tier 1 shares the host kernel, Tier 2 replaces it with a user-space shim, and Tier 3 runs a separate kernel entirely. Each tier has legitimate use cases, but the threat model determines which is appropriate [3][4].
Tier 1: Container Isolation
Docker containers use Linux namespaces (pid, net, mount, user, uts, ipc) and cgroups to create isolated execution environments. They share the host kernel, which means a kernel vulnerability can escape the container.
When Tier 1 is sufficient:
- Trusted code execution (code written by vetted developers, executed by agent)
- Read-only tool invocations (grep, stat, curl with fixed URLs)
- Internal tool-use where the consequence of escape is limited
- Development and testing environments
When Tier 1 is NOT sufficient:
- Arbitrary AI-generated code from open-ended prompts
- Code that accesses network resources based on agent decisions
- Multi-tenant environments where one agent’s sandbox escape compromises others
- Any deployment where kernel access equals data access
# Container-based sandbox — Docker SDK for agent code execution
import docker
import tempfile
client = docker.from_env()
def run_in_sandbox(code: str, timeout: int = 30) -> dict:
"""Execute Python code in a container sandbox."""
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
f.write(code)
f.flush()
try:
container = client.containers.run(
image='python:3.12-slim',
command=['python', f'/tmp/{f.name}'],
volumes={f.name: {'bind': f'/tmp/{f.name}', 'mode': 'ro'}},
network_mode='none', # No network egress
read_only=True, # Read-only filesystem
mem_limit='256m',
cpu_period=100000,
cpu_quota=50000, # 0.5 CPU
auto_remove=True,
detach=True
)
result = container.wait(timeout=timeout)
logs = container.logs(stdout=True, stderr=True).decode()
return {"exit_code": result['StatusCode'], "output": logs}
except docker.errors.APIError as e:
return {"exit_code": -1, "output": str(e)}
The network_mode='none' and read_only=True flags are the most important lines. Without explicit network isolation, a container sandbox can phone home. Without read-only filesystem, an agent can install cryptominers or write persistent malware inside the container [3].
Tier 2: gVisor — The User-Space Kernel
gVisor (Google’s container sandbox) replaces the host kernel with a user-space kernel (runsc) that intercepts all system calls. The agent’s code makes syscalls to gVisor’s kernel, which either handles them internally or forwards them through a limited, audited path to the host kernel [4].
# gVisor sandbox configuration — syscall interception
# RuntimeClass manifest for Kubernetes-based agent sandboxing
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor-agent
handler: runsc # Uses gVisor runsc runtime
overhead:
podFixed:
cpu: "250m" # Fixed overhead per pod
memory: "64Mi" # Fixed memory overhead for gVisor
---
# Example: running an agent-triggered command via gVisor
apiVersion: v1
kind: Pod
metadata:
name: agent-code-exec
spec:
runtimeClassName: gvisor-agent
containers:
- name: sandbox
image: python:3.12-slim
command: ["python", "-c", "$(AGENT_CODE)"]
resources:
limits:
memory: "512Mi"
cpu: "1"
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
automountServiceAccountToken: false
The key advantage over Tier 1: a kernel exploit in gVisor breaks the gVisor kernel, not the host kernel. Public benchmarks show gVisor adds 5–15% CPU overhead and ~500ms–2s cold start latency compared to native containers [4][5]. The overhead comes from syscall interception — every filesystem access, network call, and memory allocation passes through gVisor’s kernel implementation.
The real-world cost: On a production agent cluster running 100 concurrent sandboxes, the 250m CPU and 64Mi memory overhead per sandbox (as seen in the RuntimeClass definition) means 25 CPUs and 6.4Gi of memory consumed entirely by the sandbox layer, not the agent’s workload [5].
Tier 3: Firecracker MicroVMs
Firecracker (AWS, open-source) runs each sandbox as a lightweight microVM with its own Linux kernel. The isolation boundary is the hardware virtualization layer — a guest kernel compromise cannot reach the host because the hypervisor (KVM) enforces the boundary at the CPU level [6].
The startup overhead (2–6 seconds for a microVM) is the main operational concern. Production systems solve this with sandbox pooling — a pre-warmed pool of microVMs ready to accept code execution requests.
# Architecture sketch: pre-warmed Firecracker sandbox pool
# Each microVM boots once, runs a persistent agent shim,
# and handles multiple code execution requests over vsock.
class MicroVMPool:
"""Pool of pre-warmed Firecracker microVMs for agent code execution."""
def __init__(self, pool_size: int = 10):
self.pool = asyncio.Queue(maxsize=pool_size)
self._warm_pool()
def _warm_pool(self):
"""Pre-boot microVMs with agent shim ready."""
for _ in range(self.pool.maxsize):
vm = self._boot_microvm()
self.pool.put_nowait(vm)
async def execute(self, code: str, timeout: int = 30) -> dict:
"""Acquire a warm microVM, execute code, return result."""
vm = await self.pool.get()
try:
result = await vm.run_code(code, timeout=timeout)
return result
finally:
# Reset VM state and return to pool
await vm.reset()
self.pool.put_nowait(vm)
def _boot_microvm(self) -> 'MicroVM':
"""Boot a Firecracker microVM with vsock agent shim.
Boot time: ~3-4s (includes kernel load + agent init).
Memory: 128Mi base + workload memory.
Storage: rootfs from read-only snapshot.
"""
# Uses firecracker-go-sdk or direct API calls
pass
E2B and Modal both use pre-warmed Firecracker pools to achieve sub-100ms “warm start” code execution — the pool absorbs the 3–6s boot time, and each execution request acquires a ready microVM in milliseconds [7]. The pool size is the critical tuning parameter: too small and requests queue under load; too large and you waste memory on idle microVMs.
Network Egress: The Most Commonly Missed Control
Every sandbox technology handles network isolation differently, and getting it wrong is the most common production sandbox failure. An agent that can make arbitrary HTTP requests from inside a sandbox can exfiltrate data, call attacker-controlled endpoints, and bypass security controls designed for the agent’s own environment.
The standard pattern is default-deny egress with allowlisted endpoints:
# Network egress firewall for agent sandbox
# Deny all, allow only specific endpoints needed for the agent's work
AGENT_ALLOWLIST = {
"github.com:443": "API access for PR operations",
"api.openai.com:443": "LLM inference endpoint",
"pypi.org:443": "Package installation for dependencies",
"files.pythonhosted.org:443": "Package downloads",
}
def apply_egress_policy(netns_name: str, allowlist: dict[str, str]):
"""Apply network egress policy to a sandbox network namespace.
Strategy: default DROP, allowlist-specific ACCEPT.
"""
rules = []
for host_port, purpose in allowlist.items():
host, port = host_port.split(":")
rules.append({
"direction": "egress",
"action": "accept",
"destination": host,
"dport": int(port),
"purpose": purpose,
})
# Apply via iptables/nftables in the sandbox's network namespace
# Deny all other egress traffic at the namespace boundary
The most effective pattern discovered in production: layer 3 (IP-based) deny by default, with layer 7 (TLS SNI) verification as a second gate. This prevents DNS rebinding attacks where an allowlisted domain resolves to an attacker-controlled IP after the initial lookup [3].
Filesystem Isolation: Read-Only Root + Ephemeral Writes
An agent’s filesystem access should follow the principle of least privilege: read the minimum needed, write only to disposable scratch space.
Sandbox filesystem layout:
/ → read-only rootfs (from immutable image)
/app/code/ → read-only, agent's reference data
/tmp/ → read-write, temporary scratch (cleared after execution)
/output/ → read-write, for agent-generated results (preserved)
This is trivial to implement with overlayfs (overlay2 for Docker, overlay for gVisor, device-mapper snapshots for Firecracker). The read-only rootfs prevents the agent from modifying system binaries, installing persistent software, or planting anything that survives the sandbox reset.
Key production pattern: send SIGKILL to all processes before destroying the sandbox. Agents can spawn background processes that survive the main process exit. A deferred SIGKILL sweep (kill -9 -1 from the init namespace) ensures no orphan processes linger [6].
Production Deployment Architecture
A production-grade agent sandbox infrastructure combines all three tiers:
Agent Request
│
▼
┌─────────────────────────────┐
│ Sandbox Router │ ← Routes to correct tier
│ (threat classification) │ based on code provenance
└──────────┬──────────────────┘
│
┌──────┴──────┐
│ │
▼ ▼
┌────────┐ ┌──────────┐
│ Tier 1 │ │ Tier 2/3 │
│ (trusted) │ │ (untrusted)│
│ Docker │ │ gVisor / │
│ │ │ Firecracker │
└────────┘ └──────────┘
│
▼
┌──────────────────┐
│ Sandbox Pool │
│ (pre-warmed) │
├──────────────────┤
│ ┌──┐ ┌──┐ ┌──┐ │
│ │VM│ │VM│ │VM│ │ ← Hot pool (idle but ready)
│ └──┘ └──┘ └──┘ │
│ ┌──┐ ┌──┐ ┌──┐ │
│ │VM│ │VM│ │VM│ │ ← Warm pool (booting)
│ └──┘ └──┘ └──┘ │
└──────────────────┘
│
▼
┌──────────────────┐
│ Egress Firewall │ ← Default deny + allowlist
└──────────────────┘
│
▼
┌──────────────────┐
│ Result Collector │ ← Gather stdout/stderr/exit code
└──────────────────┘
The sandbox router classifies each code execution request by provenance:
- Trusted (first-party tool code, vetted packages) → Tier 1 (Docker) for speed
- Untrusted (AI-generated code, user-submitted scripts) → Tier 2 or 3 for isolation
This hybrid approach minimizes overhead: ~80% of code executions in production systems are trusted (deterministic tool invocations, library calls), and only ~20% need full microVM isolation [7].
Benchmark Data: Sandbox Overhead in Production
Public benchmarks from production sandbox deployments in 2026 provide actionable data for capacity planning:
| Operation | Native | Docker | gVisor | Firecracker |
|---|---|---|---|---|
Python print("hello") |
0.02s | 0.25s | 0.8s | 3.5s (cold) / 0.05s (warm) |
pip install requests |
2.1s | 2.4s | 4.2s | 6.1s (cold) / 2.8s (warm) |
| grep 1GB file | 8.3s | 8.5s | 14.2s | 9.1s (warm) |
| Network GET (200ms round trip) | 0.2s | 0.2s | 0.8s | 0.3s (warm) |
| Memory allocation (1GB) | 0.01s | 0.01s | 0.12s | 0.02s |
Data compiled from Northflank, Augment Code, and Spheron sandbox benchmarks, January–June 2026 [3][4][7].
Key observations:
- gVisor’s syscall interception overhead is most visible on I/O-heavy operations (grep: 70% slower, network: 300% slower on first connection)
- Firecracker’s cold start is prohibitive (3.5s for a trivial operation), but warm-start performance is within 2–3× of native
- Docker’s overhead is primarily startup (200–500ms for container creation), not runtime — making it the clear choice for long-running code executions
- Memory-intensive operations see minimal sandbox overhead in all tiers — the bottleneck is the workload, not the isolation layer
Operational Patterns
Pattern 1: Proportional Pool Sizing
The sandbox pool should be sized to the 95th percentile of concurrent demand, not the mean. Production data from E2B’s Firecracker deployments shows that pool size at P95 is ~3.2× the mean concurrent load [7]. Sizing to the mean results in cold starts during traffic spikes; sizing to P99 wastes memory on idle VMs.
Pattern 2: Graceful Degradation
When the sandbox pool is exhausted, queue, don’t fail open. A common production bug is falling back to less-isolated execution (Tier 3 → Tier 1) when the pool is empty. This defeats the purpose of layered sandboxing. Instead, implement a request queue with a bounded wait time and clear timeout semantics.
Pattern 3: Audit Logging
Every sandbox execution should produce an audit record with:
- Code hash (SHA-256 of the executed code)
- Provenance (which agent turn triggered it)
- Sandbox tier (Docker / gVisor / Firecracker)
- Execution time breakdown (boot, runtime, teardown)
- Network connections (destination, port, bytes transferred)
- Exit code and truncation status
This audit trail is the single most valuable tool for incident response. A prompt injection that yields a sandbox escape attempt shows up as an anomalous network connection attempt in the audit log before any production data is exfiltrated [1].
Decision Framework
| If your agent… | Use Tier | Because |
|---|---|---|
| Only runs read-only CLI tools (grep, jq, curl) | 1 (Docker) | Low risk, low overhead |
| Generates code from known templates | 1 (Docker) | Predictable output, minimal risk |
| Generates arbitrary code from user prompts | 2 (gVisor) | Unpredictable, needs syscall filtering |
| Runs in multi-tenant environments | 3 (Firecracker) | Kernel isolation per tenant |
| Executes code with network access | 3 (Firecracker) | Network escape from Tier 1/2 is too easy |
| Requires GPU access | 1 (Docker with GPU) | MicroVM GPU passthrough is immature in 2026 |
The Parallax paper’s finding (48% propagation rate for prompt injections in multi-agent systems) should drive most teams toward Tier 3 for any sandbox that handles untrusted input [2]. The operational cost of Firecracker’s infrastructure is significant, but the alternative — a sandbox escape that compromises 48% of your agent cluster — is catastrophic.
References
[1] OWASP, “OWASP Top 10 for Agentic Applications for 2026,” December 2025. https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/
[2] “Parallax: Why AI Agents That Think Must Never Act,” arXiv:2604.12986, April 2026. https://arxiv.org/html/2604.12986v1
[3] Northflank, “How to Sandbox AI Agents in 2026: MicroVMs, gVisor & Isolation,” February 2026. https://northflank.com/blog/how-to-sandbox-ai-agents
[4] Augment Code, “What Is an Agent Execution Sandbox?,” May 2026. https://www.augmentcode.com/guides/agent-execution-sandbox
[5] gVisor Project, “Kubernetes RuntimeClass Overhead,” gVisor documentation, 2026. https://gvisor.dev/docs/user_guide/kubernetes/#runtimeclass-overhead
[6] AWS, “Firecracker: Secure and Fast MicroVMs for Serverless Computing,” USENIX ATC 2020 (updated 2026). https://www.usenix.org/conference/atc20/presentation/agache
[7] Spheron Network, “AI Agent Code Execution Sandboxes on GPU Cloud,” April 2026. https://www.spheron.network/blog/ai-agent-code-execution-sandbox-e2b-daytona-firecracker/
📖 Related Reads
- NiteAgent — AI agent development, frameworks, and production patterns
- ToolBrain — tool reviews, LLM comparisons, and AI workflow guides
Cross-links automatically generated from CodeIntel Log.