Skip to content

Cloudflare's Outage and the React Flaw: An RCE Post-Mortem

In December 2025, an RCE vulnerability in React’s server serialization led to a Cloudflare outage. Error rates reached 22-25 million HTTP 500 responses per second at the peak. This post-mortem covers the vulnerability, the mitigation that backfired, and the sequence of events.

React 19’s Server Components use a serialization format called the flight protocol. Servers stream JSON payloads to clients, with unresolved promises marked for later resolution. Payloads use model strings that start with a dollar sign to reference data chunks by index.

The reported exploit chains two chunks. Chunk 0 holds a promise-like structure. Chunk 1 references it with a model string of type B, written $B{...}. React’s parseModelString decodes type B by reading internal state, where attacker-controlled data lands in response.formData and response.get. The exploit points response.get at Promise.prototype.then.constructor, which resolves to the Function constructor:

const thenConstructor = Promise.prototype.then.constructor;
const maliciousFn = new thenConstructor(`console.log('RCE!'); /* payload */`);
maliciousFn();

A crafted prefix reaches the Function constructor with a comment-terminated string. No authentication is required. Researcher Lackland Davidson reported spending over 100 hours reverse-engineering the chain. Any unpatched site using server components was exposed.

React’s team patched the flaw. Cloudflare raised the HTTP buffer on Workers from 128KB to 1MB, matching Next.js recommendations. The rollout exposed a problem in FL1, Cloudflare’s Lua-based firewall layer. Engineers disabled the FL1 testing tool to keep the fix moving, and the larger buffers then hit the disabled path.

Some requests carry an execute tag that delegates to secondary rule sets. With the tool disabled, that path returned nil:

if rule_set.action == "execute" then
local extra_results = get_action_results(rule_set) -- Returns nil
end

The nil value cascaded. Rule sets were not evaluated, errors went unhandled, and frontline servers returned 500s. FL2, the Rust rewrite, stayed up, because its type system rejects null dereferences at compile time.

The failure repeats a pattern from a 1994 Sun Microsystems paper, which warned against treating client and server as one object space without location-aware serialization. Java hit this class of bug, and JavaScript is hitting it again as server components blur the boundary. The operational lesson: a mitigation can be worse than the bug if it runs through unexercised code paths. The engineering lesson: serialization boundaries deserve the same review as authentication code.

Cloudflare’s role also changed the blast radius. CDNs started as caches for static assets. Current CDNs parse application-layer payloads, and FL1 had to understand React’s serialization to filter it. When infrastructure inspects deep application logic, it inherits that logic’s failure modes. The outage is a case study in the smart-edge tradeoff: each inspection layer adds a crash surface of its own.

Unpatched sites should update React and validate payloads at the edge. The incident also argues for testing mitigation paths before deploying them. The useful takeaway is narrower than the headline: a serialization bug, a risky mitigation, and a disabled test path combined into one outage.