React2Shell (CVE-2025-55182): How an Undocumented Protocol Led to Critical RCE in React
📅 May 9, 2026 ⏱ 15 min read 🏷️ React, Security, RCE, CVE
What Is React2Shell?
React2Shell is a critical RCE vulnerability in React's core — specifically in the Flight protocol, the internal transport mechanism that powers React Server Components (RSC) and React Server Functions (formerly Server Actions).
By sending a specially crafted Flight payload to a vulnerable server, an attacker could trick React into executing arbitrary JavaScript on the server. This means full server compromise: reading environment variables, accessing databases, exfiltrating secrets, and pivoting to internal infrastructure.
- CVE: CVE-2025-55182
- CVSS: Critical (10.0) — Remote Code Execution
- Disclosure: Reported to Meta Nov 30, 2025
- Patch: Dec 3, 2025 (React 19.1.1, Next.js 14.2-sdk-63)
- Impact: All Next.js apps using React Server Components
The Discovery Story
Lachlan Davidson, a professional security researcher, didn't set out to find a vulnerability in React. He wanted to understand the Flight protocol — the strange message format used by React Server Functions — so he could pentest modern web applications more effectively.
The Undocumented Protocol
Server Actions in React send data between browser and server using a format that looks like JSON with "bells and whistles":
0=[{ "a": "$$undefined", "b": "$1:foo:bar" }]&1=...
This format, named "Flight," is React's internal serialization protocol. It supports data types that JSON can't represent natively — Date, BigInt, Map, Set, typed arrays, Promises, circular references, and cross-chunk references. But crucially, there was no specification — only source code.
Prototype Property Enumeration — The First Red Flag
Flight allows referencing an object's properties using the $x:y syntax. Davidson discovered that this includes inherited properties from the prototype chain. For example, a Flight payload like this:
0 = { "foo": "$1:toString" }
&1 = 123
Would resolve to:
0 = { "foo": Number.prototype.toString }
Guillermo Rauch (creator of Next.js, founder of Vercel) called this "a glaring omission of a safety check."
Weaponizing Flight — From Curiosity to Exploit
Type Coercion Attacks
Consider this innocent-looking Server Function:
async function sayHello(name: string): string {
'use server'
return 'Hello, ' + name + '!'
}
TypeScript says name is a string. But TypeScript annotations aren't enforced at runtime. An attacker can send a Flight payload where name is an object with a malicious toString method — and when the server concatenates "Hello, " + name, it calls the attacker's function.
The Thenable Breakthrough
The real breakthrough came from understanding how await works. React decodes incoming Flight payloads using await decodeReply(...). Under the hood, await calls Promise.resolve(), which automatically unwraps thenables — any object with a .then method.
Davidson realized he could craft a Flight payload where the resolved result was a thenable object with an attacker-controlled .then function:
{
then: Array.prototype.push
}
When await decodeReply() resolved this, it automatically called .then(resolve, reject) — invoking the attacker's function with two arguments.
The Chain — Unlimited Function Calls
By chaining thenables (a thenable resolving to another thenable), the attacker could trigger unlimited sequential function calls, each with controlled arguments. This was the foundation of the full exploit.
The Final Exploit — Chunk.prototype.then
Inside React's Flight implementation, each chunk is represented by a Chunk object that extends Promise. Davidson found he could:
- Use
$@xsyntax to create Promise references - Extract
Chunk.prototype.thenvia prototype property enumeration - Inject it into an attacker-controlled object
- When
awaitresolves the object, it calls React's own internal.thenhandler — but against the attacker's fake chunk instead of a real one
0 = { "then": "$1:then" }
&1 = "$@2"
This gave the attacker full control over React's internal chunk state — including the server manifest, the mapping that tells Flight which module and function to execute for a given Server Function ID.
By overriding the server manifest to map a Server Function ID to child_process or a similar module, the attacker could achieve arbitrary code execution on the server.
Timeline
| Date | Event |
|---|---|
| Nov 24, 2025 (Mon) | Davidson begins reverse-engineering the Flight protocol |
| Nov 25 (Tue) | Discovers prototype property enumeration — starts weaponizing Flight for app-level exploits |
| Nov 27 (Thu) | Finds the thenable vulnerability in decodeReply |
| Nov 28 (Fri) | Breakthrough: Discovers Chunk.prototype.then injection vector |
| Nov 29 (Sat) | Discovers globalThis.__FLIGHT_SERVER_MANIFEST__ tampering path |
| Nov 30 (Sun) | Full RCE achieved — reports to Meta |
| Dec 3, 2025 | Meta releases fix and publishes CVE-2025-55182 |
| Dec 2025 – May 2026 | Full disclosure and write-ups published across the community |
Who Was Affected?
Every Next.js application using React Server Components or Server Functions was potentially vulnerable. This includes:
- All Next.js App Router sites (the default since Next.js 13)
- Any framework using React's Flight protocol directly
- Self-hosted deployments and Vercel deployments (Vercel applied the fix server-side)
npm list react next to verify your versions.
Lessons for the Industry
1. TypeScript Is Not a Security Boundary
TypeScript's type annotations are compile-time only. Any function that accepts input from an untrusted source (network, user, another service) must validate types at runtime. This is especially critical for Server Actions, where the "client" can send arbitrary JavaScript objects.
2. Protocol Designers: Document and Sandbox
The Flight protocol had no specification, making security review extremely difficult. Protocols that accept structured data from untrusted clients need formal specifications, input validation, and a strict allowlist of allowed types and operations.
3. Promise/Thenable Safety
The await keyword and Promise.resolve() implicitly unwrap any thenable — including attacker-controlled ones. Any code path that awaits user-controllable data is potentially exploitable. Use Promise.resolve(x).then(v => ...) only after validating the resolved value.
4. Cognitive Bias in Security Research
Davidson himself noted a "ridiculous cycle" — because React had such a strong security track record, both he and his collaborator Sylvie Mayer assumed the vulnerability must not exist. This blind spot almost caused them to miss the exploit entirely.
Conclusion
React2Shell is one of the most significant React vulnerabilities ever discovered — a critical RCE in the framework's core transport protocol, affecting millions of websites. It's a cautionary tale about undocumented protocols, the illusion of type safety, and the dangers of implicit trust in framework internals.
If you're running a Next.js site, verify you're on a patched version. If you're building frameworks that accept structured input, remember: every deserialization boundary is a potential RCE surface. Document your protocols. Validate everything. And never assume a battle-tested framework is immune to a critical bug.