On December 3, 2025, a security vulnerability shook the React ecosystem. CVE-2025-55182, dubbed "React2Shell," received a perfect CVSS score of 10.0. This wasn't just another bug—it revealed a fundamental structural problem facing modern web development.
This article explores why React2Shell happened and how it relates to the absence of web standards for partial rendering.
The first vulnerability disclosed on December 3rd was a Remote Code Execution (RCE) flaw in React Server Components (RSC)'s Flight protocol.
The core issue was insecure deserialization. A single unauthenticated HTTP request could completely compromise a server. Even a basic Next.js app created with create-next-app was vulnerable.
Within hours of disclosure, multiple threat actors—including China-nexus groups—actively exploited it, with attacks confirmed in production environments.
Days later, on December 11th, security researchers analyzing the initial patch discovered two additional vulnerabilities:
- CVE-2025-55184: Denial of Service (DoS). Infinite Promise recursion could freeze Node.js servers.
- CVE-2025-55183: Source code exposure. Calling
.toString()on Server Functions could leak hardcoded API keys and secrets.
The initial patch was incomplete, necessitating yet another patch: CVE-2025-67779.
A thought occurred to me:
"React started as a browser UI library. Did something go wrong when it extended to the server?"
React Server Components created a custom messaging pattern called Flight Protocol. Rather than using standard HTTP/REST APIs, servers and clients communicate using React's proprietary serialization format.
Traditional HTTP works like this:
Client → [HTTP Request] → Server
Server → [JSON/HTML] → Client
✓ Clear boundaries
✓ Proven formats
✓ Established security best practices
But the Flight Protocol:
Client → [React Serialized Payload] → Server
Server → [React Serialized Payload] → Client
✗ Custom serialization format
✗ Transmits JavaScript objects directly
✗ Serializes function references and closures
The React team aimed to maximize Developer Experience (DX):
'use server'
async function updateUser(formData) {
await db.update(formData);
}
// Call from client "as if it were a local function"
<form action={updateUser}>Making the server-client boundary transparent seemed elegant. But there was a trap.
A fundamental security principle states: "Define clear boundaries and thoroughly validate untrusted input."
React intentionally obscured this boundary, and as a result, both developers and React itself neglected validation at the boundary.
This pattern isn't new:
- Java RMI: "Call remote objects like local ones" → Countless deserialization vulnerabilities → Abandoned by most enterprises
- PHP's
unserialize(): Transmitting object serialization via HTTP → RCE vulnerabilities → Deprecated - SOAP/XML-RPC: Complex type systems and serialization → Security issues → Replaced by REST/JSON
In the trade-off between "developer experience" and "security," high-level abstractions provide convenience but simultaneously blur security boundaries.
But we need to think one step further.
Isn't HTTP itself too outdated to keep pace with the complexity of modern web applications?
HTTP is a protocol created in 1991. Its essence:
- Resource-centric (URL = Resource identifier)
- Request/Response model
- Stateless
But modern web apps require:
- Component-centric (URL ≠ Component)
- Streaming/Incremental updates
- Stateful interactions
HTTP is a "document transfer protocol," not an "application protocol."
Consider when a user adds a single comment:
// Method 1: Full page reload
window.location.reload(); // ← 2000s approach
// Method 2: Fetch JSON and render on client
const comment = await fetch('/api/comments', {method: 'POST'});
setComments([...comments, comment]); // ← React's way
// Method 3: Fetch HTML fragment and insert
const html = await fetch('/api/comments/latest');
container.innerHTML = html; // ← htmx way
// Method 4: RSC - "magic"
// ← React's attempt, but with security issuesAll of these are workarounds.
Currently, each framework solves the same problem differently:
- React: RSC (Flight Protocol)
- Phoenix: LiveView (WebSocket + diffing)
- Laravel: Livewire (Ajax + Morphdom)
- Hotwire: Turbo Streams
- htmx: HTML over the wire
- Qwik: Resumability
Each invents its own serialization format, there's no interoperability, and security models vary.
The React incident demonstrates that the absence of standards encourages frameworks to create dangerous custom protocols.
I believe we need a web standard for partial rendering.
1. Component Addressing
- URLs alone are insufficient
- Methods to identify specific components within a page
2. Incremental Transfer
- Transfer only changed portions, not the whole
- Streaming support
3. State Synchronization
- Synchronize state between server and client
- Handle optimistic updates
4. Security Model
- Which components execute with server privileges?
- How to validate client input?
HTTP Extension:
PATCH /page HTTP/2
Content-Type: application/component-update+json
X-Component-Path: /comments/new
{
"component": "Comment",
"props": { "text": "Hello" },
"action": "append"
}
Or HTML Extension:
<!-- Server response -->
<template component="Comment" action="append" target="#comments">
<div class="comment">New comment</div>
</template>But creating standards isn't easy:
- Who will define it? W3C/WHATWG is slow, and framework communities are fragmented.
- The abstraction level problem: Too low-level is hard to use; too high-level lacks flexibility.
- Conflicts with existing infrastructure: Will CDN, proxy, cache, WAF, and IDS understand new protocols?
The React2Shell incident isn't just an implementation bug. It resulted from:
- HTTP standards failing to keep pace with modern web app requirements
- Each framework creating its own solution
- Ignoring security principles
React's mistake wasn't the absence of standards—it was ignoring principles (explicit boundaries, input validation) that should be followed regardless of standards. However, if standards had existed, React likely wouldn't have created a dangerous custom protocol.
We need web standards for partial rendering.
Standardization takes time and won't be perfect, but it's better than each framework inventing dangerous protocols. Just as TypeScript became a de facto standard despite not being official, major frameworks could collaborate to find common ground and propose it to browser vendors.
Until then, it's crucial for developers to prioritize "clear boundaries" and "input validation" over the convenience of "magic."
- React Official Blog: Critical Security Vulnerability in React Server Components
- Wiz Research: React2Shell (CVE-2025-55182)
- AWS Security Blog: China-nexus cyber threat groups rapidly exploit React2Shell
- React Official Blog: Denial of Service and Source Code Exposure
This article was written based on conversations with Claude.