React2Shell: how the React Flight protocol became an RCE attack vector 🚨
React2Shell and CVE-2025-55182 explained: React Flight deserialization, the exploit chain and defensive measures.

The move to React Server Components (RSC) and Server Actions was presented as the ecosystem’s biggest architectural update in years. But blurring the boundary between client and server brought fundamental security risks with it.
Smashing Magazine has published a detailed analysis of CVE-2025-55182 (known as React2Shell), which received the maximum severity score: CVSS 10.0.
At the heart of the problem is the text-based React Flight protocol, used to stream data and reconstruct the UI tree from the server on the client.
🧠 What is the vulnerability in the Flight protocol?
Unlike plain JSON, the newline-delimited Flight protocol transmits references to executable code, RPC endpoints and client modules. Put simply, every Flight stream is a deserialization boundary.
The root cause of the critical RCE turned out to be mundane: the getOutlinedModel function in React’s internal packages lacked a hasOwnProperty check. This enabled an attack chain through prototype chain traversal.
⚡️ The anatomy of the exploit in 5 steps:
- Walking the prototype chain: Using Flight’s prefix syntax (
$:), an attacker moves from a base object toObject.prototype➔Object➔ and reaches the built-inFunctionconstructor (a native equivalent ofeval()). - Intercepting internal state: The
$@0reference returns a rawChunkinstead of a resolved value, exposing mutable access to the internals of React’s pipeline. - Thenable hijack: Overwriting a chunk’s
.thenmakes the React runtime think it is a Promise and invokeawait. - Replacing the context: On a second deserialization pass,
_response._formData.getis redirected to the capturedFunction, while the shell command is hidden in_response._prefix. - Triggering execution: Invoking the
$B0blob executesFunction("attacker_command")(). The result is code running directly on the server with the Node.js process’s privileges.
🛡 What should you do, and how can you defend against it?
The problem demonstrated that treating a Flight stream as “internal framework magic” is no longer acceptable: it is untrusted external input.
- Patch versions right now: Upgrade
react,react-domand Next.js to recent releases with the correctedhasOwnPropertycheck. - Validate Server Actions: Do not rely on TypeScript’s automatic typing at runtime. All Server Action arguments must be strictly validated with Zod or Valibot at the entry point.
- Isolate modules with
server-only: Explicitly addimport 'server-only'to all files containing business logic and secrets to keep them out of the Flight context. - Configure a WAF: Filter incoming Server Action requests for anomalies such as
$:and__proto__.