React2Shell: Unpacking a Critical RCE in React Flight
React2Shell (CVE-2025-55182) was a critical RCE vulnerability in React's Flight protocol, discovered by unpicking its undocumented internal workings. It leveraged how Flight deserializes complex objects and how `await` leniently handles "thenables," ultimately allowing attackers to execute arbitrary code by manipulating React's internal promise resolution logic.
In late 2025, a critical remote code execution (RCE) vulnerability, dubbed "React2Shell" (CVE-2025-55182), shook the React ecosystem. This discovery, initially a deep dive into an undocumented protocol, revealed fundamental flaws affecting millions of websites. This post recounts the technical journey, from unraveling an obscure communication format to uncovering how an oversight in handling asynchronous operations could lead to arbitrary code execution.
The Mystery of React Flight
Modern React frameworks like Next.js extensively use React Server Components (RSC) for efficient server-side rendering and React Server Functions (formerly Server Actions) to enable seamless invocation of server-side JavaScript from client-side code. To facilitate these advanced features, React introduced a novel communication protocol for exchanging complex JavaScript objects between the browser and server. This protocol, known as "Flight," handles data types beyond what standard JSON can represent, such as Date, BigInt, Map, circular references, and Promises.
Initially, Flight's details were scarce, with no formal specification and limited documentation. Developers often mistook its requests, which look like URL-encoded JSON with special markers, for standard JSON. An example of a Flight message might look like this:
0 = { "email": "test@example.com", "updated": "$D04 Dec 1995 00:12:00 GMT", "details": "$1" } &1 = { "firstName": "$2", "lastName": "$3:foo" } &2 = "John" &3 = { "foo": "Doe" }
Once parsed, this resolves to a rich JavaScript object on the server:
javascript { "email": "test@example.com", "updated": Date(Mon Dec 04 1995 13:12:00 GMT+1300 (New Zealand Daylight Time)), "details": { "firstName": "John", "lastName": "Doe" } }
The $ syntax in Flight denotes special types: $D for a Date object, $x for a reference to another chunk, and $x:y for property selection. This ability to reference properties, including those inherited from a prototype, became a critical early observation. For instance, sending { "foo": "$1:toString" } with &1=123 would successfully retrieve Number.prototype.toString and place it on the attacker's object. While seemingly benign due to adhering to standard JavaScript prototype lookup, this was later recognized as a "glaring omission of a safety check."
Initial Exploitation Avenues and Type Safety Illusions
Initially, the hunt wasn't for a vulnerability in Flight, but for ways to abuse Flight in Next.js applications that lacked proper input validation. Flight's ability to send complex objects meant developers' assumptions about user input types could be easily broken. Consider a server function:
javascript async function sayHello(name: string): string { 'use server' return 'Hello, ' + name + '!' }
Despite the name: string type annotation, TypeScript only provides build-time type checking. At runtime, an attacker could send a custom object. If this object had a malicious function on its toString property, concatenation with 'Hello, ' would implicitly invoke it. Similarly, a function like str.replaceAll(before, after) could be exploited if str were an attacker-controlled object with a custom replaceAll method.
These examples highlight the "illusion of type safety." TypeScript types are compile-time aids, not runtime guarantees, making runtime validation of untrusted input crucial, especially when dealing with protocols that allow arbitrary object construction.
The Breakthrough: Abusing Thenables
The real breakthrough came when examining how React's await decodeReply(...) function processed incoming Flight payloads. This function is designed to handle JavaScript Promises, but it also leniently processes "thenables" – any object with a .then method. await invokes Promise.resolve, which, if it encounters a thenable, will call its .then method with resolve and reject callbacks as arguments. Crucially, if a thenable resolves to another thenable, it too is automatically awaited and invoked.
This behavior opened a significant attack vector. An attacker could send a Flight payload like { then: Array.prototype.push }. When await decodeReply(...) processed this, it would call Array.prototype.push on the attacker's object, passing React's internal resolve and reject functions as arguments. This effectively allowed an attacker to call an arbitrary function on their object, with React's internal promise resolution functions as arguments. While this initially seemed like just another way to call an attacker-controlled function, its true power lay in the ability to chain calls and manipulate internal execution flow.
Accessing React's Internals and the Path to RCE
The final piece of the puzzle involved leveraging Flight's internal mechanisms. Flight uses $@x to create a Promise of a chunk that will arrive later, which internally constructs a new Chunk(...) object. This Chunk object inherits from Promise and contains React's internal .then implementation.
By crafting a Flight payload that references Chunk.prototype.then and applies it to an attacker-controlled object, the RCE became tangible. Specifically, a payload like 0 = { "then": "$1:then" } &1 = "$@2" would cause React's Chunk.prototype.then to be invoked against the attacker's object. React's internal then implementation, expecting a Chunk object, would attempt to access properties like .status and .reason on the attacker's malicious object. Since these properties would be undefined or attacker-controlled, this interaction allowed the attacker to effectively spoof internal Chunk state and manipulate React's core logic.
The immediate implication was the ability to trick React into believing it was dealing with valid internal constructs, leading to a path where an attacker could influence the "server manifest" – the strict list mapping Server Function IDs to actual server-side code. By corrupting or controlling this manifest, an attacker could potentially invoke arbitrary functions, achieving remote code execution.
Practical Takeaways
The React2Shell vulnerability serves as a stark reminder of several critical security principles for developers:
- Validate All Untrusted Input: Never rely solely on compile-time type annotations (like TypeScript) for runtime safety. Always perform robust runtime validation of data received from clients, especially when dealing with complex data structures or novel communication protocols.
- Understand Underlying Protocols: Abstracted frameworks can hide complex underlying mechanisms. A deeper understanding of how data is serialized, transmitted, and deserialized (like with Flight) can reveal unexpected attack surfaces.
- Beware of Implicit Conversions and Asynchronous Magic: JavaScript's flexible nature, including implicit type coercion and the lenient handling of thenables by
async/await, can create subtle security vulnerabilities. Be explicit with types and conversions wherever possible. - Security in Depth: Even battle-tested frameworks can have critical vulnerabilities. A multi-layered security approach, including robust input validation, secure coding practices, and regular security audits, is essential.
FAQ
Q: What is the primary difference between Flight and standard JSON?
A: Flight extends JSON's capabilities by supporting complex JavaScript types (like Date, BigInt, Map, Promise), references (including circular ones), and even server-side function references, which JSON cannot natively represent.
Q: How does TypeScript's type safety relate to the React2Shell vulnerability?
A: TypeScript provides build-time type checking, which helps catch errors during development. However, it does not enforce types at runtime. The vulnerability exploited this by sending arbitrary objects from the client, bypassing the server-side code's expectation of a specific type (e.g., string), even if that type was declared in TypeScript.
Q: What is a "thenable" and why was its behavior crucial for React2Shell?
A: A thenable is any JavaScript object that has a .then method. The await keyword, when used with Promise.resolve, leniently invokes this .then method. This behavior was crucial because it allowed attackers to force React's internal resolve and reject functions to be passed as arguments to an attacker-controlled .then method on a malicious object, effectively manipulating React's internal asynchronous execution flow.
Related articles
Trump Orders Voluntary AI Model Review Before Release
President Trump has signed an executive order creating a voluntary framework for AI companies to share advanced models with the federal government before release. This initiative aims to bolster secure innovation and protect critical infrastructure, reflecting a shift from the administration's previous hands-off approach to AI safety. Companies opting for pre-release review may receive confidentiality protections.
Blue Origin's New Glenn Explosion: Key Components Survive, 2026
Blue Origin announced that critical fuel tanks and key launch pad components survived last week's New Glenn rocket explosion, paving a faster path back to flight. CEO Dave Limp pledges a return to orbital missions before year-end, which is crucial for NASA's Artemis lunar program to maintain its tight schedule for crewed landings.
Great Question (YC W21) Seeks Applied AI Interns: A Deep Dive
As fellow developers, we’re constantly scanning the landscape for companies pushing the boundaries, especially in the rapidly evolving AI space. Great Question, a Y Combinator W21 alumnus, has caught our eye with an
startups: The White House is at war with itself over who gets to
An intense internal power struggle within the Trump administration has stalled US federal AI regulation, leaving a policy vacuum after Anthropic's Mythos model revealed critical cybersecurity risks. Factions within the Commerce Department, intelligence agencies, and pro-industry groups are locked in a "knife fight" over who gets to evaluate and oversee advanced AI systems. This paralysis follows the abrupt cancellation of a landmark executive order and the unexplained withdrawal of AI testing announcements.
Navigating the Global AI Arena: Beyond Silicon Valley's Borders
The international AI landscape presents unique challenges and opportunities, requiring developers to think beyond traditional tech hubs. Key aspects include adapting AI models to local languages and cultures, navigating the complex global supply chain for critical hardware like semiconductors, and understanding how venture capital assesses these international ventures. Success hinges on deep local market understanding, robust technical solutions for localization, and resilience against logistical hurdles.
Engineering a Solution: Debugging Global Mosquito-Borne Diseases
As developers, we're constantly tasked with solving complex problems, whether it's optimizing a database query or architecting a distributed system. But what if the 'bug' we're trying to fix is biological, with global




