← Back to EasyTool.me

React2Shell (CVE-2025-55182): How an Undocumented Protocol Led to Critical RCE in React

📅 May 9, 2026 ⏱ 15 min read 🏷️ React, Security, RCE, CVE

TL;DR: A critical remote code execution vulnerability (CVE-2025-55182, aka React2Shell) was found in React's undocumented Flight protocol — the transport layer behind React Server Components and Server Functions. Reported by security researcher Lachlan Davidson to Meta on November 30, 2025, and patched on December 3, 2025. This is the full story of how reverse-engineering an undocumented protocol led to a vulnerability affecting millions of websites.

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.

⚡ Key Facts:
  • 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.

"No docs, only code." Before React2Shell was disclosed, even the protocol's name "Flight" was surprisingly difficult to find. The best information available was a few threads on X discussing RSC internals.

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.

⚠️ Critical Lesson: TypeScript does not validate types at runtime. React Server Functions accept raw Flight payloads, and TypeScript annotations provide only build-time safety.

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:

  1. Use $@x syntax to create Promise references
  2. Extract Chunk.prototype.then via prototype property enumeration
  3. Inject it into an attacker-controlled object
  4. When await resolves the object, it calls React's own internal .then handler — 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

DateEvent
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, 2025Meta releases fix and publishes CVE-2025-55182
Dec 2025 – May 2026Full 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:

🛡️ How to Check if You're Protected: Ensure you're running React 19.1.1+, Next.js 14.2-sdk-63+, or any version released after December 3, 2025. Run 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.