The landscape of modern web development was significantly shaken in December 2025 with the disclosure of CVE-2025-55182, dubbed "React2Shell," an unauthenticated remote code execution (RCE) vulnerability that scored a critical CVSS 10.0. This flaw, residing deep within the React Server Components’ custom streaming protocol known as Flight, revealed a profound structural risk inherent in novel deserialization mechanisms. Security researcher Durgesh Pawar meticulously deconstructed the vulnerability, illustrating how seemingly benign protocol manipulation could grant attackers unhindered shell access to compromised systems. The incident prompted immediate patching efforts and a critical re-evaluation of security postures for applications leveraging React Server Components, highlighting the urgent need for robust defenses ranging from stringent schema validation to enhanced CSRF hardening.
The Rise of React Server Components and the Enigma of Flight
React Server Components (RSCs) represent a paradigm shift in how React applications are built, aiming to enhance performance, simplify data fetching, and improve developer experience by blurring the lines between server and client. Unlike traditional server-side rendering (SSR) that sends HTML, or client-side rendering (CSR) relying on JSON APIs, RSCs introduce a custom, binary-like streaming protocol called Flight. This protocol is the backbone of RSCs, enabling the server to stream interactive UI components directly to the browser, which then reconstructs the live component tree.
Most React developers, focused on the declarative UI and performance gains, rarely delve into the intricacies of Flight. It’s a line-delimited format, a mix of JSON fragments, dollar-sign-prefixed references, and module pointers that the React runtime silently reassembles. The framework’s abstraction layer ensures developers can build complex UIs without ever inspecting a Flight payload in their network tab. This inherent trust, however, proved to be a critical blind spot, as the system tasked with reconstructing executable behavior from a stream of text inherently functions as a powerful deserialization engine.
The React2Shell Revelation: A CVSS 10.0 Threat
The gravity of CVE-2025-55182, or React2Shell, became immediately apparent upon its disclosure in December 2025. A CVSS score of 10.0 signifies the highest possible severity, indicating an unauthenticated, network-exploitable vulnerability with complete loss of confidentiality, integrity, and availability. In the case of React2Shell, a single, specially crafted HTTP request targeting a Server Function endpoint was sufficient to achieve remote code execution, bypassing authentication entirely.
The United States federal Cybersecurity & Infrastructure Security Agency (CISA) promptly added React2Shell to its Known Exploited Vulnerabilities (KEV) catalog, a clear indicator of its critical nature and active exploitation potential. The security firm Sysdig later corroborated in-the-wild exploitation, attributing sophisticated attacks to North Korean state-sponsored threat actors. These groups were observed deploying file-less implants, notably "EtherRAT," which utilized the Ethereum blockchain for command-and-control (C2) communications—a technique dubbed "EtherHiding"—making detection and takedown exceptionally challenging. Another group, identified by Palo Alto Networks’ Unit 42, deployed a backdoor known as "KSwapDoor," designed to mimic legitimate Linux kernel processes and communicate over an encrypted P2P mesh network, showcasing the advanced capabilities of adversaries targeting this vulnerability.
Dissecting the Flight Protocol: A Deserialization Sink
Durgesh Pawar’s deep dive into the Flight protocol’s source code, particularly functions like getOutlinedModel and getChunk, revealed that React2Shell was not merely an isolated parsing bug. It was a symptom of a fundamental design flaw: Flight’s role as a robust deserialization system. The protocol reconstructs not just data, but executable references, lazy-loaded components, server RPC endpoints, and asynchronous state from its streaming input.
Flight operates on a row-based format, where each line carries a specific tag (e.g., J for JSON Tree, M for Module, I for Import, D for Data, E for Error) and a payload. The critical attack surface, however, lies in its $ prefix system. When the client-side parser encounters a string value starting with $ (e.g., $2, $1:user:name, $F), it triggers type-specific resolution paths. These prefixes dictate the parser’s control flow, allowing it to:
$(Model Reference): Resolve to other chunks in the stream.$:(Property Access): Traverse object properties (e.g.,$1:user:name). This was the linchpin of React2Shell.$F(Server Reference): Represent callable Server Actions (RPC endpoints).$@(Promise/Raw Chunk): Expose internalChunkobjects, which manage resolution state and callbacks.
This design fundamentally transforms Flight from a mere data format into a system capable of orchestrating behavior. The parser’s execution path is directly influenced by the stream’s content, allowing an attacker to manipulate what functions are called, what objects are constructed, and what internal framework state is exposed.
The Mechanics of React2Shell: Prototype Pollution and Thenables
The vulnerability in React2Shell leveraged classic deserialization attack patterns: prototype pollution and the exploitation of JavaScript’s "Thenable" duck typing.
-
Prototype Pollution: JavaScript’s prototype-based inheritance allows objects to inherit properties from their
__proto__link. If an attacker can inject__proto__orconstructor.prototypeas a key during object reconstruction, they can modify the shared base prototypes, leading to attacker-controlled values being read by downstream code. ThegetOutlinedModelfunction, responsible for resolving deep property paths (e.g.,$1:user:name) via the$:prefix, lacked proper validation. Its core loop,parentObject = parentObject[reference[key]], blindly traversed properties withouthasOwnPropertychecks. This allowed an attacker to inject__proto__andconstructorinto the path, ascending the prototype chain from a plain object toObject.prototype, then to theObjectconstructor, and finally to theFunctionconstructor. In JavaScript,Function("arbitrary code")()acts as aneval()equivalent, enabling RCE. -
Thenable Exploitation: JavaScript’s V8 engine treats any object with a callable
.thenproperty as a "Thenable." Whenawaitis used, the runtime invokes this.thenmethod. Flight’s asynchronous chunk resolution provided a vector. By constructing an object with a manipulated.thenproperty and injecting it into the resolution pipeline, attackers could force the runtime to execute their arbitrary function during normalawaitoperations.
The complete gadget chain involved:
- Injecting a specially crafted Flight payload into a Server Function endpoint.
- Using the
$:prefix with__proto__:constructor:constructorto reach theFunctionconstructor via prototype pollution. - Leveraging the
$@prefix to expose internalChunkobjects, which allowed for mutable handles to be created. - Manipulating the
thenproperty of these objects to point to theFunctionconstructor. - Supplying arbitrary JavaScript code as an argument, which would then be executed when the compromised "Thenable" was
awaited by the React runtime.
The Official Fix and Subsequent Vulnerabilities

The React team’s response was swift and targeted. The fix, implemented in React versions 19.0.1, 19.1.2, and 19.2.1, involved caching the genuine Object.prototype.hasOwnProperty method at module load time and explicitly invoking it via .call() for every property check in the deserialization path. This effectively neutralized the prototype chain traversal, blocking the known gadget chain.
However, React2Shell was merely the first in a series of related vulnerabilities. Subsequent security audits revealed that the fundamental deserialization surface remained complex, leading to additional disclosures:
- CVE-2025-55184 (DoS, CVSS 7.5): An infinite recursion of nested Promises during Server Function deserialization could hang the Node.js event loop, causing denial of service. Fixed in 19.0.2, 19.1.3, 19.2.2.
- CVE-2025-67779 (DoS, CVSS 7.5): An incomplete fix for CVE-2025-55184, where edge cases in the Promise loop were missed, requiring a second patch round. Fixed in 19.0.4, 19.1.5, 19.2.4.
- CVE-2026-23864 (DoS/OOM, CVSS 7.5): Disclosed in January 2026, this vulnerability exploited unbounded request body buffering and zipbomb-style decompression, leading to memory exhaustion and denial of service. Fixed in 19.0.4+, 19.1.5+, 19.2.4+.
- CVE-2025-55183 (Info Disclosure, CVSS 5.3): A "sneaky" vulnerability where crafted requests could reflect Server Function source code back to the attacker if the function implicitly or explicitly stringified a malicious argument. This could expose business logic, database queries, and hardcoded secrets. Fixed in 19.0.1, 19.1.2, 19.2.1.
- CVE-2026-27978 (CSRF Bypass, CVSS 5.3): A Next.js-specific bug where the
Origin: nullheader (sent by sandboxed iframes) was incorrectly treated as a missing origin rather than an explicit cross-origin indicator, allowing CSRF attacks. Fixed in Next.js 16.1.7.
These subsequent vulnerabilities underscored a crucial lesson: patching specific gadgets does not eliminate the inherent risk of a complex deserialization system. The underlying architectural patterns—reconstructing behavior from untrusted input—remain fertile ground for new attack vectors.
Historical Parallels: A Recurring Security Pattern
The challenges faced by React Flight are not unique. History is replete with examples of frameworks that introduced custom serialization formats for server-client communication, only to discover they had inadvertently created new attack surfaces.
- Google Web Toolkit (GWT): Its custom RPC protocol for syncing Java objects between browser and server was found to be vulnerable to arbitrary deserialization, eventually leading GWT to disable binary serialization.
- Java Server Faces (JSF) and ASP.NET: Both serialized
ViewStateto the client. Weak or missing cryptographic signing allowed attackers to tamper with this serialized state, leading to RCE and necessitating repeated patching from Microsoft and Oracle. - Python’s
pickle, PHP’sunserialize, .NET’sBinaryFormatter: These widely used serialization mechanisms have all been vectors for RCE due to their ability to reconstruct executable code or trigger magic methods during deserialization, withBinaryFormattereventually being deprecated entirely.
The pattern is consistent: frameworks introduce rich, stateful, often executable data formats, assuming a trusted server-to-client flow. Attackers then find ways to manipulate these formats, either in transit or by tricking the server into deserializing attacker-controlled input. React Flight, despite its modern design, is the latest manifestation of this enduring security challenge.
Defenses for React Server Components: A Layered Approach
Securing React Server Components requires a defense-in-depth strategy, moving beyond relying solely on framework patches. Developers must actively implement robust safeguards at the application level.
-
Strict Input Validation on Server Actions (Zod, Valibot): This is paramount. Since the Flight deserializer processes raw network input before application code executes, every Server Action must begin with strict schema validation. Tools like Zod or Valibot should validate types, shapes, lengths, and bounds. Crucially, validate the entire argument before destructuring or accessing individual properties, as premature access can expose deserialized, unvalidated input to exploits. Using
.safeParse()is recommended to avoid leaking internal error details. -
The
server-onlyPackage: This simple yet effective package prevents server-side code (e.g., database credentials, sensitive business logic) from accidentally being bundled into client components. Importingserver-onlyat the top of a file ensures a build-time error if a client component attempts to import it, directly or transitively. Developers must be mindful of "barrel files" that might inadvertently re-export server-only functions alongside client-safe utilities. It’s important to remember thatserver-onlyprotects code, not data; sensitive data returned by server components must still be explicitly filtered before being passed to client components. -
Enhanced CSRF Protections: Following CVE-2026-27978, relying solely on Next.js’s default
Originvs.Hostheader check is insufficient. For state-changing Server Actions, developers should:- Configure
SameSite=StrictorSameSite=Laxfor session cookies. - Implement explicit CSRF tokens for high-value operations, generating per-session tokens on the server and validating them in the Server Action.
- Never add
'null'toexperimental.serverActions.allowedOriginsin Next.js config, as this reopens theOrigin: nullbypass.
- Configure
-
Verify
hasOwnPropertyPatch and Stay Updated: The RCE fix for React2Shell landed in React 19.0.1, 19.1.2, and 19.2.1. Developers must verify theirreact-server-dom-webpackversion in their lockfile. Furthermore, subsequent DoS fixes (CVE-2025-55184, CVE-2025-67779, CVE-2026-23864) require 19.0.4+, 19.1.5+, or 19.2.4+. Regular updates are non-negotiable for critical framework components. -
The Taint API (
taintObjectReference,taintUniqueValue): React’s experimental Taint API provides a development-time guardrail. It registers objects or strings as "tainted," causing an error if they attempt to pass through the Flight serializer to the client. This helps catch accidental data leaks (e.g., passing a full user object withpasswordHashto a client component). However, taint tracks references, not data content. Any derivation (spreading, individual property access, serialization/deserialization) breaks the taint tracking. It’s a useful defense-in-depth mechanism but not a primary security boundary against a motivated attacker. -
Web Application Firewalls (WAFs): WAFs can serve as a detection layer, blocking known attack patterns. Rules can be configured to block requests containing
__proto__orconstructor:constructorinNext-ActionPOST requests, flag responses leaking error digests (E{"digest"), or mitigate DoS vectors by blocking excessively large payloads. However, WAFs are not foolproof; sophisticated attackers can bypass them using padding or encoding tricks to evade inspection buffers. They are a valuable noise-reduction layer, not a substitute for robust application-level security.
Ongoing Structural Risks and Future Outlook
Despite the patches, several structural risks remain inherent in the Flight protocol’s design:
- Man-In-The-Middle (MITM) on the Flight Stream: An attacker with the ability to intercept and modify the plain-text Flight stream (e.g., via CDN compromise, cache poisoning, rogue proxy) could inject malicious
I(Import) rows to redirect component loading, embedF(Server Reference) tags for hidden RPC triggers, or modifyD(Data) rows to introduce XSS viadangerouslySetInnerHTML. - Server Action Enumeration: Although Server Action IDs are obfuscated hashes, a misconfigured server exposing
server-reference-manifest.jsoncould allow attackers to map these IDs to their source implementations, enabling direct parameter tampering and IDOR attacks. - Encrypted Closure Tampering: Server Actions capturing variables from their scope are encrypted. If an attacker gains file read access and extracts the
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY(especially in multi-server setups using a static key), they can decrypt, modify (e.g., changeuserId,role), and re-encrypt the closure state, making the server accept forged data. - Supply Chain Activation via Module IDs: If an attacker can inject
Iimport references into the Flight stream, they could potentially force the loading of dormant, compromised npm packages present in the bundle output, even if those packages are not explicitly imported by the application’s code.
The React Flight protocol is an innovative solution to a complex problem, enabling dynamic, server-driven UI. However, its reliance on serializing executable references, async state, module pointers, and RPC endpoints over a streaming text protocol introduces inherent trust assumptions that have historically proven problematic. The $:, $@, and $B prefixes, which expose powerful internal primitives and allow arbitrary property traversal, represent design choices that have led to severe vulnerabilities.
As more frameworks embrace server-driven UI patterns, the industry must evolve beyond the implicit trust of "the server is trusted." Future solutions will require stronger primitives: cryptographic validation of serialized payloads, signed component trees, and comprehensive content integrity checks on the Flight stream itself. Developers are urged to engage with the framework’s internal workings, particularly the react-client/src/ReactFlightClient.js file, to fully understand the mechanisms that underpin their applications and the trust placed in them. The React2Shell incident serves as a stark reminder that even cutting-edge technologies are susceptible to foundational security challenges if deserialization risks are not comprehensively addressed from the outset.




