CVE-2025-55182: The React2Shell Vulnerability Explained
Published on
/9 mins read
I was reviewing a PR when the security alert hit our team Slack. "CVSS 10.0. React Server Components. Pre-auth RCE." I stopped mid-review. This site runs Next.js 15. So does every project I've shipped this year.
TL;DR: CVE-2025-55182 is a critical remote code execution vulnerability in React 19's Server Components. Attackers can execute arbitrary code through a single HTTP POST request—no authentication, no special configuration, no developer mistakes required. Even a fresh create-next-app project is immediately exploitable in production. Patch now: React 19.0.1/19.1.2/19.2.1 and Next.js 15.0.5+/16.0.7.
Check if you're vulnerable right now:
# Check your React versionnpm ls react-server-dom-webpack 2>/dev/null | grep -E "19\.(0\.0|1\.[01]|2\.0)" && echo "⚠️ VULNERABLE" || echo "✓ Not using vulnerable RSC packages"# Check your Next.js version npm ls next 2>/dev/null | grep -oE "next@[0-9.]+" | head -1
If you see versions 19.0.0, 19.1.0, 19.1.1, or 19.2.0 for React RSC packages—or Next.js 14.3.0-canary.77 through unpatched 16.x—stop reading and patch first.
Security researcher Lachlan Davidson discovered what's being called the most severe JavaScript ecosystem vulnerability since Log4Shell. Disclosed two days ago on December 3, 2025, this flaw earned the nickname "React2Shell"—and the comparison is earned. It's the fourth major Next.js security incident this year, but the first to hit everyone—Vercel-hosted and self-hosted alike.
According to Wiz Research, 39% of cloud environments contain vulnerable instances. Palo Alto Networks Unit 42 identified over 968,000 servers running affected React/Next.js deployments. If you're running React 19 with Server Components, you're almost certainly affected.
The four-day sprint from discovery to patch
The timeline here is impressive—and terrifying. Davidson reported to Meta's Bug Bounty program on November 29, 2025. Meta confirmed the vulnerability the next day. By December 1st, a fix was ready and coordination with major hosting providers began.
Vercel, Cloudflare, Google Cloud, Akamai, Fastly deployed WAF rules
The related CVE confusion: CVE-2025-66478 was initially assigned for Next.js, but the NVD rejected it as a duplicate—Next.js simply inherited the vulnerability from its React dependency. One root cause, one CVE.
Inside the deserialization flaw
The vulnerability lives in React's Flight protocol—the serialization mechanism that transports component trees and Server Function calls between client and server. Three functions in react-server-dom-webpack made this attack possible.
The vulnerable requireModule function
Here's the critical code from version 19.0.0 (around lines 2546-2558):
That last line—moduleExports[metadata[2]]—is the problem. JavaScript bracket notation traverses the entire prototype chain, not just the object's own properties. The code never validates that the requested export is a legitimate, developer-defined export.
How attackers exploit the Flight protocol
The exploitation flow:
HTTP POST to any Server Function endpoint (e.g., /formaction)
decodeAction(formData, serverManifest) processes the request
Two supporting functions complete the attack chain:
reviveModel: Deserializes JSON from clients. The original implementation lacked __proto__ injection protection, enabling prototype pollution that modifies Object.prototype server-wide.
loadServerReference: Takes the bound array from attacker payloads and passes it to fn.bind.apply(), letting attackers specify arbitrary arguments to dangerous functions.
The attack in action
An attacker crafts a malicious HTTP POST to any Server Function endpoint. The payload specifies a module and export name that, through prototype chain traversal, references dangerous Node.js built-ins.
Step-by-step exploitation
Step 1: Send a crafted POST request:
curl -X POST http://target:3000/formaction \ -F '$ACTION_REF_0=' \ -F '$ACTION_0:0={"id":"vm#runInThisContext","bound":["require(\"child_process\").execSync(\"whoami\")"]}'
Step 2: The server deserializes the Flight payload, treating vm#runInThisContext as the module/export reference.
Step 3: requireModule resolves vm as a Node.js built-in and accesses runInThisContext via bracket notation.
Step 4: The bound array becomes arguments via fn.bind.apply().
Step 5: Arbitrary JavaScript executes in the server context. Full RCE achieved.
Every metric at maximum severity: network-accessible, low complexity, no authentication, no user interaction, with complete impact on confidentiality, integrity, and availability. The CVSS 3.1 vector is AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H.
This isn't theoretical—it's a single-request, unauthenticated, complete server takeover.
Next.js (all versions from 14.3.0-canary.77 through 16.x before patches)
Other affected frameworks
If you're using React Server Components outside Next.js, check these:
React Router (RSC mode)
Waku
@parcel/rsc
@vitejs/plugin-rsc
rwsdk (RedwoodSDK)
Expo
Exposure statistics
Metric
Value
Source
Cloud environments affected
39%
Wiz Research
Cloud environments with Next.js
69%
Wiz Research
Publicly exposed Next.js instances
44%
Wiz Research
Public servers identified
968,000+
Palo Alto Unit 42
JS developers using React
82%
2024 State of JavaScript
You're already being scanned
A developer reported exploitation attempts within 12 hours of public disclosure. Attackers were scanning routes like /login and /formaction even on low-traffic applications with minimal SEO presence. If it's on the internet, assume it's being probed.
Check your server logs for these patterns:
# Grep for suspicious Server Action payloadsgrep -E '\$ACTION_|vm#|child_process#|fs#write|module#_load' /var/log/nginx/access.log# Look for POST requests to common Server Function endpointsgrep -E 'POST.*(formaction|_rsc|__nextjs)' /var/log/nginx/access.log | grep -v 200# Check for vm/child_process references in request bodies (if logging bodies)zgrep -i 'runInThisContext\|execSync\|spawnSync' /var/log/*/access*.log*
If you find matches with status codes 200 or 500, assume compromise and initiate incident response. Rotate secrets, check for persistence mechanisms, and audit for lateral movement.
How Meta fixed it
React PR #35277 synchronized the FlightReplyServer deserialization logic with previously hardened client-side code. The core fix adds hasOwnProperty checks:
hasOwnProperty checks in requireModule to prevent prototype chain traversal
Refactored reviveModel to handle __proto__ keys safely during JSON deserialization
Strengthened loadServerReference to validate module references against the legitimate server manifest
What you need to do right now
Immediate actions
Upgrade React packages to 19.0.1, 19.1.2, or 19.2.1
Upgrade Next.js to 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, or 16.0.7
Verify WAF protection if using Vercel, Cloudflare, or other protected platforms
Check logs for unusual POST requests to Server Function endpoints
Audit dependencies for frameworks using affected React packages
Platform-level protections already deployed
Provider
Status
Vercel
WAF rules auto-deployed for all hosted projects (no cost)
Cloudflare
Managed WAF rules deployed before public disclosure
Google Cloud
Cloud Armor preconfigured WAF rule (cve-canary) available
Akamai
Adaptive Security Engine Rapid Rule 3000976
Fastly
NGWAF Virtual Patch released
Firebase
Automatic protections for Hosting and App Hosting
Netlify
Platform-level patch deployed December 3rd at 14:00 UTC
Self-hosted deployments remain fully exposed until you patch. Platform WAF rules only protect platform-hosted applications.
The real lesson: JavaScript's prototype chain is a security liability
The generic advice—"never trust client input"—isn't what makes this vulnerability interesting. What makes it interesting is how the trust was violated.
The expression moduleExports[metadata[2]] looks innocuous. It's just property access, right? But in JavaScript, bracket notation traverses the entire prototype chain. When you write obj["constructor"], you don't get undefined—you get Object. When you write obj["__proto__"], you get the prototype itself. This isn't a bug; it's the language working as designed.
The fix—hasOwnProperty.call(moduleExports, metadata[2])—is a one-liner. But someone had to know to add it. The React team hardened the client-side Flight deserialization years ago. The server-side code, added later for Server Components, missed the same check.
The pattern to internalize: Any time you're accessing object properties with user-controlled keys, you need hasOwnProperty guards. This applies to:
Deserialization logic
Dynamic property access from URL params, form data, or JSON
Any obj[userInput] pattern
ESLint can't catch this. TypeScript won't warn you. It requires developers to know that JavaScript's object model is adversarial by default.
Conclusion
CVE-2025-55182 is the worst-case scenario we always knew was possible: unauthenticated RCE in default configurations of the world's most popular JavaScript framework. The four-day turnaround from report to patch is genuinely impressive. The 39% exposure rate two days later is genuinely terrifying.
I patched my projects within an hour of the disclosure. If you haven't yet, do it now—before you finish reading this sentence.
And if you're feeling generous, Lachlan Davidson probably deserves more than a coffee for this one.