The Sandbox Didn't Zero the Disk
A paying customer opened their own container’s root disk at /dev/vdc and read bytes that belonged to somebody else’s container: directory structures, database pages, structurally complete SQLite databases, Chromium profiles, .env files, credential files. No privilege escalation, no memory corruption, no hypervisor escape. The technique was to write 4 KiB into a region of filesystem free space, then read the 64 KiB storage block that write had just been handed.
Cloudflare described it on September 24, 2026 in How Cloudflare addressed a cross-tenant data exposure vulnerability in Containers, written jointly with Oren Yomtov and the Accomplish research team, who reported it on September 4 through Cloudflare’s HackerOne program and were paid a bounty ten days later. Cloudflare Containers, Cloudflare Sandboxes (which is built on Containers), and per Accomplish the same disk implementation under Browser Run were all affected. No customer-side changes were required after the fix.
The interesting part is which boundary held and which one leaked. The compute boundary held. Every container runs inside a dedicated Firecracker VM, and the storage pool below it was where tenant data actually crossed accounts.
One flag in the storage pool
The architecture, from the Cloudflare writeup, is short enough to hold in your head. Each container gets a writable root disk from Linux device-mapper thin provisioning (dm-thin), presented to the guest as /dev/vdc. Affected pools used a 64 KiB thin-block size, and those physical blocks were returned to a pool shared by workloads belonging to multiple customer accounts when a disk was deleted. The pool configuration included one option:
skip_block_zeroing
dm-thin has a default behavior worth stating explicitly, because the whole exploit is a consequence of losing it: when a thin block is allocated to your volume, it is normally zeroed first. With skip_block_zeroing set, that step is skipped. A full-block write still replaces the previous contents. A partial write does not. The rest of the block keeps whatever the previous owner left there.
There is a second detail that makes this feel less like luck and more like arithmetic. Reading an unmapped region of a new thin device returns zeroes without allocating a physical block at all. You cannot read free space and hope. You have to force the allocator to hand you a recycled block first.
The proof of concept did exactly that:
- Create a container on a Workers Paid account, open
/dev/vdc. - Read the disk and record a baseline.
- Locate 64 KiB-aligned regions corresponding to free space in the guest’s ext4 filesystem.
- Write exactly one aligned 4 KiB block into each region.
- Read those blocks again and examine only the portions not overwritten.
Each 4 KiB write triggered allocation of a reused 64 KiB block, filled 4 KiB of it, and left up to 60 KiB of someone else’s data readable through a raw device read. Cloudflare’s post, which is unusually clear about the mechanics, describes the outcome directly: “A subsequent raw-device read could therefore observe bytes that the new container had never written.”
Proving cross-tenant exposure without reading anyone’s files
The interesting methodological problem is that proving this class of bug normally requires demonstrating the leak, which means handling other people’s data. Accomplish avoided that using ext4’s own integrity metadata. When metadata_csum is enabled, directory block checksums incorporate filesystem and inode values, so a block can be attributed to the filesystem that created it without reading its contents.
Across six production placements, the researchers examined 5,614 testable directory blocks. Zero of them belonged to the filesystem they had created for the test. Checksum analysis identified 2,700 distinct foreign directory inodes. They validated the method against 162 blocks they had deliberately created and deleted in their own test filesystem, and the method attributed all 162 correctly.
The aggregate finding is the sentence worth carrying out of this story. Residual material was observed on 18 of 24 placements and 20 of 22 underlying nodes across four continents, and the recovered block types included directory structures, database pages, and structurally complete SQLite databases. This was not a theoretical window. On most tested hosts, some previous tenant’s bytes were reachable.
Cloudflare is explicit about what was not shown: an attacker could not select a particular victim, workload, host, or data, could not access an actively attached disk, and could not modify another customer’s active data or affect availability. Exposure depended on placement and on which released blocks the allocator happened to reassign.
The fix was two fixes
The first mitigation is what you would expect: remove skip_block_zeroing from the dm-thin pool configuration across the fleet, restoring the default zeroing of newly allocated blocks. The researchers independently confirmed their proof of concept stopped working on September 14.
The second mitigation is the part that generalizes, and it is the part most teams would miss.
Zeroing new allocations does not sanitize blocks that are already mapped into existing thin devices. At the time of the fix, those mappings existed in two places: running container disks, and each host’s cache of prepared dm-thin snapshots backing OCI image layers. A new container could inherit mappings from a cached layer without ever allocating those blocks again, which left the residual bytes in unused regions, ext4 free space included, still readable through a raw read of /dev/vdc.
So the fix required replacing the artifacts, not just the setting. Cloudflare retired all running container disks and removed cached image snapshots created before the mitigation, draining hosts during off-peak hours, restarting the VMs on each host, and clearing each host’s image cache so disks and layers were recreated from zeroed allocations. The rollout of the config change completed on September 7; the fleet-wide cleanup of pre-mitigation cached snapshots completed on September 19.
| Time (UTC) | Event |
|---|---|
| Sep 4, 15:26 | Yomtov reports the issue via HackerOne |
| Sep 4, 18:45 | Cloudflare opens a security incident and confirms the production setup |
| Sep 4, 21:27 | Runtime fix and reuse test merged |
| Sep 4, 23:15 | Rollout begins; completes Sep 7, 06:13 |
| Sep 14, 10:50 | Researchers confirm the proof of concept no longer works |
| Sep 14, 12:52 | Bounty awarded |
| Sep 19, 15:03 | Cleanup of all pre-mitigation cached snapshots complete |
Cloudflare also mined retained disk I/O telemetry for the signature of the technique, the characteristic relationship between small writes and larger reads. Only the researchers and Cloudflare engineers doing authorized validation matched it. There is no evidence anyone else exploited it. That last claim has a boundary worth stating plainly: it covers the historical telemetry available to Cloudflare, which is not the same as a proof that no one ever read a stray block.
What an agent operator should change
The recovered artifacts are the interesting list, because they are the things agent sandboxes are built to hold. .env files, credential files, SQLite databases, Chromium profiles. Every one of those is something a coding agent or a browser agent writes into guest storage as a matter of routine.
- Treat the guest filesystem as non-persistent public memory. If your platform’s storage pool can recycle an uncleared block, then anything your sandbox writes may outlive the sandbox and be readable by a later workload on the same host. Do not bake durable credentials into the image or into files the agent writes. Cloudflare’s own outbound-traffic guidance points the right way: keep credentials in the Worker and inject authorization headers on the way out, so the guest never holds the long-lived secret at all.
- Do not read “the sandbox was deleted” as “the bytes are gone.” Thin provisioning is a recycling system, and zeroing is the only thing that makes a recycled block safe to hand to a different tenant. If you cannot show that your provider zeroes allocations at the pool layer, or encrypts with keys the next tenant cannot reach, assume the residual is there.
- Ask the storage isolation question at vendor selection. VM isolation and data isolation are separate properties with separate failure modes, and only one of them shows up in a sandbox marketing page. The questions that matter: what is the thin block size, is block zeroing enabled, what happens to the snapshot cache behind your image layers, and when you patch the pool config, what happens to blocks that were already mapped.
- When you fix a default, rotate the artifacts that inherited it. This is the transferable engineering lesson. A configuration change remediates future allocations. Everything already mapped into live devices and cached layers keeps the old behavior until it is recreated.
The boundary you test is not the boundary that leaks
Three days before this disclosure, Perplexity published its own sandbox red team: nine frontier models, 108 escape attempts against a Firecracker boundary, zero successes, and four models walking past a domain-based egress policy without breaking anything. Cloudflare Sandbox appears in that test’s third-party table as no bypass demonstrated. It passed the network test and leaked data at the storage layer in the same month.
This is Accomplish’s sixth published sandbox escape since July 2026, after SharedRoot in Claude Cowork, Beltdown and Beltdown2 in Claude Code and the Cursor CLI, OpenAI Codex’s sandbox on September 15, and Docker’s hypervisor on September 19. The pattern across all six is not “vendors are careless.” It is that isolation is a chain of independent properties, and teams test the link they own and can model. The hypervisor is a named product boundary with a threat model. A thin-provisioning option in a storage pool is a line in a config file, and nobody writes a test for a line in a config file.
The uncomfortable truth here is that the sandbox was doing what it said on the label, and the data still crossed between tenants. Firecracker is not the failure. The control plane is not the failure. The failure is that a recycled 64 KiB block is only safe if someone zeroes it, and nothing in the architecture was checking. If a block can be reassigned without being cleared, your isolation model has a hole below the layer you are defending, and it will not show up in any escape test you run against the VM.
Sources:
- How Cloudflare addressed a cross-tenant data exposure vulnerability in Containers, Cloudflare Blog, Sep 24, 2026 (primary: dm-thin 64 KiB blocks and skip_block_zeroing, Firecracker VM and /dev/vdc, the 4 KiB write then raw read technique, checksum attribution counts, 18 of 24 placements and 20 of 22 nodes, two-part remediation including snapshot cache retirement, telemetry analysis, full disclosure timeline Sep 4 to Sep 19)
- Escaping the Cloudflare sandbox, Accomplish Research Blog, Sep 24, 2026 (primary: reported Sep 4, affected Sandboxes and Browser Run, recovered artifact types including .env and credential files, joint writeup, sixth escape since July 2026)
- HackerOne report 3997565, Cloudflare program, filed Sep 4, 2026 (primary: disclosure record)
- Cloudflare Sandbox SDK documentation, developers.cloudflare.com (primary: product scope and isolation claims, Workers Paid availability, AI code execution use case, outbound traffic credential-injection guidance)
- Cloudflare Containers documentation, developers.cloudflare.com (primary: multi-tenant container runtime, no host selection)
- Escaping SPACE: Part I, Perplexity, Sep 23, 2026 (primary: 108 VM escape attempts with zero successes, domain-based egress bypasses, third-party platform table)
- @_orcaman (Or Hiltch) on X, Cloudflare Containers disclosure, Sep 24, 2026 (primary: disclosure thread; 596 likes, 62 reposts, 77.9K views at capture)