Review the glue
JSG marshals lifetimes and pointers across the JS/native seam — exactly where UAFs live — on a fraction of V8’s scrutiny.
Exploiting Cloudflare Code Mode & Workers
The glue around it is part of the boundary too.
Hold that thought — everything after this is evidence.
How this goes
what they built, and what they promise it holds
The Claimed Boundary
Instead of one tool call at a time, the model writes one program.
The Claimed Boundary
Reach the declared tools — and nothing else.
fetch() and connect() functions throw errors.
— Cloudflare, Code Mode
The Claimed Boundary
The same core runtime code that powers Cloudflare Workers
same core runtime code — not byte-identical; production adds security + orchestration
Why target the runtime
Like breaking an AI assistant by auditing Docker’s source.
A whole security model resting on one in-process software wall. Bold things deserve a stress test.
V8 gets picked apart continuously. workerd had almost none of that attention.
Web + Node APIs reimplemented in C++ — each its own implementation, all reachable from untrusted JS.
A bug here reaches all of Workers — not one experimental feature.
Blast radius
Workers developers (Q1 FY2026)
+1M in a single quarter — vs +1.5M in all of 2025
Cloudflare Q1 FY2026 earnings (CFO)
agentic requests / month
“growing exponentially”
Cloudflare CEO, Q1 FY2026 earnings call
of Cloudflare network requests use Workers
July 2020 — a dated floor, not currentA bug here is not contained to a feature.
The Claimed Boundary
The isolate is the boundary — in one shared process.
The Claimed Boundary
So they layered defenses — the cage, MPK, an L2 sandbox.
The Claimed Boundary
Node, in C++, reachable from untrusted JS — on the unprotected heap.
5 found · 2 rated Critical
Evidence: Five Bugs in the Glue
Four findings — the URLPattern OOB exists in both implementations.
deflateParams() UAF → controllable write
Same OOB in both URLPattern implementations Cloudflare ships — that’s 5 reports.
Evidence · URLPattern
Match a URL against a pattern; read the capture groups.
const p = new URLPattern({ pathname: '/users/:id' }); p.exec('/users/42').pathname.groups; → { id: '42' }
exec() builds groups from two parallel listsgroupsurlpattern_originalworkerd-native
↔
urlpattern_standardAda-backed · default 2025-05-01
Evidence: URLPattern
V8 counts every group. URLPattern’s parser misses a nested one.
Check Point analysis — undocumented. One nested paren is the whole bug.
Evidence: URLPattern
One slot past nameList is a kj::String you control.
one of the two Critical — the one that goes to a shell
Evidence · node:zlib
It hands raw pointers to the real zlib — then never clears them.
next_out = a raw pointer straight into your JavaScript output buffer.
Fixed upstream by clearBuffers() (next_out = &dummyByte via KJ_DEFER) — shown pre-fix.
Evidence · node:zlib
deflateParams() flushes before it applies — through the dangling pointer
↳ the flush writes pending output through strm->next_out — our dangling pointer
write(input, outBuf, Z_NO_FLUSH)next_out → outBufoutBuf = null; gc()next_out now danglingparams(6, 0)deflateParams flushes via stale next_out = UAF writeBytef* — dangles after GCpre-fix: upstream clearBuffers() later sets next_out = &dummyByte via KJ_DEFER
Evidence: Five Bugs in the Glue
HTMLRewriter UAF · the KV SQL bypass
Iterator holds a raw pointer into the element’s attribute array.
Grow it past a size class and the array reallocates — the old one is freed; next() reads it back.
The authorizer checks the tables a query references — but never the destination of a rename.
storage.get('k') feeds those bytes to the
structured-clone deserializer — built for trusted, in-process data.
Reported, not weaponized. It’s about the surface.
Both now patched — iter->invalidate(); the authorizer checks the rename destination.
Evidence: Five Bugs in the Glue
Every one reaches the same native heap — outside the cage.
Cloudflare confirmed this heap is outside both the cage and MPK — via coordinated disclosure, not a public quote.
from primitive to payoff
Proof: Two Live Demos
Different assumptions — one heap.
⚠ Lane B was NOT run on Cloudflare production — on a shared host, a crash could take down a co-tenant.
Proof: Sandbox Escape
Stop caring about the bytes. Inflate a length.
Proof: Sandbox Escape
workerd hands you a 256 MB RWX region at a fixed address.
writeShellcode(0xaaaaf0000000 + 0x100000)
ARM64 reverse shell, written straight in — no ROP
locate z_stream native write callback
the dangling output path from the zlib UAF
overwrite callback target → +0x100000
point it at our shellcode
handle.write()
once more → control jumps to shellcode
id / uname -a
Proof: Cross-tenant
Production — cage + MPK ON. It still works.
Bearer sk-…
Bearer sk-live-… matched to planted secret
Proof: Two Live Demos
The cage and the keys protect V8’s heap. We never touched it.
what the evidence proves
The glue around it is part of the boundary too.
Proven.
Four takeaways
JSG marshals lifetimes and pointers across the JS/native seam — exactly where UAFs live — on a fraction of V8’s scrutiny.
tcmalloc, kj containers, VFS buffers sit outside the cage. If the cage is your story, what it skips is your attack surface.
Model-written JavaScript runs like any other. Model the model as an attacker who can write JS.
In Code Mode it’s a code-execution entry point, not a content problem. Treat it like one.
The Verdict — Disclosure
All five reported via HackerOne — a clean process, start to finish.
4 of 5 reported — zlib UAF, HTMLRewriter UAF, two URLPattern OOB reads.
Cloudflare rates two Critical — the zlib and HTMLRewriter use-after-frees. 2× CRITICAL
5th reported — the KV SQL bypass.
workerd v1.20260602.1 closes everything for self-hosted. Managed Workers were already fixed in production.
Changelog reads “2026 05 27 update” — a deliberately quiet note, no security mention. 0 CVEs as of writing.
Credit to Cloudflare — a clean coordinated process, and good to work with throughout.
It’s the shape of every in-process sandbox running untrusted code.
Wherever a memory-safe engine sits behind memory-unsafe glue, the glue is the boundary.
Setup→evidence→proof→verdict
The cage is necessary. It is not sufficient. Mind the glue.