The free space was not empty
On September 24 Cloudflare and Oren Yomtov of Accomplish published a cross-tenant flaw in Cloudflare Containers and Sandboxes. Write 4 KiB into a fresh sandbox, read the raw disk, and the other 60 KiB of the block came back holding the previous tenant's .env files, credential files and Chromium profiles. Nothing escaped anything: every live boundary held, and the leak was in what happened to a block after its owner was done with it. Cloudflare sells Sandboxes to run code generated by language models. Bromure Agentic Coding runs that code from an image file on your own Mac, and the free blocks it draws from are yours.
A sandbox has two jobs. Contain what runs inside it, and forget what ran inside it before. The second one is a configuration option.
You send the agent's test suite to a cloud sandbox, because running unreviewed
code on your laptop stopped feeling clever some time last year. The sandbox comes
up in about a second with a full Linux filesystem. The agent writes a small file.
Underneath, the platform hands it a 64 kilobyte block of disk, and 60 of those
kilobytes still hold somebody else's .env.
You did not have to break out of anything to get there.
Write four kilobytes, read sixty
On September 24 Cloudflare published a joint write-up with the researchers of a cross-tenant flaw in Cloudflare Containers. Oren Yomtov of Accomplish reported it on September 4 through HackerOne and published his own account the same week. The Hacker News covered it the next day.
Containers give each customer a disk out of a shared pool, using Linux
device-mapper thin provisioning, with a block size of 64 KiB. The pool carried the
skip_block_zeroing option, which does what it says: dm-thin did not clear a
newly allocated block before handing it to a container. When a container was
deleted its blocks went back to the pool, contents intact, and the pool spanned
customer accounts.
From there the technique is four steps and no exploit:
- Find 64 KiB-aligned regions that the container's own ext4 filesystem thinks are free.
- Write a single 4 KiB block into each one. That is enough to make dm-thin allocate a physical 64 KiB block from the shared pool.
- Read the raw device rather than the filesystem.
- Keep the other 60 KiB.
Cloudflare's own sentence for the primitive is the whole bug: "a full-block write replaced its previous contents, but a smaller write changed only the written portion."
The recovered blocks held usable structure. Cloudflare reports "directory
structures, database pages, and structurally complete SQLite databases", and
counts 2,700 distinct foreign directory inodes across six production
placements, with residual material on 18 of 24 placements. The Hacker News reports
data recovered on 20 of 22 underlying machines across four continents. Yomtov
lists what he read off those blocks: "directory listings, SQLite databases,
Chromium profiles, .env files, and credential files."
Cloudflare states two limits that bound the incident. An attacker "could not select a particular victim or access an actively attached disk", so you got whoever had been on that machine rather than a target you chose. And Cloudflare built detection signatures from the researchers' proof of concept, ran them against historical disk-activity telemetry, and found "no evidence that this specific attack vector was exploited by anyone else."
Cloudflare moved fast. Reported 15:26 UTC on September 4; production misconfiguration confirmed at 18:45; runtime fix merged at 21:27; pool configuration merged at 22:03; rollout started at 23:15. All of that inside one day. The rollout finished on September 7, the researchers confirmed their proof of concept was dead on September 14, and Cloudflare cleared the last pre-mitigation disk snapshots on September 19. Customers had no action to take, and no way to check from inside a sandbox whether their own blocks had arrived clean.
Every live boundary held
The title on Yomtov's write-up, "Escaping the Cloudflare Sandbox", undersells how quiet the bug is.
He found no memory-safety bug, no privilege escalation and no container breakout. Every boundary around a running container held as designed for the duration of the incident. Customer B could not read customer A's live container, and Cloudflare notes that a sandbox could not reach an actively attached disk either.
The leak sat in the handover. Cloudflare released a block from one tenant and issued it to the next, and the thing standing between those two customers was a boolean in a pool configuration.
Researchers spend their attention on what the code inside a sandbox can reach right now, and much less on what the last occupant left behind. That second question has a boring answer most of the time: the platform zeroes the block. The answer holds until an engineer turns the zeroing off for throughput.
Accomplish counts this as its sixth published sandbox escape since July 2026. The previous five: SharedRoot in Claude Cowork, Beltdown in Claude Code, Beltdown2 in Cursor's CLI, one in Docker's VMM, and one in OpenAI's Codex sandbox. Five of those six run code an agent wrote. Yomtov's own conclusion runs one line: "isolation can fail in many ways."
This is where agent code was supposed to go
Cloudflare Sandboxes run on Containers, so they inherited the bug. Sandboxes are also the product Cloudflare sells for the thing this blog keeps writing about. From Cloudflare's own documentation, the SDK exists to "run untrusted code securely in isolated environments", it is for "Execute code generated by Large Language Models safely and reliably", and it suits "AI agents, code assistants, and autonomous systems that need to run untrusted code." Each sandbox runs "in its own isolated container with a full Linux environment, providing strong security boundaries while maintaining performance."
Every clause of that is a reasonable thing to want, and it describes the Bromure workspace VM about as well as it describes a Cloudflare Sandbox. The difference is who else is on the machine.
Moving agent execution off your laptop relocates a trust decision rather than removing it. You now trust a pool allocator you cannot see, in a placement you did not choose, beside tenants nobody will name for you. That can be the right trade. Make it on purpose, and ask the residue question first: where do my free blocks come from, and what was in them.
The disk is a file on your Mac
Bromure Agentic Coding answers that question by not having a pool.
Each workspace maps to exactly one VM. Two workspaces never share a VM, and one
workspace never has two. The VM boots from two virtual disks that are ordinary
files in that workspace's own directory on your Mac. disk.img is the system
disk, created on first launch as an APFS copy-on-write clone of the local Ubuntu
base image: instant, and free until the guest writes. home.img is a private
sparse ext4 image holding /home/ubuntu, attached as a second virtio-blk device.
The four-step technique has nowhere to land here. An agent in the guest can read the raw block device instead of the filesystem, since it is root in there, and that is fine. It reaches one image file belonging to one workspace. The blocks that file grows into come from your Mac's own APFS volume, and the previous occupant of a free block on your Mac was you.
One object is shared between workspaces, and "nothing is shared" would be the
wrong claim, so name it: every workspace disk is cloned from base.img. Ubuntu
24.04, 24 GB logical and 6 to 8 GB physical, fetched against a signed image catalog
carrying an ed25519 signature made with the same key that signs app updates,
covering the image identity, its SHA-256 and every postinstall command, with
rollback protection and refusal of unsigned catalogs. It is byte-identical on every
install and contains nothing of yours. That is the right kind of thing to share: a
public artifact whose job is to be identical everywhere.
And the residue would be worthless
The block layout is half the answer. The other half is what a coding agent writes
to disk, which is what made this bug worth reporting: .env files, credential
files, Chromium profiles.
Inside a Bromure workspace those files exist, and the tools that read them work as
usual, but the values in them are fake. No file, environment variable or
process inside the VM ever holds a real API key, OAuth token, AWS secret or SSH
private key. The guest gets structure-preserving placeholders instead:
sk-ant-api03-brm-…, a ghp_-shaped GitHub token, brm_…, deterministic per
install, so client-side validators are satisfied and no tool ever thinks a key
rotated between sessions. The real values live in the host proxy's in-memory swap
map on the Mac, each keyed to the destination host it belongs to, and get
substituted on the way out with scoping that is exact-or-subdomain, never
substring. AWS requests are re-signed host-side: the guest signs with a fake
secret, the host strips that signature and re-signs. SSH is signature-only over a
vsock bridge, and private key bytes never cross into the guest at all.
Bromure handles the Chromium profile separately. The browser pane the agent drives runs in its own ephemeral Alpine VM, a copy-on-write clone in the temporary directory that the app deletes when you tear the pane down. Keeping logins is opt-in, and it puts the profile on an encrypted per-workspace disk.
If some future flaw exposed a Bromure guest's blocks, the reader would come away with a directory tree, source code you already have, and a credential file that cannot be spent anywhere.
Disposability as a verb
Workspaces are persistent on purpose. Coding needs durable state: cloned repos, package caches, toolchains, shell history. So Bromure controls what the VM is allowed to hold rather than destroying it nightly, and erasing a disk is something you ask for. The app is loud about each way of asking:
Reset to base
Deletes disk.img so the next launch re-clones a fresh copy of the base
image. The dialog names what goes: everything you apt installed, and your
edits to /etc, /var, /usr, /opt. Home survives.
Erase home, delete workspace
Erase home drops /home/ubuntu and the generated keypair. Deleting the
workspace removes the whole profile directory, both images, the encrypted
secrets file, every checkpoint and the host-side SSH material, then releases
its reserved MAC address.
Throwaways and checkpoints
bromure-cli vm run --rm gives you a docker-style workspace deleted when the
VM stops. Every successful boot leaves a copy-on-write checkpoint of disk and
home, taken only after a boot that produced a working terminal, so each one is
proven bootable.
The forced wipe
If the proxy catches a placeholder credential heading somewhere it does not belong, Bromure marks the workspace compromised, and it will not boot again until you wipe its disk and home. You keep your settings, tokens and SSH keys, and the wipe never touches shared host folders.
That set of verbs is the difference between erasure you asked for and erasure you assumed.
Ask what the last tenant left
Cloudflare found the misconfiguration, fixed it in nine hours, cleared the pools and the snapshots, built detection signatures to check the past, and published the block size and the flag name. Read that as a vendor handling a bad day well.
"Sandbox" names a hope about a boundary, and this boundary turned out to be a resource lifecycle rather than a wall. The industry's answer to agents running unreviewed code has been to move the execution somewhere else and trust that the somewhere else arrives clean. It does, most of the time. You cannot check it from inside the sandbox, this week or any week.
Run the code on infrastructure you can see and you get to answer the residue question yourself: one image file per workspace, free blocks off your own volume, one shared object and it is a signed public base image, and nothing on that disk worth reading if it ever did leak. Erasing it is a menu item.