On 3 December 2025, Meta disclosed CVE-2025-55182, a CVSS 10.0 remote code execution vulnerability in React Server Components. The security community named it React2Shell. A single HTTP POST request to a server endpoint is sufficient: no authentication, no user interaction, no prior foothold, and no developer error in your application code. The vulnerability lives in the framework itself.
React2Shell is not a story about a bug. It is part of the broader React Server Components security crisis — a story about what happens when a framework’s internal communication protocol, designed assuming trusted origins, gets exposed to untrusted HTTP by architectural default. This article walks through the exploit mechanism, explains why the React Flight protocol’s deserialisation process created the attack surface, and places React2Shell in context against decades of unsafe deserialisation vulnerabilities across other ecosystems. By the end, you will understand why this vulnerability class is different from what the industry has seen before.
What Is React2Shell and Why Did It Earn a CVSS 10.0 Score?
CVE-2025-55182, colloquially named React2Shell, is a remote code execution vulnerability in the React Flight protocol’s server-side deserialiser. Security researcher Lachlan Davidson privately reported it to Meta’s bug bounty programme on 29 November 2025. The React team shipped a patch within 48 hours, and public disclosure followed on 3 December.
The CVSS 10.0 score reflects a worst-case vector: network attack vector (AV:N), low attack complexity (AC:L), no privileges required (PR:N), no user interaction (UI:N), and scope changed (S:C) with high impact across confidentiality, integrity, and availability. In practice, this means any internet-facing React Server Components application running a vulnerable version is easily exploitable.
To understand why, you need to know where the vulnerability sits. React Server Components (RSC) run on the server with access to databases, filesystems, and environment variables. They communicate with clients through the React Flight protocol, which serialises component trees, props, and execution context into chunked streams. The affected packages are react-server-dom-webpack, react-server-dom-parcel, and react-server-dom-turbopack, the server-side Flight deserialisers used by Next.js, React Router, Waku, and others.
The blast radius data is substantial. Wiz Research found that 39% of cloud environments contain vulnerable React or Next.js instances, and 44% of all cloud environments have publicly exposed Next.js applications. Default Next.js App Router configurations were vulnerable out of the box because RSC support is the default. This ended React’s 13-year security record, the only previous blemish being a minor XSS in 2018. Cloudflare observed 582 million React2Shell-related hits in the first eight days after disclosure. Exploitation began almost immediately, accelerated by public proof-of-concept code and a Metasploit module.
That volume of attacks is why understanding the exploit mechanism matters. Let’s walk through exactly how a single request achieves code execution.
How Does a Crafted HTTP Request Become Remote Code Execution?
The exploit chain relies on three JavaScript mechanisms, each explored below: prototype pollution, thenable resolution, and context confusion. None of them require you to have written vulnerable code. The framework’s own deserialiser does all the work.
Here is the chain, step by step. First, the attacker crafts a Flight payload containing malicious JSX properties with __proto__ key paths. A path like $1:__proto__:constructor:constructor traverses the JavaScript prototype chain from any data object to the global Function() constructor. When the server deserialises this payload, the Flight deserialiser assigns the __proto__ property without checking if the object actually owns it, polluting Object.prototype globally on the server process.
This is prototype pollution: every plain object in the entire Node.js process now carries the attacker’s injected properties. When the attack sets Object.prototype.then to point at Function(), the deserialiser’s behaviour changes in a predictable and exploitable way.
JavaScript treats any object with a .then() method as a “thenable” under the promise specification. When the runtime encounters await obj and obj.then exists, it automatically invokes .then(resolve, reject) with two internal callbacks. The Flight deserialiser eagerly resolves thenables via await, and because Object.prototype.then now points to Function(), calling .then(resolve, reject) on any deserialised object invokes Function(resolve, reject). The attacker now has a code execution primitive.
The third mechanism is context confusion, a Confused Deputy attack. The reviveModel() function is a privileged internal function that processes chunk data during deserialisation. By supplying a malicious _response object as the revival context, the attacker redirects reviveModel() to dereference _response._formData.get as the Function() constructor and _response._prefix as the code payload. The function executes with server privileges but reads from attacker-controlled context objects. The $@chunkId raw reference syntax is abused to return raw chunk objects with their metadata and prototypes intact, breaking the boundary between data and runtime.
Weaponised payloads in the wild use Chunk.prototype.then rather than Object.prototype.then for stability, avoiding global side effects. Blob resolution through $B references provides an alternative pathway by redirecting through a hijacked _response._formData.get. The entire chain is deterministic across all vulnerable instances because Function(), the prototype chain, and thenable resolution exist in every Node.js runtime.
This is not a code defect. It is an architectural exposure.
Why Does the React Flight Protocol’s Deserialisation Process Create an Attack Surface?
The React Flight protocol was designed to transport component trees, module references, props, state, promises, and execution contexts between server and client as incremental chunks. It was built for trusted client-to-server communication where the client is a React application, not an attacker.
When React Server Components expose this protocol through HTTP, that trust assumption collides with reality. In Next.js App Router, Flight payloads arrive at /_rsc endpoints by default, without authentication required. The server’s Flight deserialiser reconstructs objects and resumes execution through a pipeline that begins with decodeReply() and flows into reviveModel(). Server Actions, the 'use server' directive, expose Flight deserialisation endpoints even when you have written no explicit Server Functions. The framework registers these endpoints automatically.
The deserialisation pipeline lacked three validations that would have prevented exploitation: property ownership checks (hasOwnProperty), restrictions on __proto__ traversal in property paths, and restraint around eager thenable resolution. All three behaviours are safe when processing trusted internal payloads. They become dangerous when processing attacker-supplied HTTP request bodies.
The comparison with JSON is instructive. JSON is a deliberately limited data format carrying no execution context, no code, no object methods, just data structures. REST APIs using JSON avoid the entire class of deserialisation vulnerabilities by design. The Flight protocol, by contrast, carries rich execution semantics like promises, closures, and module references to enable seamless server-client integration. Those same semantics become exploitation primitives when the deserialiser processes untrusted input.
A protocol designed for trusted internal use was made accessible from untrusted HTTP, and the security properties of that protocol became the security properties of every application using the framework. The trust-model analysis of why the trust model made this attack possible explores this architecture in depth.
Understanding why the Flight protocol created this surface at all helps explain how React2Shell differs from every deserialisation vulnerability that came before it.
How Is React2Shell Different from Traditional Unsafe Deserialisation Vulnerabilities?
Traditional unsafe deserialisation follows a well-worn pattern across Java, PHP, Python, and Ruby. A developer explicitly accepts and deserialises untrusted data using mechanisms like Java’s ObjectInputStream, PHP’s unserialize(), Python’s pickle, or Ruby’s Marshal.load. It is a documented antipattern with decades of CVEs. The fix is straightforward: stop deserialising untrusted input, or switch to a safe format like JSON.
React2Shell breaks that pattern. You never choose to deserialise untrusted data. The framework does it automatically as part of its normal server-side rendering pipeline. You may not even know deserialisation is occurring. It is invisible infrastructure, not an explicit application-level decision.
The gadget chain comparison makes this difference concrete. Java ObjectInputStream exploitation requires specific vulnerable classes on the classpath, tools like ysoserial generate payloads for known chains, and exploitation is often application-specific. React2Shell’s gadget chain is universal: every RSC server has Function(), the prototype chain, and thenable resolution. Exploitation is deterministic across all vulnerable instances, with no application-specific vulnerable classes required.
PHP unserialize() attacks typically need magic methods like __wakeup and __destruct to form a Property-Oriented Programming chain, and they require the application to explicitly deserialise user input from cookies or POST parameters. React2Shell needs neither explicit developer acceptance of untrusted data nor application-specific magic methods. The JavaScript runtime provides the gadgets universally.
The Log4Shell comparison is particularly useful. Both are CVSS 10.0 framework-level RCE vulnerabilities with the X-to-Shell naming pattern. Both triggered widespread emergency patching and were exploited by attackers at scale. But Log4Shell exploited a feature, JNDI lookups in log messages, that organisations could disable as a workaround. React2Shell exploits the communication protocol of the framework, which cannot be disabled without abandoning React Server Components entirely.
The blast radius comparison reinforces the point. Log4Shell required a crafted log message reaching a vulnerable Log4j instance. Rails YAML deserialisation required an endpoint accepting YAML parameters. React2Shell requires only that your application uses React Server Components, which is the default for Next.js App Router. The blast radius is defined by framework adoption, not your specific configuration choices.
What distinguishes React2Shell as a vulnerability class is this: when a framework’s internal communication protocol carries execution context and is exposed to untrusted input by architectural default, the framework’s abstractions become the attack vectors, and adoption becomes exposure. This is a supply-chain vulnerability pattern, not a coding-error pattern.
The exploit mechanism described here is the foundation for understanding the recurring vulnerability pattern that followed in React Server Components. The real-world exploitation evidence at /react2shell-in-the-wild-exploitation-evidence-and-the-platform-defence-landscape shows how attackers operationalised this unique attack surface at scale.
What React2Shell demonstrates is that the security properties of a framework are not just about the code you write on top of it. They are about the internal protocols the framework exposes, the trust assumptions baked into those protocols, and whether those assumptions survive contact with untrusted networks. For React Server Components, the answer turned out to be no. Understanding why is the starting point for assessing every other framework your organisation depends on. For the full picture, how the ecosystem is defending against exploitation is covered in the pillar overview.
Frequently Asked Questions
How do I know if my application is vulnerable to React2Shell?
Your application is vulnerable if it uses React Server Components with a react-server-dom-* package version prior to 19.0.1, 18.3.2, or 18.2.1, or a Next.js version prior to 14.2.22 (14.x), 15.0.5 (15.x), or 15.1.4 (15.1.x). The vulnerability exists by default in Next.js App Router. Check your lockfile for the affected packages. If your application registers RSC endpoints (commonly /_rsc paths), it processes Flight payloads and may be exploitable.
Is React on the client side affected, or only server components?
Only server-side React is affected. The vulnerability exists in the Flight protocol deserialiser, which runs exclusively on the server to reconstruct component trees from client requests. Client-side React rendering, including react-dom, createRoot, and all browser-based React code, is not vulnerable to CVE-2025-55182. However, a compromised server can serve malicious content to clients, so the blast radius extends to users of an exploited application.
What version of React patches this vulnerability?
React 19.0.1, 18.3.2, and 18.2.1 include the fix. For Next.js users, patched versions are 14.2.22, 15.0.5, 15.1.4, and 15.2.0 or later. React Router 7 in library mode requires version 7.2.0 or later, while Waku requires 0.21.20 or later. The React team shipped patches within 48 hours of the private disclosure on 29 November 2025. Upgrading the framework is the only complete mitigation.
Can a web application firewall block React2Shell attacks?
A WAF can provide partial defence by detecting known payload signatures, particularly __proto__ in request bodies and Flight protocol chunk patterns. However, signature-based detection is inherently fragile. Attackers can mutate payload encoding, use blob resolution pathways ($B references), or shift to Chunk.prototype.then instead of Object.prototype.then to evade static rules. WAFs are a useful stopgap but not a substitute for patching the framework.
Was React2Shell being exploited in the wild before the public disclosure?
Current evidence suggests no. The vulnerability was reported privately to Meta on 29 November 2025 and patched before public disclosure on 3 December 2025. However, exploitation began within days of the public announcement, accelerated by proof-of-concept code and a Metasploit module. Wiz and other security vendors observed scanning and exploitation attempts targeting internet-facing Next.js instances shortly after disclosure.
Does this vulnerability affect React Native applications?
No. React Native does not use React Server Components or the React Flight protocol. The vulnerability is specific to the server-side Flight deserialiser packages (react-server-dom-webpack, react-server-dom-parcel, react-server-dom-turbopack). React Native applications render entirely on the client and do not expose Flight deserialisation endpoints. However, if a React Native app communicates with a vulnerable RSC backend, that backend remains exploitable independently.
What exactly happens on the server when the exploit succeeds?
The attacker achieves code execution with the privileges of the Node.js process running the React server. This grants access to environment variables, database credentials, filesystem operations, and outbound network connectivity. In observed attacks, payloads have deployed cryptominers, exfiltrated environment secrets, established reverse shells, and enumerated cloud metadata endpoints. The entire server process is compromised at the operating system level, not just the application layer.
Why is the vulnerability called React2Shell?
The name mirrors the X-to-Shell naming convention popularised by Log4Shell (CVE-2021-44228), indicating a framework-level remote code execution vulnerability that grants shell access. “React2Shell” describes the attack trajectory: a crafted React Flight payload (React) leads to shell command execution on the target server (Shell). Security researchers adopted the name during the disclosure period, and it became the vulnerability’s colloquial identifier across the industry.
Are applications hosted on Vercel automatically protected from React2Shell?
Vercel deployed mitigations for its platform infrastructure shortly after the disclosure, but this does not automatically protect individual applications. The Vercel platform can apply WAF-like filtering at the edge, but the application itself remains vulnerable if running an unpatched framework version. Vercel recommended customers upgrade their Next.js deployments. Platform-level protections are a supplementary defence layer, not a replacement for patching.
If I am using Next.js Pages Router instead of App Router, am I safe?
Applications using only the Pages Router are not affected by React2Shell because Pages Router does not implement React Server Components or expose Flight protocol deserialisation endpoints. The vulnerability requires the react-server-dom-* deserialiser to process untrusted Flight payloads. However, if your application uses both App Router and Pages Router (a common hybrid migration pattern), any RSC-enabled route creates an exploitable surface regardless of which router serves the majority of traffic.