Back to Blog
    Security

    The Execution Layer Is the Trust Boundary: MicroVM Sandboxing for Claude Code

    July 26, 2026
    15 min read
    By Saanj Vij

    Your AI coding agent doesn't execute code. It predicts text.

    Everything you actually care about — editing a file, running a test, executing npm install, or pushing a commit — happens in the unprivileged shell underneath it. The model is just the brain deciding what to run. The execution layer is the muscle deciding what's possible.

    Confusing the two is why most agent security strategies fail.

    Teams dump months into system prompt tuning and eval benchmarks, but a 100% aligned model won't save you if a hijacked prompt or a malicious npm postinstall script executes rm -rf ~ in your host terminal. You can have perfect alignment and still lose your host machine.


    What We Are Solving Today

    Running --dangerously-skip-permissions is non-negotiable if you want an agent to do real, unattended, multi-step work — approving every file write and shell command by hand doesn't scale. But flipping that flag on your bare host, with your SSH keys, your .env files, and your home directory sitting right there, isn't a risk tradeoff. It's recklessness with extra steps.

    The rest of this post builds the fix, one layer at a time:

    Evolving Edge Security: from bare metal to MicroVM and zero-trust determinism — the five-layer agent trust stack

    Each layer closes exactly the gap the previous one left open. Here's where it starts.


    1. The Starting Point: Brain vs. Execution Environment

    Claude Code, like any terminal-based agent, is a loop: the model proposes a tool call, something executes it, the result goes back into context. The "something" is a shell — usually backed by Node or Bun — spawning subprocesses, touching the filesystem, hitting the network. That execution layer is where all the actual risk lives.

    Anthropic's default posture reflects this. Claude Code ships read-only by default and requires manual approval for nearly every consequential tool call — file writes, shell commands, network requests. That's the correct default. It's also the reason almost everyone eventually turns it off.

    Approval fatigue is real. Once you're running an agent through a multi-step refactor or a long dependency upgrade, you're approving dozens of near-identical prompts per session. The rational response, under fatigue, is --dangerously-skip-permissions — "YOLO mode." The flag name is doing you a favor by being honest about what it does: it removes the approval loop entirely and gives the agent unmediated access to your host shell.

    At that point you're exposed to three concrete failure modes, not hypothetical ones:

    • Destructive commands — an agent confidently executing rm -rf against the wrong path, or a migration script against the wrong database, because it misread context.
    • Prompt injection — untrusted content (a scraped webpage, a malicious README, an issue description) contains instructions that get interpreted as commands once they're in the model's context window.
    • Supply-chain compromise — a transitive npm dependency with a postinstall script that phones home or grabs credentials from environment variables the moment npm install runs.

    None of these require the model to be "misaligned." They require the execution environment to have no boundary. Fixing this is an infrastructure problem, not a prompting problem.

    Execution models compared: bare metal execution versus MicroVM isolation — the hypervisor boundary blocks rm -rf, SSH key reads, and .env exfiltration from ever reaching the host

    To stop the agent from wiping your machine, the obvious first step is to fence off process permissions at the OS level — which brings us to local OS-native sandboxing.


    2. Local Execution and OS-Native Safety

    Before reaching for heavier isolation, it's worth being precise about what a fast local agent loop actually needs, and what's already available on the OS.

    High-throughput agent runners increasingly use lightweight JS runtimes — Bun over Node, for the startup latency alone — because tool-call round-trip time compounds. A 200-step agentic session with a 400ms shell-spawn tax per step is a session nobody waits for. Speed isn't a nice-to-have here; it directly determines whether unattended, multi-step agent work is viable at all.

    But raw execution speed with zero boundary is just a fast way to damage a host. This is where OS-native process sandboxing earns its place as a default:

    MechanismPlatformBoundary typeOverhead
    Apple Seatbelt (sandbox-exec)macOSKernel-enforced process policyNear-zero
    bubblewrapLinuxUser namespaces + seccompNear-zero
    Container (shared kernel)BothNamespace isolationLow
    MicroVM (hypervisor)BothHardware-virtualized boundaryLow-moderate

    Seatbelt and bubblewrap are policy engines, not virtualization. They constrain a single process's view of the filesystem and its syscall surface — the agent's shell can be told "you may read/write inside this directory tree and nowhere else," enforced by the kernel, with no VM boot time and negligible overhead. For interactive local sessions where the agent is trusted-ish and the risk is mostly "overeager file write," this is a genuinely good default. It's cheap, fast, and closes the most common failure mode (writing or deleting outside the intended workspace) without touching your architecture.

    What it doesn't give you: a boundary that survives a process-level compromise. Seatbelt and bubblewrap still share your kernel. If the sandboxed process finds a policy gap or a kernel-level escape, the blast radius is the host, because there was never a hypervisor between the sandbox and the OS — only a policy. For a supervised, interactive coding session, that's an acceptable trade. For an unattended background agent running with full tool autonomy, it isn't.

    If we can't share the host kernel, we need to bring in a real hardware-virtualized hypervisor — enter Docker Sandboxes (sbx).


    3. Elevating Security to Docker Sandboxes (sbx MicroVMs)

    The failure mode that OS-native sandboxing doesn't cover is exactly the one that matters for unattended agent runs: you're not standing over the terminal watching each command anymore. The agent is running in the background, executing an arbitrary number of tool calls, and you find out what happened after the fact. At that point, "the kernel enforced a policy" is a weaker guarantee than "the agent was never in the same kernel as your host."

    That's the shift Docker Sandboxes (sbx) make. Instead of a shared-kernel container or a policy-fenced host process, sbx run claude launches Claude Code inside an ultra-lightweight microVM:

    # Launch Claude Code inside an isolated microVM
    sbx run claude
    

    The distinction between "container" and "microVM" is not marketing — it's the actual security-relevant boundary:

    Isolation typeShares host kernel?Escape impactTypical use
    Raw host shellYes (is the host)Full host compromiseNever, for untrusted code
    OS sandbox (Seatbelt / bubblewrap)YesFull host compromise if bypassedInteractive, supervised sessions
    Container (namespaces/cgroups)YesFull host compromise if kernel exploit foundTrusted, known workloads
    MicroVM (hypervisor-backed)No — own kernelContained to the VMUntrusted / unattended agent execution

    A microVM boots its own minimal kernel under a hypervisor boundary. A container-escape-class bug doesn't help an attacker, because there's no shared kernel to escape into — the next layer out is virtualized hardware, not the host OS. This is the same isolation model (Firecracker-class microVMs) that public cloud providers use to run untrusted multi-tenant workloads on shared hardware. Applying it to a single-tenant local agent session is arguably overkill for a supervised session and exactly correct for an unattended one.

    Sandboxed Autonomy: the Claude Agent secure stack — agent brain, guest kernel, hypervisor boundary, and host OS that never sees a raw agent syscall

    This is what makes YOLO mode legitimate rather than reckless:

    # Inside the sandbox, --dangerously-skip-permissions is safe:
    # the "danger" is scoped to a disposable VM, not the host.
    sbx run claude --dangerously-skip-permissions
    

    The flag still does what it says — it removes the approval loop and gives the agent unmediated tool access. What changes is the territory it has unmediated access to. A compromised agent, a malicious dependency, or a bad tool call can now do maximum damage inside a VM that gets torn down, not inside ~/Documents. You get full agent autonomy and you get to walk away from the terminal, because the worst case is bounded.

    Isolation stops host damage, but it doesn't stop data or key exfiltration. To fix that, we must strip raw credentials out of the sandbox completely.


    4. Real-World Security Mechanics

    The microVM boundary handles compute and filesystem isolation. Three more mechanics close the remaining gaps: credentials, network egress, and code review before merge.

    The Middleman Credential Proxy

    The naive approach — ANTHROPIC_API_KEY=sk-... sbx run claude — puts a live, unscoped credential directly inside the VM's environment. If the agent (or an injected instruction, or a malicious dependency) can read its own environment, it can read the key. Once it's out, it's out; you're rotating a credential and auditing usage after the fact.

    sbx avoids this by never putting the real key inside the sandbox at all:

    # Register the credential with the proxy, not the sandbox
    sbx secret set -g anthropic ANTHROPIC_API_KEY
    
    # The sandbox talks to a local proxy that injects the real key
    # on outbound requests to the Anthropic API — externally, at the edge
    sbx proxy managed anthropic
    

    The VM holds a reference, not a secret. Outbound API calls leave the VM addressed to the proxy; the proxy — running outside the sandbox boundary — attaches the real credential and forwards the request. The agent's execution environment never has a raw key to leak, log, or accidentally echo into a commit. This is the same pattern as a cloud metadata-service credential broker: the workload authenticates through something, it never holds the thing.

    Layer 7 Egress Network Policy

    Filesystem and credential isolation don't stop a compromised agent from making outbound network calls — exfiltrating data over DNS, hitting an attacker-controlled endpoint, or reaching a production database it should never see. That's a network policy problem, and it needs to be enforced at Layer 7 (hostname-aware), not just Layer 3/4 (IP/port), because most of what you want to allow or block is expressed as domains, not addresses.

    sbx exposes three network modes and a domain allowlist:

    ModeBehaviorWhen to use
    openUnrestricted egressLocal experimentation only
    balancedAllowlisted domains + common package registriesDefault for most agent work
    lockdownExplicit allowlist only, nothing implicitUnattended / production-adjacent runs
    # Restrict outbound traffic to exactly what the agent needs
    sbx policy allow network "*.anthropic.com"
    sbx policy allow network "registry.npmjs.org"
    sbx policy set network lockdown
    

    Under lockdown, a compromised agent can talk to Anthropic's API and your package registry — and nothing else. It cannot reach an internal database, a random webhook endpoint, or an attacker's exfil server, because those hostnames were never allowlisted. This turns "the agent tried to do something unauthorized" from a data breach into a blocked connection in a log.

    AI Agent Secure Egress: key injection and Layer 7 filtering — the agent's outbound request carries no raw API key, the local proxy injects it at the edge, and the egress filter allows only the Anthropic API and npm registry

    Bind Mounts vs. Git Worktree Clone Mode

    The last question is how agent-generated changes get from the sandbox back onto your host, and sbx gives you two models depending on how much you trust the run:

    Direct bind-mount — the sandbox mounts your working directory directly. File edits appear on the host in real time, no export step, no diff to reconcile. This is right for supervised sessions where you're watching the agent work and want immediate feedback — you get the isolation benefits (credentials, network, compute) without changing your workflow.

    Clone mode — for anything unattended:

    sbx run --clone claude
    

    Instead of mounting your working tree, sbx clones the repo into an isolated Git worktree on a generated branch (sandbox-...). The agent works entirely inside that worktree. Nothing touches your actual working directory until you explicitly fetch the branch and review it — functionally identical to reviewing a pull request from a contributor you don't yet trust:

    git fetch origin sandbox-<run-id>
    git diff main sandbox-<run-id>
    

    This is the correct default for background or scheduled agent runs: the agent gets full autonomy inside its own branch, and the host repository is never touched until a human has looked at the diff.

    You can lock down every byte of infrastructure, but you can't sandbox a moving AI model. To handle model drift, we need provenance tracking.


    5. The Moving Brain and Reproducibility Receipts

    Lock down compute, credentials, and network, and you've solved every infrastructure risk. One variable is still unsolved: the model itself. "Claude" is not a fixed artifact — model weights, routing, and system behavior can shift between runs, sometimes silently from the caller's perspective. A sandboxed agent that behaved one way on Monday is not guaranteed to behave identically on Friday, even with an unchanged prompt and an unchanged sandbox image.

    You can't sandbox your way out of that, because it isn't an execution-environment problem — it's a provenance problem. The fix is to stop treating "the model" as an assumption and start treating it as a logged fact, on every run, the same way you'd log a container image digest in a deployment pipeline.

    A reproducibility receipt is a small metadata snapshot, generated automatically alongside every agent run or eval suite, that answers "what, exactly, produced this output" after the fact:

    Show code
    import hashlib import json import subprocess from dataclasses import dataclass, asdict from datetime import datetime, timezone @dataclass class ReproducibilityReceipt: model_id: str # e.g. "claude-sonnet-5-20260115" system_prompt_hash: str # sha256 of the exact prompt sent runtime_hash: str # sha256 of the agent harness / runtime version sandbox_image_digest: str # sbx / OCI image digest for the run environment timestamp: str def sha256_of(content: str) -> str: return hashlib.sha256(content.encode("utf-8")).hexdigest() def sandbox_image_digest(image: str = "sbx-claude:latest") -> str: # Resolve the exact content-addressed digest of the running sandbox image — # a tag can move; a digest can't. result = subprocess.run( ["docker", "inspect", "--format", "{{index .RepoDigests 0}}", image], capture_output=True, text=True, check=True, ) return result.stdout.strip() def capture_receipt(model_id: str, system_prompt: str, runtime_version: str) -> ReproducibilityReceipt: return ReproducibilityReceipt( model_id=model_id, system_prompt_hash=sha256_of(system_prompt), runtime_hash=sha256_of(runtime_version), sandbox_image_digest=sandbox_image_digest(), timestamp=datetime.now(timezone.utc).isoformat(), ) if __name__ == "__main__": receipt = capture_receipt( model_id="claude-sonnet-5-20260115", system_prompt=open("SYSTEM_PROMPT.md").read(), runtime_version="claude-code-cli@2.4.1", ) with open("receipt.json", "w") as f: json.dump(asdict(receipt), f, indent=2)

    Four fields, none of them optional if you want an eval result — or an incident — to be explainable later: which model actually answered, exactly which system prompt it saw (hashed, not stored verbatim, so the receipt itself isn't a secrets liability), which runtime executed the tool calls, and which sandbox image enforced the isolation boundary at the time. Store the receipt next to the run's output, or next to the commit it produced. When a result looks different from last week's, the receipt tells you in seconds whether the model moved, the prompt drifted, the runtime updated, or the sandbox image changed — instead of a debugging session that starts from zero.


    Further Reading

    • Docker Sandboxes — the sbx CLI and its microVM isolation model for AI coding agents
    • Firecracker — the open-source microVM hypervisor (built by AWS) that this isolation model is based on
    • bubblewrap — the unprivileged Linux sandboxing tool used by Flatpak and similar projects
    • macOS Sandbox internals — Apple never published official docs for sandbox-exec/Seatbelt; it's an undocumented, now-deprecated private API, so this is the best available technical reference
    • Claude Code security & permissions — Anthropic's own documentation on the default approval model and --dangerously-skip-permissions

    Where This Leaves You

    None of these layers are independently sufficient, and that's the point — they're not alternatives, they're a stack:

    1. MicroVM isolation bounds compute and filesystem blast radius, making --dangerously-skip-permissions a reasonable choice instead of a reckless one.
    2. Credential proxying means there's never a raw key inside the boundary to leak in the first place.
    3. Layer 7 egress policy bounds what a compromised agent can reach, turning exfiltration attempts into blocked connections.
    4. Clone-mode worktrees keep unattended agent output on a branch until a human reviews it, same as any other untrusted contribution.
    5. Reproducibility receipts cover the one thing infrastructure can't fix — the model itself changing under you.

    The system prompt tunes what the agent wants to do. This stack determines what it's actually able to do if something goes wrong. For unattended, full-autonomy agent execution, the second one is the one that matters.

    What's your actual blast radius if your agent's next tool call is malicious?

    Found this useful? Let's go deeper.

    Book a free 15-minute call to discuss your cloud, DevOps, or AI strategy challenges.

    Book a Free Call