Agent harness design: the sandbox fabric

June 1, 2026

This is a companion to Agent harness design: trade-off analysis, which frames the agent platform as a small set of separable primitives connected by contracts. That overview names four primitives the ecosystem agrees on, sandbox, persistence, identity, and the harness itself. This post is a deep dive on the first of them: the sandbox fabric, the primitive that owns isolation, the execution environment's lifecycle, and the network boundary around it.

The sandbox fabric answers exactly one consumption request, and a small administrative surface around it.

Give me an environment matching this spec: this isolation class, this resource shape (CPU, memory, disk), this network policy, bootstrapped from these inputs. Hand back a handle I can run commands against. Tell me when it is gone.

Everything past that handle belongs to a different primitive. Who the caller is belongs to identity. What survives a pod death belongs to persistence. What the model should send belongs to the harness. The sandbox fabric's job is narrow: provide an execution environment whose blast radius is known, whose egress is enforced at a layer the code inside cannot reach, and whose recreation is deterministic enough to call "recovery." The four properties in the contract, isolation class, resource shape, network policy, and bootstrap, are the four axes this post deepens.

The trust boundary is a placement decision, not an isolation product

The first decision is not "which sandbox vendor" but "where does the trust boundary sit relative to the agent's reachable set." The harness has to touch two things that normally live on opposite sides of that boundary: high-value secrets (LLM keys, OAuth tokens, platform credentials) and the agent's execution environment (a shell, a filesystem, package installers, browser automation). Where the harness sits relative to the sandbox decides how those two reachability sets compose. There are three canonical placements.

graph TB
    classDef trust fill:#f9d6d6,stroke:#c0392b
    classDef control fill:#d6eaf8,stroke:#2980b9
    classDef exec fill:#d5f5e3,stroke:#27ae60

    subgraph A["Full outside"]
        direction TB
        subgraph A_trust["Trust plane"]
            A_vault[Credential vault]
            A_proxy[Egress policy]
        end
        subgraph A_control["Control plane"]
            A_harness[Harness / Runner]
        end
        subgraph A_exec["Execution plane"]
            A_sandbox[Sandbox pod]
        end
        class A_trust trust
        class A_control control
        class A_exec exec
        A_harness -->|gRPC| A_sandbox
        A_vault -.->|keys| A_harness
    end

    subgraph B["Hybrid"]
        direction TB
        subgraph B_trust["Trust plane (external)"]
            B_proxy[Proxy + vault]
        end
        subgraph B_combined["Control + Execution (colocated)"]
            B_harness[Harness]
            B_sandbox[Sandbox]
        end
        class B_trust trust
        class B_combined exec
        B_proxy -.->|inject creds| B_harness
        B_harness -->|local call| B_sandbox
    end

    subgraph C["Full inside + external proxy"]
        direction TB
        subgraph C_trust["Trust plane (external)"]
            C_proxy[Proxy]
        end
        subgraph C_inside["Single container"]
            C_all[Harness + Sandbox]
        end
        class C_trust trust
        class C_inside exec
        C_proxy -.->|sole egress| C_all
    end

Full outside. The harness runs in a trusted control plane. The sandbox is a credential-free execution environment that accepts commands over gRPC and returns results. Generated code cannot read a credential because no credential is present to read. The blast radius of compromised code or prompt injection is bounded to what the sandbox itself can do: read files in /workspace, run user code, make whatever outbound calls the egress policy permits. The mechanism that makes this strong is also what makes it expensive: every tool call is a network round-trip. Persistent multiplexed gRPC connections put that in the low-millisecond range, but it will never match an in-process syscall. Browser Use argues this cost is "noise compared to LLM response times" for most workloads [11]; LangChain names it the defining trade-off of the sandbox-as-tool pattern [12]. The model also pays a second, subtler tax: when sandbox exec, MCP tools, and web fetch all coexist, it has to track which environment holds which state and sometimes picks the wrong one. The reason to accept those costs is empirical: Anthropic reports that splitting the "brain" from the "hands" cut p50 time-to-first-token by roughly 60% and p95 by more than 90%, mostly by keeping container provisioning off the critical path for sandbox-optional work [5]. The decoupling also buys independent failure domains: a harness crash does not kill the sandbox, and a sandbox crash does not lose session history.

Hybrid. Harness and sandbox share a process or container; credentials arrive from an external proxy that injects them at request time and enforces egress. This is the shape Anthropic documents for secure Agent SDK deployment, an application inside a sandbox with --network none and a Unix-socket proxy to the host [13], and the one Daytona documents [3]. Inner-loop latency collapses to in-process calls. The trust plane stays non-bypassable because the network path is shut off except through the proxy. The trap is that "non-bypassable" is a property you have to actually achieve, not assume: not every runtime respects HTTP_PROXY/HTTPS_PROXY (Node.js fetch() famously ignores them), TLS prevents content inspection of HTTPS traffic, and a single bypass of the egress boundary exposes exactly the credentials the proxy was injecting. The n8n expression sandbox escape (CVE-2026-25049, CVSS 9.9) [16] is what that looks like when the boundary is treated as sufficient on its own.

Full inside. Harness and sandbox collapse into one container with a proxy as the only egress. This is Fly.io's "Sprites" model [4] and the common single-user CLI deployment. It is the simplest thing that works and has the fastest tool loop, and Fly.io's argument that "the age of sandboxes is over" for persistent single-tenant workloads is coherent when the threat model excludes untrusted code. It is unsuited to multi-tenancy: one user's compromised session can reach another's data, and isolation is only as strong as the proxy is hard to bypass.

The separable concern, and the reason placement is subtle, is that trust-boundary placement and harness physical placement are two decisions, not one. The security model does not require the harness to be physically outside the sandbox. It requires that credentials and policy enforcement sit outside the agent's reachable boundary. An external proxy injecting into a colocated harness satisfies that just as well as moving the harness out. Vercel frames this as four isolation approaches with "separated compute with secret injection proxy" as the default production recommendation [14]. The pushes-toward signals are familiar: shared multi-tenant control plane, untrusted input combined with high-privilege writes and high-value secrets, and TTFT as a hard metric all push outside or hybrid; single-user deployments, same-directory file ops with no high-value secrets, and a tight complexity budget push inside or hybrid.

One mechanism-level caveat that placement alone does not resolve: the container runtime itself is part of the trust boundary. The runc escapes CVE-2025-31133 / CVE-2025-52881 (November 2025) [7] are container-level breaks independent of the egress proxy. They matter most to the full-inside threat model, where a runtime escape and a credential leak are the same event, and least to full-outside, where there is no credential inside the broken container to steal. This is the concrete reason the placement choice and the isolation-class choice (gVisor, Kata, Firecracker) are coupled but not identical.

What isolation solves, and the larger set it does not

"Harness outside the sandbox" is an important security property and a narrow one. It solves secret reachability: generated code cannot read high-value credentials. By itself it solves nothing else, and the gap between what isolation covers and what an agent platform actually needs is where most production incidents live.

graph LR
    classDef safe fill:#d5f5e3,stroke:#27ae60
    classDef partial fill:#fdebd0,stroke:#e67e22

    subgraph "Isolation coverage"
        SR[Secret reachability]
        FD[Fault domain isolation]
    end

    subgraph "Requires additional controls"
        PI[Prompt injection]
        OP[Overprivileged actions]
        NB[Non-bypassable enforcement]
        CL[Credential lifecycle edge cases]
    end

    class SR,FD safe
    class PI,OP,NB,CL partial

Prompt injection is orthogonal to isolation. The model may follow malicious instructions embedded in web pages, files, repository content, or MCP tool responses, and isolation does not stop the agent from taking harmful actions through its legitimate tool interface. It only stops credential theft. Willison names the precondition the "lethal trifecta": private data access, exposure to untrusted content, and the ability to communicate externally [17]. OWASP ranks prompt injection the number one LLM vulnerability for 2025 and notes it is unclear whether fool-proof prevention exists [18]. One audit found prompt injection in 73% of production AI deployments assessed in 2025 [26], and industry telemetry for late 2025 reports a sharp year-over-year rise in both attempts and successful attacks, with indirect injection now the majority [27], which is precisely the channel MCP tool responses and web fetch expose. The reasoning the sandbox fabric has to internalize from this: isolation reduces the consequence of code that the agent runs, not the consequence of decisions the agent makes. Those are different attack surfaces and need different controls.

Overprivileged allowed actions slip through legitimate channels. Even with a domain allowlist, broad permissions on an allowed destination, for instance write access to a production database through an allowed MCP server, let prompt injection cause damage without ever leaving the permitted set. Short-lived credentials shrink the replay window; they do not shrink the permissions inside that window. The mechanism worth naming: an allowlist constrains where the agent can act, not what it can do once there, and the second constraint is a policy problem the sandbox fabric does not own.

Non-bypassable enforcement is a property of the layer, not the config. True egress enforcement requires network-level controls, not configuration that a process inside the sandbox can ignore. ARMO contrasts application-layer guardrails, which a prompt injection can manipulate, with kernel-level enforcement such as eBPF at 1 to 2.5% CPU overhead: "a prompt injection can manipulate the agent's behavior, but it can't override kernel-level restrictions" [15]. There is a quantitative version of this argument. A study surveyed by SandboxEscapeBench found kernel mechanisms (capabilities, seccomp, mandatory access control) block 67.57% of privilege escalations versus only 21.62% for namespaces and cgroups alone [19]. The lesson for the fabric: the more enforcement you can move from the application layer down to the syscall boundary, the smaller the set of capabilities an attacker needs to defeat it. The next section makes that gradient concrete.

Credential lifecycle is where the happy path and the failure path diverge. Issuing a scoped token is easy; the edge cases decide behavior under stress. What happens when a token is refreshed while a connection is active, does the connection carry the old token or the new one? When a token is revoked mid-session, does the error reach the model as "retry" or as "stop"? When a scheduled tick fires near token expiry, who refreshes proactively? These are design questions the fabric and the identity boundary have to answer together before the first incident, not operational accidents to be handled after.

Two production CVEs anchor that prompt injection is exploited, not theoretical: Microsoft Copilot's EchoLeak (CVE-2025-32711, CVSS 9.3) and a Cursor IDE flaw (CVE-2025-54135, CVSS 9.8) were both exploited in 2025 and 2026 through injection, not through any failure of sandbox isolation. Isolation was never the control that would have stopped them.

Lifecycle modes and bootstrap determinism

The sandbox has a lifecycle independent of both the session and the harness, and that lifecycle is itself a design axis with a direct cost-versus-latency trade-off.

  • Always-on per session. Created at session start, live until session end or idle timeout. Fastest tool latency because there is no provisioning hop; highest cost because idle pods accrue compute charges during every LLM wait.
  • Hibernation / pause-resume. Paused between turns or scheduler ticks. You pay only for storage while paused and a cold-start penalty on resume, tens of milliseconds for Firecracker snapshots, seconds for Kubernetes pause/unpause. A good fit for bursty interaction.
  • Lazy provisioning. No sandbox exists until the first sandbox tool call. The right default for agents that often do not need a sandbox at all (conversational agents, MCP-only monitoring), where the cold-start cost is paid only by the call that triggers it.
  • Pre-warmed pool. A pool of ready pods absorbs burst demand for ephemeral sessions, trading idle cost against p99 session-start latency. E2B [1], Modal [2], and others offer this as a managed product. The GKE Pod Snapshots plus the Kubernetes agent-sandbox CRD (SIG, November 2025) [20] combine snapshot resume with Kata or gVisor isolation, and the OSDI '25 analysis "Fork in the Road" studies cold-start latency optimizations in production serverless systems directly applicable here [21].
  • Per-session ephemeral. Fresh pod per session, destroyed at end. Strongest inter-session isolation, worst cold-start.

Cold-start is the number these modes trade against, and it varies by an order of magnitude across providers: 2026 benchmarks put it in the tens of milliseconds for snapshot-restore designs and sub-second for others [8] [9], with Firecracker snapshot restore reported as low as 28 ms in production [10]. The reason no serious provider cold-pulls a Docker image on the critical path is exactly this: a multi-second image pull would dominate every other latency budget in the system. The "10 to 20 second Docker pull" number people quote is a property of bare-Kubernetes deployments, not of the sandbox fabrics built for this workload.

The deeper point is that every non-always-on mode implicitly depends on bootstrap reproducibility. The moment a sandbox can be destroyed and recreated, recovery fidelity is bounded by what the bootstrap spec can reconstruct. Determinism is not binary; it is a gradient, and the level you pick is the recovery guarantee you can honestly make.

Level Example Recovery fidelity
Pinned git checkout <sha>, lockfile-pinned npm ci High: identical workspace each time
Versioned git checkout main, npm install with lockfile Medium: drifts as upstream main moves
Floating git clone main, npm install (no lockfile) Low: whatever upstream looks like right now
Side-effectful Setup that registers with a remote service, creates credentials, or mutates shared state Not reproducible: re-running changes external state

Recovery after sandbox loss replays bootstrap. If bootstrap is non-deterministic, "resume" is an ambiguous operation: the agent sees a workspace that is similar to what it had, not the same, and over a long-lived session that drift accumulates silently and undetectably. The reasoning chain is worth making explicit, because it is what couples this axis to the persistence axis: a sandbox is only as disposable as its bootstrap is deterministic, and a fabric that markets ephemeral sandboxes while permitting floating bootstraps is selling a recovery story it cannot back.

Two failure modes deserve mechanism-level attention. First, snapshot staleness versus bootstrap determinism are distinct problems with opposite symptoms. Resume-from-snapshot bypasses bootstrap entirely, so the determinism table above applies only to fresh boots. Snapshot recovery instead inherits whatever the snapshot froze: expired credentials, stale DNS caches, now-invalid session tokens. A perfectly deterministic bootstrap and a snapshot-based resume can disagree about the state of the world, and the snapshot is the one more likely to be subtly wrong. Second, a bootstrap that mutates remote state re-runs its side effects on every recovery. A setup script that creates credentials, registers webhooks, or posts "starting" to a channel will do all of that again each time the sandbox is rebuilt, which is why the side-effectful row is not merely "low fidelity" but actively unsafe to replay. Pre-warmed pools add a third: pods returned to the pool unwashed from a prior session leak state into the next, so pool hygiene is a correctness property, not an optimization.

The workspace that survives a pod death, and how a hibernated pod's volume follows it across nodes, is a persistence-fabric concern rather than a sandbox-fabric one. It is consumed across that seam; see the hub for the persistence side of the contract.

Network policy: the layer where egress becomes non-bypassable

Proxies are configuration-level controls. Network policy is the substrate that makes them non-bypassable. The two together are a defense in depth; neither is sufficient alone. The way to reason about any egress design is not "is egress controlled" (a yes/no that flatters every design) but "what is the minimum capability needed to bypass this control," and that question sorts the enforcement layers cleanly.

Layer Mechanism Enforcement point Bypass vector
HTTP client HTTP_PROXY env var Application Any runtime that ignores the env var
iptables / nftables Kernel packet filter Node Root in the sandbox (should not exist, but)
Kubernetes NetworkPolicy CNI plugin Pod network Misconfigured default-allow
Service mesh (Istio, Linkerd) mTLS + L7 policy Sidecar Sidecar bypass if not enforced
eBPF (Cilium, Tetragon) Kernel syscalls Syscall boundary Kernel vulnerability
Private subnet + NAT Network topology Route table Misconfigured route

Read top to bottom, the bypass vector gets harder to reach. An HTTP_PROXY env var is defeated by any process that simply declines to honor it, which is the entire content of the Node.js fetch() warning in Anthropic's deployment guide [13]: configuration-layer controls fail open against code that ignores the configuration. A Kubernetes NetworkPolicy with default-deny is real L3/L4 enforcement, but it only knows addresses and ports: it can permit "egress to 10.0.1.0/24" and cannot express "only GET, never DELETE." eBPF L7 policy (Cilium) moves enforcement to the syscall boundary and can express exactly that path-and-method distinction, at the 1 to 2.5% CPU overhead ARMO measured [15], which is the concrete reason it is the right answer when application-layer controls are bypassable by prompt injection. A private subnet with a sole egress proxy is the strongest of all because it is a topology guarantee, there is no route except through the proxy, at the cost of being a cloud-level design decision that does not travel with the pod.

DNS is a separate layer from HTTP, and it is the one most often forgotten. An allowlist of HTTP hosts is bypassable if DNS resolves those hosts to attacker-controlled IPs, or if DNS-over-HTTPS is permitted at all. DNS tunneling is a documented exfiltration channel even through sandboxes advertised as fully isolated: Palo Alto Unit 42 demonstrated bypassing the network isolation of an AWS AgentCore sandbox precisely this way [6]. This is the single strongest argument for keeping high-value credentials out of the sandbox boundary entirely rather than trusting any isolation guarantee, and it is why the placement decision and the network-policy decision reinforce each other. If a determined exfiltration channel exists through DNS, then "the credential is unreachable" is a far more durable property than "the network is locked down."

Supply chain: when the package name itself is the attack

Agents install packages, pull images, and fetch dependencies, and every one of those is a supply-chain attack surface. An agent that runs npm install on whatever the model asks for is an unusually permissive version of an already-dangerous operation, because the thing being installed is now chosen by a probabilistic model rather than a human who knows what they meant to type.

The standard controls all attach at the registry-proxy seam, which is also the sandbox fabric's sole-egress point for package traffic. A registry proxy with an allowlist (Artifactory, Verdaccio, Nexus) serves a curated subset of public packages plus private ones, and signing, scanning, and vulnerability gates hang off it. Malicious-package detection (Socket.dev [22], Snyk, Phylum) scans pre-install for typosquats, install-script exfiltration, and known indicators of compromise. SBOM generation (Syft, Trivy [23]) records everything installed, which is what makes "what did the agent actually install" an answerable question in incident response. Container image scanning (Trivy, Grype, Clair) checks the base image for CVEs continuously as new ones are disclosed. Reproducible builds, lockfiles, digest-pinned base images, deterministic build scripts, close the loop with the bootstrap-determinism axis above: the same pinning that makes recovery faithful makes the install auditable.

The agent-specific risk is categorically new. A developer running npm install foo trusts that foo is the package they have in mind. An agent running npm install foo may have been prompt-injected into installing a typosquat, or, worse, may have hallucinated the package name outright. Spracklen et al. (USENIX Security 2025) found 19.7% of LLM-recommended packages do not exist, spanning 205,474 unique hallucinated names across 576,000 samples, with open-source models hallucinating at 21.7% against 5.2% for commercial ones [24]. The attack that follows is distinct enough from typosquatting to have earned its own name, slopsquatting: register a commonly hallucinated name, then wait for an agent to install it. Because the hallucination distribution is stable across runs, an attacker does not need to guess; the model reliably points at the same nonexistent names. The Shai-Hulud npm worm (CISA alert, September 2025) operationalized supply-chain compromise at scale as a self-replicating worm that used stolen npm tokens to republish infected packages, and its second wave affected more than 180 packages and produced over 25,000 malicious repositories [25].

This reshapes the mitigation. The right defense for slopsquatting is not detection after the fact but verification before the model's output is acted on: confirm a package exists and check its provenance before the name-to-install reaches an installer. Three failure modes show why the registry proxy is necessary but not sufficient on its own. Transitive compromise: the agent installs an allowlisted package that depends on a newly compromised upstream, so allowlisting direct dependencies alone leaves the tree exposed. Build-time code execution: npm install runs install scripts, and scanning the package source does not catch what an install script does at runtime, which means the sandbox is the last line of defense, the reason the placement and isolation axes matter even when supply-chain controls are in place. Stale allowlist: an unmaintained allowlist either blocks legitimate work or keeps permitting dependencies long after they should have been removed. Every install belongs in a tamper-evident log so that "what got installed, and when" survives the incident; that durable, tamper-evident record is a persistence-fabric responsibility consumed across the seam (see the hub).

Where this leaves the human

The thread running through why Latere exists is that the most important intelligence in an autonomous system is the one you cannot see: the person who set the direction and drew the boundaries. The sandbox fabric is where several of those boundaries are physically enforced. Which isolation class an agent runs under, what it may reach on the network, whether a credential is even present for compromised code to steal, and what it is allowed to install are not decisions an agent should make for itself, and they are not decisions that should be implicit in a proxy nobody can prove is non-bypassable.

The discipline this post argues for keeps those boundaries both explicit and durable. Put the credential outside the agent's reachable set, so a DNS tunnel or a runtime escape leaks nothing worth having. Enforce egress at the syscall boundary, so a prompt injection can change what the agent wants but not what the kernel permits. Pin the bootstrap, so recovery means the same workspace and not a drifting approximation. Verify a package exists before the agent installs it, so a hallucinated name is caught before it becomes code. The agent runs at full speed inside that envelope; the authority over how wide the envelope is stays with a person. That is the same bet the rest of the platform makes, applied to the layer where the agent's code actually runs.

Latere's Cella is one implementation of this fabric; the hub maps which primitives Latere builds against and which seams, supply-chain control among them, are still open.


References

[1] E2B. "Secure Sandboxes for AI Code Execution." e2b.dev

[2] Modal. "Serverless Compute for AI Workloads." modal.com

[3] Daytona. "Architecture Documentation." Daytona

[4] Fly.io. "Sprites: Persistent MicroVMs for AI Agents." 2026. SDxCentral

[5] Anthropic Engineering. "Managed Agents." 2026. Anthropic

[6] Palo Alto Networks Unit 42. "Cracks in the Bedrock: Escaping the AWS AgentCore Sandbox." 2026. Unit 42

[7] runc. "CVE-2025-31133 / CVE-2025-52881: Container Escape via Namespace Handling." November 2025. GitHub Security Advisories

[8] Superagent. "AI Code Sandbox Benchmark 2026." Superagent

[9] Northflank. "Daytona vs E2B: AI Code Execution Sandbox Benchmarks." 2026. northflank.com

[10] "How I Built Sandboxes That Boot in 28 ms with Firecracker Snapshots." dev.to, 2025. dev.to

[11] Browser Use Engineering. "How We Built Secure, Scalable Agent Sandbox Infrastructure." 2026. Browser Use

[12] H. Chase. "The Two Patterns by Which Agents Connect Sandboxes." LangChain, 2026. LangChain

[13] Anthropic. "Securely Deploying AI Agents." Claude Code Docs. Anthropic

[14] M. Ubl, H. Arora. "Security Boundaries in Agentic Architectures." Vercel, 2026. Vercel

[15] ARMO Security. "AI Agent Sandboxing: Kubernetes-Native Enforcement." 2025. ARMO

[16] NVD. "CVE-2026-25049: n8n expression sandbox escape to RCE." CVSS v3.1 9.9. NVD

[17] S. Willison. "The Lethal Trifecta for AI Agents." 2025. simonwillison.net

[18] OWASP. "LLM01:2025, Prompt Injection." OWASP

[19] "SandboxEscapeBench: Quantifying Kernel vs Namespace Defenses." March 2026. arXiv:2603.02277

[20] Kubernetes SIG / Google. "GKE Pod Snapshots and agent-sandbox CRD." November 2025. GitHub; Google Cloud Blog

[21] X. Chai et al. "Fork in the Road: Reflections and Optimizations for Cold-Start Latency in Production Serverless Systems." OSDI 2025. Tsinghua MADSys

[22] Socket. "Socket.dev: Supply Chain Security for Open Source." socket.dev

[23] Aqua Security. "Trivy: Vulnerability Scanner." trivy.dev

[24] J. Spracklen et al. "We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code-Generating LLMs." USENIX Security 2025. USENIX

[25] CISA. "Widespread Supply Chain Compromise Impacting npm Ecosystem (Shai-Hulud)." September 2025. CISA; Unit 42. "Shai-Hulud npm Supply-Chain Attack." Unit 42

[26] Obsidian Security, SwarmSignal, Airia. "Prompt Injection in 73% of Production AI Deployments." 2025. Obsidian

[27] Wiz Research / SwarmSignal. "AI Agent Security Report 2026." 2026. swarmsignal.net; sqmagazine.co.uk