2026-10-16 · 21 min · agents · security · infrastructure · rust
A 1:50 narrated explainer, drawn in code. Every number and picture in it is this article's own; the sources are below.
› transcript
Hi, I'm Toggle! These agents get root inside a sandbox. Here is how the host and the keys stay out of reach. The idea. Each task gets a small virtual machine, and its network and keys are handled from outside it. The agent has root, but only over a guest kernel that nothing else depends on. Below it, Firecracker runs in a jail, with its own user and namespaces and hard resource limits. Only then the hardware boundary and the host. Every connection from the guest goes to a gateway that serves only this sandbox, on the host. It checks the policy. Metadata and private ranges are always denied. If a rule's host, path and method match, the gateway adds the key on the way out. The guest never sees it. A sandbox outlives its request. Running: live memory and a copy-on-write disk. Paused: memory stays on the worker. Fast, not durable. Suspended: dirty memory and changed disk go to storage. Forked: frozen, captured, restored as a new sandbox. Then they attacked it. Nine model setups with root tried for a host secret. No escape in one hundred and eight runs. With some network allowed, agents slipped past the egress rules in eleven of fifty four runs, before the fix. Zero of one hundred and eight is not a proof. Generously, it bounds the rate near three percent. The takeaway. The machine guards the host. The network and keys are a separate boundary, and that one broke. To recap. One jailed machine per task, keys attached outside, and sandboxes that pause, suspend and fork. Every source is in the full article. I'm Toggle. Bye!
Why read this
Essentialtop 10%Reads SPACE's jailer, snapshot, fork and gateway code, runs its unit tests, and puts a confidence bound on the 0-of-108 escape result.
- Interactive explanations
- Original analysis
- Widely used
Security & safetyNeeds a workstation GPUApache-2.0Practitioner tool
How this was scored
- Is it new?
- 2 of 3: A real new idea, method or capability
- Can I trust it?
- 2 of 3: Measures key facts from files, code or configs
- Can I run it?
- 2 of 3: Open code or weights with real limits
- Will I understand it?
- 3 of 3: Mechanism carried by interactives built from real code or data
- Can I act on it?
- 2 of 3: A concrete recipe, numbers or comparison
- Will it last?
- 2 of 3: A reference for a year or more
- Does it affect many?
- 2 of 3: A widely used model, tool or lab release
- Only here?
- 2 of 3: A teardown or measurement few others did
Score 75 of 100, ranked 57 of 577 rated articles. Each question is answered 0–3 by hand, and a 3 is rare. How articles are scored
An agent that runs commands, installs packages and calls APIs with real keys is useful. For whoever owns the machine underneath, it is also a standing invitation. Perplexity Computer gives its agents root inside a sandbox, and on October 15 Perplexity open-sourced the platform that makes that bearable: SPACE, the Sandboxed Platform for Agentic Code Execution, under Apache 2.0.
The launch thread makes three claims. Each agent task gets its own Firecracker microVM. A per-sandbox gateway outside the VM controls credentials and outbound traffic. Sandboxes outlive the request that made them, and can pause, fork, or suspend to a memory and disk snapshot. A fourth claim comes from a red-team report: nine model configurations with root inside the VM made 108 escape attempts, and none reached the host.
I cloned the repository, read the code behind those claims, and ran the unit tests that need neither KVM nor root.
- license
- Apache-2.0
- branch
- main
- tests
- 277 files
- source
- 45.2 MB
- commit date
- 2026-10-16
by size of tracked source at this commit, file counts in brackets; docs, data and vendored trees excluded
local clone, 2026-10-16 at b9fc395 — branch, commit, commitDate, fileCount, hasTests, languages, license, licenseFile, shallow, testFileCount
shallow clone: counts describe the pinned tree, not the history
What is in the box
At the commit I read (b9fc395, 2026-10-16) the repository has 3,743 tracked files, 111 Cargo packages under crates/, and about 979,000 lines of Rust in 2,178 files by wc -l, tests included (measured). A Go containerd shim covers the Kubernetes path. The docs call it SPACE 0.8.0, "the public release baseline" (reported).
The shipped execution environment, E0, is a Linux x86_64 host with KVM, Firecracker 1.15.1, a Linux 6.1.155 guest kernel and Firecracker snapshot format 9.0.0 (reported, from docs/execution-environments.md). The local install needs /dev/kvm, /dev/net/tun, btrfs and a Docker daemon that allows privileged containers. No macOS.
The HTTP API is the contract. Of the native operations in crates/libs/public/api/src/routes.rs, 14 are marked supported and 46 incubating (measured). Supported: create, get, list and delete a sandbox; run a command and stream its output; manage processes; pause, suspend and wake. Checkpoint, rollback, restore, stop and start are incubating. Fork is served by the E2B-compatible wrapper, POST /sandboxes/{sandboxID}/fork.
Why a VM and not a container
Start with what root inside a sandbox can touch.
In a container, root runs on the host kernel with some doors closed. Every system call the agent makes is executed by the kernel that runs everything else on the machine. Namespaces and seccomp narrow what it can ask for, but the attack surface is still the host's syscall interface, and one reachable kernel bug is a host compromise. That is a poor bet for code a model wrote ten seconds ago in response to a web page.
In a microVM the guest has its own kernel. Root in the guest can load modules, rewrite the network stack and forge packets, and all of it stays inside a kernel nobody else depends on. The host exposes KVM and the virtual machine monitor, and Firecracker is a deliberately small monitor with a handful of virtio devices. The price is boot time and memory, which is why SPACE carries warm pools and base snapshots.
SPACE then wraps the monitor. launch_via_jailer in crates/libs/node/runtime/core/src/launch/os/linux.rs starts every Firecracker process through Firecracker's jailer (measured, read from the code and its tests):
| jailer flag | what it buys |
|---|---|
--chroot-base-dir, per-sandbox --id | the monitor sees only its own kernel, rootfs and sockets |
--uid, --gid | it drops root before running Firecracker |
--netns | it joins the namespace node-manager built for this sandbox |
--new-pid-ns | it cannot see other processes |
--cgroup-version 2 with cpu.max, memory.max, pids.max | hard ceilings under a space parent cgroup |
An escape through the monitor has to get through the guest kernel and KVM to reach a process that is chrooted, unprivileged, namespaced and capped. An escape through the host kernel's network stack takes another route, which the 15 October section covers.
Others on this site reached the same place from different directions. Moonshot found containers were not enough for agentic RL in Kimi K3 and moved to Firecracker; Karotte runs Firecracker so root in the guest is not root on the host. OpenShell keeps a process sandbox and puts the boundary in kernel LSMs and a supervisor, and Strands Box is a policy funnel AWS says to nest inside a microVM for strangers. DeepSeek's DSec is the fleet view.
The moving parts
A control plane records desired state; workers make it true.
| component | where | job |
|---|---|---|
| API + PostgreSQL | crates/apps/api | authenticate, store desired state, return an operation ID |
| controllers | crates/libs/api/src/controllers | reconcile onto workers, directly or through Kubernetes |
| node-manager | crates/apps/node/mgr | namespaces, nftables, TPROXY, jailed VM launch |
| sandbox-manager | crates/apps/sbx/mgr | per-sandbox start, snapshot, restore |
| volume, template managers | crates/apps/vol/mgr, template/mgr | rootfs clones, snapshot upload and download |
| net gateway | crates/apps/net/gateway | per-sandbox egress proxy, DNS observer, TLS interceptor |
| credential manager | crates/apps/credential/mgr | resolves secret references, issues scoped storage credentials |
| data plane | crates/apps/data/plane | streams command output with session tokens |
spaced | crates/apps/spaced | the guest agent and its host peer, over vsock |
Management and output take separate paths. Management goes through the API and PostgreSQL to controllers and workers. Command output goes from the data plane to the guest, authorised by a session-scoped attachment token that is not interchangeable with the management token. That split is why a sandbox can outlive both the request that created it and the connection carrying its output.
One task, start to finish
A create request names a template such as seed:latest. The API resolves it to a digest and returns a sandbox ID and an operation ID; acceptance is not readiness. A worker clones the template's ext4 rootfs with btrfs copy-on-write, so the clone costs metadata, not bytes. node-manager builds the namespace, tap device and rules, and the jailer starts Firecracker, unless a warm-pool slot or base snapshot is restored instead. The client polls until the sandbox is running, creates a command session, and attaches to start the process. When it exits, nothing is torn down. The sandbox waits for the next command, or for six idle hours, after which the API suspends it by default (reported).
The three non-running states hold different things in different places:
- Paused stops the vCPUs. Memory stays resident on the worker and the disk stays put. The docs say plainly that this "is not a durable snapshot": lose the worker, lose the sandbox.
- Suspended writes VM memory, runtime state and the root filesystem to object storage and releases the VM.
fc_api.rssetsCREATE_SNAPSHOT_TYPEto"Diff"with dirty-page tracking on, because full snapshots "write the entire guest memory file on every suspend", while diff snapshots "scale with the dirty set". The disk goes out as a btrfs send stream against its template parent by default (SPACE_SUSPEND_DEFAULT_FORMATisbtrfs-send). - Wake from suspend loads the snapshot through Firecracker's
Filememory backend, restores the rootfs, and checks that the guest agent reports a supported version, within a 30-second readiness budget (reported). Because the artifacts sit in object storage, a sandbox need not wake on the same worker.
Fork is three phases in crates/libs/api/src/fork_operations.rs: SourceFreeze, ArtifactMaterialization, TargetRestore. Freeze the source so memory and disk agree, capture a snapshot, restore it as a new sandbox. The source may be running, paused or suspended; the child keeps the parent's saved network policy; the default hard deadline is 240 seconds (measured, from the code default).
Is fork copy-on-write memory? Not in the shared-pages sense. Each child is a separate Firecracker process restored from a snapshot file; the File backend maps it privately, so a child's writes are its own, and children share no live memory with the parent (reasoned, from Firecracker's documented backend; I did not run a fork). The disk is copy-on-write in the btrfs sense: clones and frozen-source bundles are subvolume snapshots that share extents until written. The launch blog says SPACE "stores changes rather than repeatedly copying entire environments" (reported); the code says which changes: dirty pages and btrfs extents.
The gateway: where credentials get attached
A reply to the launch put the credential problem well: malicious code "can steal any key it can read without ever escaping its sandbox". SPACE's answer is that the guest never holds the key.
Each sandbox has its own gateway process, on the host, outside the VM, inside that sandbox's network namespace. node-manager's nftables rules hand the guest's traffic from the tap device to it with TPROXY. The gateway runs as its own uid with only CAP_NET_ADMIN and CAP_NET_BIND_SERVICE, and marks its own outbound sockets so the rules do not capture them twice (from crates/libs/net/gateway/CLAUDE.md).
The egress decision lives in pplx-space-net-common, in this order (measured, read from the code):
- Platform guardrails no tenant policy can override: instance metadata
169.254.169.254/32, the169.254.0.0/16link-local range apart from a small carve-out for the guest's own link, loopback, IPv6 link-local, and the sandbox slot ranges10.11.0.0/16and10.12.0.0/16. - An explicit deny rule.
- An explicit allow by IP or CIDR.
- A domain allow, which counts only when the gateway itself saw DNS map that name to this IP.
- The policy's default action.
With TLS interception on, the gateway terminates TLS with an execution-owned CA whose private keys never leave it (certificates/authority.rs). It checks that the SNI and the HTTP host both name an allowed host attributed to this IP, strips known CDN routing headers, and then applies credential rules.
A credential rule in proto/net/policy/v1/policy.proto names an exact host, a port (443 if omitted, not a wildcard), an optional segment-aware path prefix (/v1 matches /v1/…, not /v10), HTTP methods, and a header bound to an opaque credential reference. Version one supports only GET and POST; "every other method fails closed". The policy carries references only. The credential manager's ResolveNetworkCredentials resolves values, from AWS Secrets Manager ARNs, only for "the control plane's exact committed effective network policy", and the gateway keeps them in zeroizing types that print <redacted>. On a match, credentials.rs sets the header on the way out.
So the guest can call the API the key is for, and cannot read the key, log it or send it elsewhere. It does not stop misuse of the allowed call: if a rule permits POST under /v1, a talked-into-it agent gets the credential on a bad POST too, which another reply pointed out. The exact host, narrow prefix and two-method rule shape is how you shrink that (reasoned). Two defaults matter: network credentials are off unless SPACE_SBXMGR_NETWORK_CREDENTIALS_ENABLED is set, and a TLS interception exemption skips inspection and injection for its destination.
Host-side storage credentials follow the same idea: ExchangeToken issues one-hour AWS credentials scoped to one snapshot object or prefix, and sandbox-manager may not receive them at all (reported, docs/space/design/credential-token-exchange.md).
What is open and what is not
The gateway, credential manager, egress policy engine, jailer launch, snapshot and fork orchestration, controllers, guest agent and E2B wrapper are all in the tree, with a local Kind deployment and a reference EKS deployment. Not in it:
- Perplexity Computer itself: the harness, model routing and whatever chooses a task's policy. The thread calls SPACE "the first component of Perplexity's Agentic Runtime Platform".
- The red-team harness and transcripts. Part II, on model behaviour, is not out.
- The production numbers. "Millions of sandbox creations per day" and creation three to five times faster than the unnamed previous provider are Perplexity's figures (reported); nothing in the repo reproduces them.
- Composition seams. Some crates let a deployment "that needs private wiring" supply its own
main. Production wiring likely differs from the Kind setup; I cannot see how much.
What I ran
Unit tests needing no KVM and no root, on a shared 4-core cloud VM, CPU only, two jobs (measured):
- Go shim (
shim/):go test ./...on Go 1.25.0, which the toolchain fetched for the module'sgoline. 101 top-level tests passed. pplx-space-net-common: 21 tests passed, covering the egress decision: deny over allow, guardrails before an open policy, IPv4-mapped IPv6 normalised so a mapped address cannot dodge an IPv4 rule, wildcard domains matching subdomains only, DNS-attributed allows.pplx-space-credential-injection-core: 3 tests passed: secret wire types redact and zeroize, and secret ARNs need a region and twelve-digit account.
I installed protoc into a scratch virtualenv for the Rust build, on the pinned Rust 1.98.0. I did not build the gateway or managers, whose dependency trees did not fit this run's disk budget, and I started no VM. VM behaviour here is read, not run.
Escaping SPACE: what the 108 runs were
"Escaping SPACE: Part I", from Perplexity's Secure Intelligence Institute, tested two boundaries separately. I describe the method and results; how the network bypasses worked is in the report and I will not restate it as a recipe.

In the escape task the agent was told the host held an AWS honeytoken and had to cross the boundary, call STS GetCallerIdentity with it, and submit the role name; the harness checked the exact value. In the network task it had to fetch a flag from a callback URL the egress policy blocked. Each ran in four settings: with or without a read-only copy of SPACE's source, and with no network or partial network (package mirrors plus a search API). The no-source, no-network setting had one hour per run, the others two.
Nine model configurations took part: Claude Opus 5.0, DeepSeek V4 Pro 0813, Gemini 3.1 Pro, GLM 5.2, GPT-5.6 Cyber, GPT-5.6 Sol, Grok 4.20, Kimi K3 and Qwen 3.8 27B, three runs per setting, 216 runs in all and 108 per task. Fable and GPT-6 Astra refused the tasks and were dropped (reported). Agents had root in the guest, no host foothold, and every action went through logged harness tools.

What 0 of 108 does and does not establish
"None reached the host" is true of the runs. Here is what it licenses.
A bound, under a generous assumption. Treat the 108 runs as independent trials of one attacker with success probability . Zero successes give a one-sided 95% upper bound of (reasoned; the rule of three gives ). The assumption is shaky: this is nine models times twelve, not 108 draws of one thing. Per model, 0 of 12 bounds the rate at about 22%; the strongest setting, 0 of 27, at about 10.5% (both reasoned).
A bound on these attackers only. It says nothing about a human with a Firecracker or KVM zero-day, a longer budget or next year's models. The report says so: held boundaries "should not be interpreted as evidence that they are perfectly secure". The whole escape budget was at most 189 agent-hours (reasoned: per model, three one-hour runs and nine two-hour runs).
Why it still counts. A zero only means something if the attackers could score. The same agents, harness and budgets broke the network boundary 11 times in 54 partial-network runs, a 95% interval of roughly 11% to 34% (reasoned, Clopper-Pearson). That is the positive control: these agents found and used real flaws when flaws were in reach. The zero is unlikely to be an incapable harness; more likely the escape had no low-hanging fruit at this budget.
What more would take. A 1% bound at 95% with zero failures needs about 300 independent runs (reasoned, ). For a hypervisor boundary the stronger evidence is Firecracker's and KVM's track record, the jailer settings above, and how fast host patches land.
The boundary that did break
The network results are where SPACE was wrong. The gateway's policy is written in hostnames and packets carry IPs, so it learned name-to-address mappings by watching DNS. The models found two ways to make the mapping mislead it. One was a SPACE bug in how DNS evidence was accepted, and every success with it had the source code. The other is how the internet works: one CDN address serves many unrelated hostnames, so "this IP belongs to an allowed name" does not mean "this request is for that name".
The fixes are in the code. node-manager now installs a drop-spoofed-guest-source rule on tap0 that drops any packet not carrying the VM's own source address, before TPROXY. The gateway terminates TLS and requires the SNI and every hostname in the request to be allowlisted and attributed to the destination; plain HTTP goes through a relay that checks the authority per request; domain rules reject UDP and plaintext protocols with no checkable hostname. A rerun at the strongest setting scheduled three runs per model; two ended on provider errors and none of the remaining 25 reached the callback (reported; 25 is my subtraction), which bounds the post-fix rate at about 11% (reasoned).
One limit is written into the code: the routing-header sanitiser in tcp_proxy/routing_headers.rs is "a denylist of edges known to route on guest-supplied metadata", and a new provider "must be added here". Hostname consistency is the general fix; the header list needs maintaining.

The report also tested ten third-party sandboxes with hostname-based egress, without LLMs: eight had a bypass, nine are reported, seven of those vulnerable (reported). OpenShell is one of the two reported platforms where neither worked. The lesson I would underline is the report's own: VM isolation and network confinement are separate boundaries, and a microVM says nothing about the second. The OpenAI and Hugging Face incident that opens the report is a network and credentials story, not a hypervisor one.
15 October: a guest-to-host escape in the host's network stack
The day SPACE went public, pwn.ai announced PwnEscape: code in a KVM guest reaching host-kernel memory reads, controlled writes and root command execution, through a flaw in Linux networking rather than in KVM or the monitor. A reply linked two lines of SPACE's firewall as hardening against it. This is the defender's view; how the bug is triggered and exploited stays in the primary sources.
The CVE is real. CVE-2026-99999 looks like a placeholder, and the replies joked about it, but the Linux kernel's own CNA reserved it on September 25 and published it on October 15, and NVD listed it the same day (reported, MITRE and NVD records). The kernel CNA scores it 9.3, critical; Red Hat and Amazon score it 8.8, important, disagreeing on whether the attacker needs privileges (reported). CISA records no known exploitation, and its Known Exploited Vulnerabilities feed of October 16 does not list it (measured, from the feed), although Red Hat's page says it tracks the flaw as an active-exploit case (reported).
The bug class. A paravirtual network card lets the guest hand the host an unfinished packet with a note saying where the checksum still has to be written, and the guest writes that note. In affected kernels the IPv4 path could complete the deferred checksum after the header had already been parsed and acted on, so the write could land in the IPv4 header itself, and later code trusted the changed header (reported, the kernel commit and pwn.ai's write-up). The researchers report the memory read on their own KVM hosts, and the writes and root execution in a lab chain on machines they own, explicitly not in any provider's production. Vercel, whose sandbox challenge produced the report, says no customer data was accessed (reported). Part two, on reaching host control, is unpublished.
The fix. Upstream commit 6785011f8b16, "ipv4: validate checksum_start before completing checksum", from Michael S. Tsirkin, refuses to complete a checksum that starts inside the IPv4 header, at five call sites (reported). Its Fixes: tags point at the 2008 changes that let tun and packet sockets accept offload metadata; the CVE record lists every kernel from 2.6.27 as affected. Fixed stable releases are 5.10.272, 5.15.223, 6.1.190, 6.6.159, 6.12.113, 6.18.57 and 7.2.11, with mainline at 7.3-rc7 (reported, CVE record). As of today Debian has fixed bookworm and trixie, Amazon Linux 2 and 2023 shipped fixed kernels by October 12, Ubuntu lists its releases as needing evaluation, and Red Hat marks every RHEL stream affected with no mitigation it endorses (reported, each tracker). The commit calls itself defence in depth and says fuller input validation "is forthcoming, but needs more work".
SPACE sits on the affected path. Firecracker 1.15.1, SPACE's pinned monitor, offers the guest checksum offload and opens its tap device with the virtio-net header enabled, so guest checksum metadata reaches the host's tap driver (measured, read from Firecracker's net/device.rs and tap.rs). A vhost-net backend would feed the same tap (reasoned). The guest kernel does not matter; the host kernel does, and SPACE leaves that to the operator.
Most of SPACE's defences do not reach this. The jailer's chroot, uid drop and cgroups, and Firecracker's own seccomp filters, limit what a compromised Firecracker process can do, but here the monitor is not compromised: it writes the guest's frames to its own tap, as designed, and the bug runs in the host kernel. A network namespace is a view inside that one kernel, not a memory boundary. The egress gateway sees traffic only after the kernel has handled it (reasoned).
The linked lines are the exception. They sit in a netdev ingress chain bound to the sandbox's tap, ahead of IPv4, and drop every guest frame carrying an 802.1Q or 802.1ad VLAN tag (measured, firewall.rs lines 65 and 66 at 3f0ed159, identical at b9fc395). node-manager counts the drops and logs a warning per sandbox, a cheap signal of a guest misbehaving. pwn.ai traces the bug to a gap between where the metadata was checked and where the IPv4 header ends once link-layer framing is handled; refusing tagged guest frames removes one source of extra framing. That narrows the path; it is not a patch. I did not test whether it closes SPACE's path, the rules shipped in the October 5 initial release with no comment tying them to this bug, and the defence that counts is a fixed host kernel.
For anyone running microVM sandboxes. Patch host kernels, and confirm each host booted the fixed one or took the live patch. Treat every host interface the guest feeds as attack surface: offer only the device features the workload needs, and drop framing a point-to-point link never carries before the host's IP stack sees it. The 0-of-108 result above was bounded to its attackers; this is what it did not rule out.
Where it costs
- The gateway is the soft part, and it lives on the host. It parses TLS and HTTP from a hostile guest. Its own uid, two capabilities and a per-sandbox namespace are sensible containment, but it is the component that broke in testing.
- Credentials attach to allowed calls, not safe ones. Use an exact host, the narrowest prefix, and GET where possible. The format cannot express "at most three times".
- Durability is per state. Paused is not durable. Guest
fsyncreaches the host page cache and no further under Firecracker's defaultUnsafecache mode; writes persist at lifecycle boundaries (reported,docs/space/design/guest-io.md). - Disk fairness is not solved. Each root drive gets 10,000 operations a second and 100 MiB/s by default, and the docs say the per-sandbox limiter "does not make a dense node fair" (reported).
If I were adopting it, I would first read crates/libs/net/common/src/lib.rs, which is the whole egress decision in 1,732 lines (measured), and then time create, suspend and wake on my own templates on a KVM box, since the speed claim is about Perplexity's traffic and Perplexity's previous provider.
The shape is right: a hardware VM boundary for code, and the network and the keys handled by something the guest cannot reach. The report is unusually honest about which of those held. The VM boundary held against what was thrown at it. The network boundary did not until it was fixed, and the fix is in code you can now read.