Main Facts: The Structural Reality of Server-Driven UIs
Modern web development has largely moved away from the traditional dichotomy of static HTML files paired with client-side Single Page Applications (SPAs). Frameworks like Next.js, leveraging the React ecosystem, have aggressively championed React Server Components (RSCs). Unlike legacy architectures, RSCs do not transmit raw HTML or standard JSON across the wire. Instead, when a server component renders, it pushes a custom, streaming, line-delimited protocol known as Flight.
Flight is not merely a data format; it is a complex, stateful serialization mechanism. It embeds type systems, reference resolution logic, module pointers, callable Server Actions (RPC endpoints), and instructions for reconstructing asynchronous execution behavior directly in the client runtime.
While this architecture delivers incredible performance benefits—such as zero-bundle-size server components and progressive streaming hydration—it also introduces profound architectural risks. In December 2025, security researchers exposed a CVSS 10.0, unauthenticated remote code execution (RCE) vulnerability within the Flight deserialization layer, colloquially dubbed "React2Shell" (CVE-2025-55182). This vulnerability demonstrated that when a framework allows protocol manipulation within a complex parsing engine, attackers can achieve full remote code execution without credentials.
Chronology of an Industrial-Scale Threat
The discovery and subsequent exploitation of the Flight protocol’s deserialization flaws unspooled across a rapid, high-stakes timeline that caught enterprise security teams off guard:
- December 3, 2025: CVE-2025-55182 (React2Shell) is officially disclosed. Security advisories classify it as a critical CVSS 10.0 unauthenticated RCE flaw residing in the Flight deserialization layer.
- Early December 2025: Active, in-the-wild exploitation begins within hours of disclosure. Threat intelligence agencies, including Sysdig and Unit 42, connect the initial waves of attacks to sophisticated, state-sponsored entities.
- December 11, 2025: Secondary discoveries lead to the issuance of patches for subsequent Denial of Service (DoS) vectors (CVE-2025-55184 and CVE-2025-67779), driven by infinite recursion loops in nested Promise deserialization.
- January 2026: Additional operational risks emerge, including CVE-2026-23864 (unbounded request body buffering and memory exhaustion vectors) and CVE-2025-55183 (source code exposure via implicit argument stringification).
- February 2026: Framework updates and auxiliary CVEs—such as Next.js CVE-2026-27978, which exposed a Cross-Site Request Forgery (CSRF) bypass via
Origin: nullhandling—force developers to re-evaluate their entire perimeter security posture around Server Actions.
Supporting Data: Understanding the Flight Wire Format
To comprehend how vulnerabilities like React2Shell manifest, one must examine the actual wire format of Flight. When monitoring network traffic on an application utilizing the Next.js App Router, responses featuring the content type text/x-component reveal the streaming, row-based syntax of Flight.
Every row follows a strict structural template: <ROW_ID>:<ROW_TAG><PAYLOAD>n.
Flight Protocol Row Tags
| Tag | Name | Functional Description |
|---|---|---|
| J | JSON Tree | Serialized virtual DOM nodes, component props, and HTML elements. |
| M | Module | Metadata for specific Client Component modules or code chunks. |
| I | Import | Directs the client runtime to load a module from the bundler chunk map. |
| HL | Hint/Preload | Directs the browser to preload stylesheets, fonts, or scripts. |
| D | Data | Server-rendered element context and environment metadata. |
| E | Error | Serialized server-side exceptions and error boundary handlers. |
Beyond these tags, the protocol relies heavily on a prefix system initiated by the $ character. When the client-side parser (ReactFlightClient.js) encounters a string beginning with $, it triggers type-specific resolution paths:
$(Model Reference): Resolves to another chunk within the active stream.$:(Property Access): Traverses deeply into a resolved chunk’s properties (e.g.,$1:user:name).$F(Server Reference): Represents a callable Server Action or RPC endpoint.$L(Lazy Component): Defers component loading until rendering demands it.$@(Promise/Raw Chunk): Returns the internal wrapper object rather than its resolved value.
This prefix system transforms what developers treat as inert data into active code execution directives.
Official Responses and Remediation Frameworks
The core vulnerability behind React2Shell resided in getOutlinedModel (found in ReactFlightReplyServer.js), specifically within the property traversal logic driven by the $: prefix:
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]];
Because the parser lacked a basic hasOwnProperty check, an attacker could supply a specially crafted path such as $1:__proto__:constructor:constructor. This sequence allowed the traversal loop to walk upward from a plain JSON object through Object.prototype, accessing the native Object constructor and ultimately reaching the Function constructor. In JavaScript, the Function constructor behaves identically to eval(), enabling arbitrary code execution.

The Vendor Response
The React core team issued targeted patches in React versions 19.0.1, 19.1.2, and 19.2.1. The fix cached the native hasOwnProperty method at module load time:
var hasOwnProperty = Object.prototype.hasOwnProperty;
Subsequent property checks in the deserialization pathway were rewritten to use .call():
hasOwnProperty.call(value, i);
While this successfully neutralized the specific prototype pollution gadget chain used in React2Shell, it left the foundational architecture—the property traversal model itself—largely unchanged. Subsequent patches were required to address recursive Denial-of-Service vulnerabilities and memory exhaustion flaws.
Implications: Securing Applications Against Structural Deserialization Risks
Because framework-level patches address symptoms rather than the foundational nature of streaming deserialization protocols, engineering teams must implement a robust defense-in-depth strategy.
1. Mandatory Schema Validation on Server Actions
Never trust input arriving via Server Actions. Because the Flight deserializer processes unvalidated network data before application logic executes, strict schema validation using libraries like Zod or Valibot must occur as the very first operation inside every Server Action:
"use server"
import z from "zod"
const ActionSchema = z.object(
id: z.string().uuid(),
value: z.string().min(1).max(255),
)
export async function handleAction(input: unknown)
const parsed = ActionSchema.safeParse(input)
if (!parsed.success)
throw new Error("Invalid payload structure")
// Proceed securely with parsed.data
2. Strategic Isolation with server-only
Enforce structural boundaries by importing the server-only package at the top of sensitive modules containing database connectors or private keys. This guarantees that accidental imports into Client Components fail at build time rather than leaking secrets to the browser.
3. Hardening CSRF and Origin Checks
In light of bypasses related to Origin: null handling (such as CVE-2026-27978), teams must ensure session cookies enforce strict SameSite policies and explicitly avoid weakening framework defaults by adding 'null' to allowed origin configurations.
4. Continuous Vulnerability Management
Given the velocity at which auxiliary deserialization and DoS flaws (CVE-2025-55184, CVE-2025-67779, CVE-2026-23864) were discovered following the initial React2Shell disclosures, automated dependency auditing must be integrated into CI/CD pipelines to ensure runtime environments consistently run fully patched versions of React and related bundler packages.
Conclusion
The emergence of React2Shell serves as a powerful reminder that convenience and performance optimizations often carry hidden architectural costs. As the web engineering community continues to embrace server-driven UI patterns and complex streaming protocols like Flight, security can no longer be treated as an afterthought or outsourced entirely to framework maintainers. By combining rigorous input validation, architectural separation, and proactive dependency management, developers can build resilient applications capable of withstanding the structural realities of modern deserialization sinks.

