Web Development and Design

Weaponizing and Defending the React Flight Protocol

The introduction of React Server Components (RSC) brought a paradigm shift in how developers build interactive web applications. By leveraging a custom streaming protocol named Flight, RSCs enable efficient delivery of dynamic UIs. However, this very mechanism, designed for seamless client-server communication, has revealed significant deserialization vulnerabilities that attackers can exploit. Durgesh Pawar’s detailed analysis uncovers the intricacies of the CVSS 10.0 “React2Shell” vulnerability, illustrating how manipulation of the Flight protocol can lead to devastating remote code execution. This article delves into the mechanics of these vulnerabilities, the real-world impact, and provides a prioritized set of defenses, ranging from stringent schema validation to robust CSRF hardening, crucial for securing modern React applications against such structural risks.

Flight On The Wire: A New Communication Paradigm

React Server Components do not transmit HTML or standard JSON. Instead, they utilize the proprietary Flight protocol, a line-delimited streaming format. This protocol boasts its own type system, reference resolution mechanisms, and rules for reconstructing executable client-side behavior. For most React developers, the inner workings of Flight remain a black box, handled seamlessly by the framework. A typical Flight payload, when observed in a browser’s network tab (often identified by the Content-Type: text/x-component header), appears as a complex interplay of JSON fragments, dollar-sign-prefixed references, and module pointers. The React runtime intelligently reassembles these elements into a live component tree, a process that often goes unquestioned.

The Flight protocol’s structure is defined by rows, each conforming to the format <ROW_ID>:<ROW_TAG><PAYLOAD>n. The ROW_ID is a numeric identifier used for cross-referencing within the stream, while the ROW_TAG is a single character or short string indicating the type of data that follows. Key row tags include:

  • J (JSON Tree): Serialized virtual DOM nodes, component props, and HTML elements.
  • M (Module): Metadata for client component modules or chunks.
  • I (Import): Instructs the client to load a module from the bundler’s chunk map.
  • HL (Hint/Preload): Directs the browser to preload resources like stylesheets or fonts.
  • D (Data): Server-rendered element context and environment information.
  • E (Error): Serialized server-side exceptions and error boundary information.

While these tags define the basic structure, the true complexity and the attack surface lie within the $ prefix system.

The $ Prefix System: Unpacking the Vulnerability

When the client-side Flight parser encounters a string value beginning with $, it triggers a special resolution path. The parseModelString function within ReactFlightClient.js acts as a central dispatcher, interpreting the character following the $ to determine the data type and its corresponding processing logic.

Prefix Type Parser Action
$ Model Reference Resolves to another chunk in the stream (e.g., $2 points to row 2).
$: Property Access Traverses into a resolved chunk’s properties (e.g., $1:user:name accesses chunk[1].user.name).
$S Symbol Creates a native JavaScript Symbol.
$F Server Reference Represents a callable Server Action, effectively an RPC endpoint on the server.
$L Lazy Component Defers component loading until it’s needed in the render tree.
$@ Promise/Raw Chunk Returns the internal Chunk wrapper object, not its resolved value. Used for Promises and as a mutable handle for exploits.
$B Blob/Binary Triggers the blob deserialization handler for binary data.

The $: prefix, in particular, allows for arbitrary property traversal based on colon-separated paths provided within the stream. This mechanism, reminiscent of JavaScript prototype pollution vulnerabilities, became the focal point of significant security concerns. The Flight protocol, therefore, is not merely a data serialization format; it’s a system capable of reconstructing behavior, including module imports, server action invocations, and asynchronous state management.

The Genesis of React2Shell: CVE-2025-55182

The critical security vulnerability, officially designated CVE-2025-55182 and colloquially known as React2Shell, emerged in December 2025. This unauthenticated remote code execution (RCE) vulnerability, carrying a CVSS score of 10.0, exploited flaws in the Flight deserialization layer. A single, carefully crafted HTTP request targeting a Server Function endpoint was sufficient to grant an attacker shell access to the server, bypassing authentication entirely.

The U.S. Cybersecurity and Infrastructure Agency (CISA) promptly added React2Shell to its Known Exploited Vulnerabilities (KEV) catalog, highlighting its severity. Sysdig’s research further connected real-world exploitation of this vulnerability to North Korean state-sponsored actors, who were deploying file-less implants utilizing the Ethereum blockchain for command-and-control (C2) communication. This level of sophisticated and rapid exploitation underscored the immediate threat posed by React2Shell.

Unpacking the Attack: The Mechanics of Flight Deserialization Sinks

The core of the vulnerability lies in how Flight deserializes complex data structures, particularly those involving property access and object reconstruction. Unlike standard JSON.parse(), which strictly adheres to data serialization, Flight’s custom logic can invoke behavior during the deserialization process, creating a fertile ground for attacks.

Prototype Pollution: JavaScript’s prototype-based inheritance model allows objects to inherit properties from their prototypes. An attacker can exploit this by injecting keys like __proto__ or constructor.prototype into the deserialized data. This allows them to modify the shared base prototypes, affecting all subsequent object operations. The $: prefix in Flight, enabling deep property traversal, directly facilitates this. The getOutlinedModel function, responsible for resolving these paths, iterates through segments like user:name. If these segments include __proto__ or constructor, the traversal ascends the prototype chain, potentially leading to unintended modifications.

Duck Typing and Thenables: JavaScript’s dynamic nature treats any object with a .then property as a "Thenable," which the runtime invokes during await operations. Flight’s asynchronous chunk resolution can be exploited by constructing objects with manipulated .then properties. When the React runtime attempts to await a resolution, it inadvertently calls the attacker-controlled function, providing a pathway for code execution.

The convergence of these risks is the fundamental problem: Flight deserializes not just data, but behavior. The $ prefix system dictates the parser’s control flow, enabling the reconstruction of executable references, lazy-loaded components, RPC endpoints, and asynchronous state. An attacker capable of influencing the stream’s content can therefore dictate which functions are called, which objects are constructed, and which internal states are exposed.

The React2Shell Gadget Chain: A Step-by-Step Breakdown

CVE-2025-55182, the React2Shell RCE, exemplifies how multiple features of the Flight protocol can be chained together for malicious purposes. The vulnerability resided in the getOutlinedModel function within the server-side reply handling code (ReactFlightReplyServer.js). This function processed deep property paths specified by the $: reference. The critical flaw was a simple loop:

for (key = 1; key < reference.length; key++)
    parentObject = parentObject[reference[key]];

This loop lacked any hasOwnProperty check or validation to ensure the property existed directly on the object rather than its prototype chain. An attacker could supply a path such as $1:__proto__:constructor:constructor. This would traverse from a plain JSON object up the prototype chain to Object.prototype, then to the Object constructor, and finally to the Function constructor. In JavaScript, the Function constructor can be used to execute arbitrary code, akin to eval(), via Function("arbitrary code")().

The gadget chain, as detailed in security advisories, typically involved:

  1. Crafting a malicious Flight payload: This payload would contain the specially constructed property traversal path.
  2. Exploiting the $: prefix: The parser would follow the path, leading to the Function constructor.
  3. Achieving Remote Code Execution: By leveraging the Function constructor, attackers could execute arbitrary code on the server.

The impact of this vulnerability was severe, leading to unauthenticated RCE. Reports from Sysdig and Palo Alto Networks’ Unit 42 documented rapid exploitation by advanced persistent threats (APTs), including the deployment of sophisticated implants like EtherRAT and KSwapDoor. These campaigns highlighted the speed at which vulnerabilities in core web frameworks can be weaponized by state-sponsored actors.

The Fix: A Patch and Lingering Concerns

Weaponizing And Defending The React Flight Protocol: Deserialization Sinks In RSCs — Smashing Magazine

The React team responded swiftly, releasing patches that addressed the immediate RCE threat. The core fix involved caching the genuine Object.prototype.hasOwnProperty method at module load time and using .call() to invoke it during property checks. This effectively blocked the prototype chain traversal that powered the React2Shell gadget chain. The patches were integrated into React versions 19.0.1, 19.1.2, and 19.2.1.

However, the author notes that while the hasOwnProperty check was implemented, the fundamental mechanism of arbitrary property traversal via the $: prefix remained intact. This suggests that the patch addresses a symptom rather than the root cause of exposing such powerful internal primitives through a network protocol. Future vulnerabilities, it is argued, could still emerge from this structural design.

Beyond React2Shell: A Cascade of Vulnerabilities

The security audits following React2Shell revealed a series of related vulnerabilities in the Flight deserialization surface. While not as severe as the initial RCE, these issues underscore the complexity of securing such systems:

  • CVE-2025-55184 & CVE-2025-67779 (Denial of Service): These vulnerabilities involved infinite recursion of nested Promises and edge cases missed by initial fixes, leading to Node.js event loop hangs and CPU exhaustion. Multiple rounds of patching were required.
  • CVE-2026-23864 (Denial of Service/Out-of-Memory): Disclosed in January 2026, this vulnerability involved unbounded request body buffering and zipbomb-style decompression, leading to memory exhaustion.
  • CVE-2025-55183 (Information Disclosure): This bug allowed attackers to expose server function source code by crafting arguments that, when stringified for logging or debugging, reflected the code itself back in the response.
  • CVE-2026-27978 (CSRF Bypass): A vulnerability in Next.js’s Server Action handling treated Origin: null (sent by sandboxed iframes) as a missing origin, allowing attackers to bypass CSRF protections and invoke Server Actions using victim cookies. This was fixed in Next.js 16.1.7.

These subsequent disclosures highlight the difficulty in completely securing complex deserialization parsers and the potential for subtle bugs to persist even after initial fixes.

Defenses, Ranked by Impact: Fortifying Your React Applications

Given the inherent complexities and the history of vulnerabilities, a multi-layered defense strategy is crucial for securing React Server Components. The following defenses are ranked by their practical impact, based on current threat landscapes and vulnerability research:

  1. Input Validation on Server Actions (Zod, Valibot): This is the single most impactful application-level defense. Strict schema validation at the very beginning of every Server Action is paramount. Before any business logic executes, validate all incoming data types, shapes, lengths, and enumerated values. Using .safeParse() over .parse() is recommended to avoid leaking internal error details. It is critical to validate the raw argument object before destructuring to prevent exploiting property access on unvalidated data.

    "use server";
    import  z  from "zod";
    
    const UpdateProfileSchema = z.object(
      name: z.string().min(1).max(100),
      email: z.string().email(),
      role: z.enum(["user", "editor"]),
    );
    
    export async function updateProfile(formData: FormData) 
      const parsed = UpdateProfileSchema.safeParse(
        name: formData.get("name"),
        email: formData.get("email"),
        role: formData.get("role"),
      );
      if (!parsed.success) return  error: "Invalid input" ;
      // proceed with parsed.data
    

    Consider implementing lint rules to flag use server exports that lack an immediate validation call.

  2. The server-only Package: This package provides a straightforward mechanism to prevent server-side code from being imported into client components. By importing "server-only" at the top of files containing sensitive logic (database credentials, API keys, business logic), build failures will occur if a client component attempts a direct or transitive import. However, it’s crucial to note that server-only prevents code leakage, not data leakage. Return values from server-only functions must be explicitly filtered before being passed to client components. Careful management of barrel files is also necessary to avoid inadvertently exposing server-only modules.

  3. CSRF Protections: Beyond framework defaults, implement robust Cross-Site Request Forgery (CSRF) protections for state-changing Server Actions. This includes:

    • Cookie Configuration: Set SameSite=Strict or SameSite=Lax on session cookies.
    • Explicit CSRF Tokens: For high-value operations, generate per-session CSRF tokens, embed them in forms or headers, and validate them within the Server Action.
    • allowedOrigins Caution: Never include 'null' in experimental.serverActions.allowedOrigins in Next.js configurations, as this reopens the CVE-2026-27978 bypass.
  4. The hasOwnProperty Patch: Ensure your React dependencies are updated to versions that include the hasOwnProperty patch (React 19.0.1+, 19.1.2+, 19.2.1+). Verify your lockfile to confirm you are not running vulnerable versions. Additionally, be aware that subsequent DoS fixes require even newer versions (e.g., 19.0.4+, 19.1.5+, 19.2.4+).

  5. The Taint API: React’s Taint API (taintObjectReference, taintUniqueValue) can serve as a development-time guardrail to prevent sensitive data from unintentionally leaking to the client. It works by throwing an error during serialization if tainted data is passed to a client component. However, taint tracking is reference-based and breaks with data transformations or derivations. It is a useful defense-in-depth layer but should not be considered a primary security boundary against determined attackers.

  6. Web Application Firewalls (WAFs): WAFs can add a detection layer for known attack patterns, such as prototype pollution attempts or specific Flight error leakage signatures. However, WAFs are not foolproof and can be bypassed by sophisticated attackers using padding or encoding techniques. They are best viewed as a noise-reduction layer for automated scanners and low-effort attacks, rather than a definitive security boundary.

What’s Still Exposed: Emerging Threats

Despite patches and robust defenses, certain aspects of the Flight protocol’s design continue to present residual risks:

  • Man-in-the-Middle (MITM) on the Flight Stream: If an attacker can intercept the communication stream (e.g., via CDN compromise or rogue proxy), they could potentially alter the Flight payload in transit. This could involve redirecting component loading, injecting hidden RPC triggers, or modifying component props, leading to XSS vulnerabilities if dangerouslySetInnerHTML is used.
  • Server Action Enumeration: Server Action IDs, while obfuscated, are mapped in a public server-reference-manifest.json. Exposure of this manifest or the .next directory can reveal a complete API map, allowing attackers to enumerate actions and attempt IDOR or parameter tampering attacks.
  • Encrypted Closure Tampering: Server Actions can capture variables from their scope (closures), which are then encrypted for client-side transmission. While Next.js encrypts these, if a static encryption key is compromised (e.g., via file read access), an attacker could decrypt, tamper with, and re-encrypt closure state, potentially altering critical parameters like user IDs or roles.
  • Supply Chain Activation via Module IDs: The Flight protocol references client components by module ID. If a compromised dependency exists in node_modules but is not directly imported, an attacker could potentially inject $ import references into the Flight stream, compelling the client to load and execute dormant malicious code from the bundled chunks.

Historical Context: A Pattern of Vulnerabilities

The challenges encountered with React Flight are not unique. Historically, other frameworks have faced similar security issues arising from custom serialization protocols:

  • Google Web Toolkit (GWT): Used a custom RPC protocol that, when manipulated, allowed for arbitrary deserialization.
  • Java Server Faces (JSF) and ASP.NET: Both serialized ViewState to the client. Weak cryptographic signing allowed attackers to tamper with this state, leading to RCE.

These historical precedents underscore a recurring pattern: frameworks that invent custom wire formats for rich, stateful, and sometimes executable data transfer often create attack surfaces that can be exploited when the server-client trust model is compromised. React Flight represents the latest iteration of this ongoing challenge.

The Path Forward: Cryptographic Validation and Content Integrity

The React Flight protocol is a powerful solution for delivering interactive UIs efficiently. However, its reliance on trusting the structure of the stream, both on the server and the client, presents inherent risks. While the React team has diligently patched known vulnerabilities, the structural issues of exposing arbitrary property traversal and executable Thenable reconstruction remain points of concern.

As server-driven UI patterns become more prevalent across the industry, the need for stronger primitives beyond a simple "server is trusted" model will grow. This includes exploring cryptographic validation of serialized payloads, signed component trees, and content integrity checks directly on the Flight stream itself. Developers building with Server Components must understand the underlying mechanisms, diligently apply security best practices, and stay informed about evolving threats and patches to ensure the robustness and security of their applications.

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button
Blog News Tweets
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.