Skip to content

React2Shell

One unauthenticated HTTP request could run code on any server using React Server Components. This is how it worked, one layer at a time, and what to do about it.

  • CVE-2025-55182
  • CVSS 10.0
  • Exploited in the wild
  • CISA KEV · 2025-12-05

In the drawing, the request steps inward layer by layer and turns amber where the decoder lets it into JavaScript's own machinery.

The request's path into the server, drawn as nested layersSix nested layers recede toward the lower right: HTTP POST, Next.js router, Flight reply decoder, reference resolver, prototype chain, and Function. A pale line steps inward through the first four. Where it crosses into the prototype chain, at the missing own-property check, it turns amber and glows, and it ends inside Function, where code runs as the server process.HTTP POSTNext.js routerFlight reply decoderreference resolverprototype chainFunctionno own-property checkcode runs asthe server process

What happened

React's server-side decoder for Server Function calls followed property names chosen by the caller without checking they were the object's own data. By walking into JavaScript's built-in machinery, one HTTP POST could make the server run code. A default Next.js App Router app was exposed even if its developer never wrote a Server Function.

Reported 29 Nov 2025 by Lachlan Davidson through Meta's bug bounty; fixed and disclosed 3 Dec; scanning seen about six hours later; added to CISA's Known Exploited Vulnerabilities catalog on 5 Dec. There was no workaround: upgrade, and rotate secrets if you were exposed.1

Am I affected?

Only if your app runs React Server Components on a server. Vulnerable: react-server-dom-webpack, -parcel and -turbopack at 19.0.0, 19.1.0–19.1.1 and 19.2.0, and every framework that ships them, including Next.js 15.x and 16.x with the App Router. Not affected: Next.js 13.x, 14.x stable, the Pages Router and the Edge Runtime.8

A guide built from the advisories, not a scanner. Run npm ls next to see what your lockfile resolves. Try

Next.js release lines, App Router (GHSA-9qr9-h5gf-34mp)
LineVulnerableRCE fixed inDec 11 follow-up
14.3 canary → 15.0≥14.3.0-canary.77 <15.0.515.0.515.0.7
15.1<15.1.915.1.915.1.11
15.2<15.2.615.2.615.2.8
15.3<15.3.615.3.615.3.8
15.4<15.4.815.4.815.4.10
15.5<15.5.715.5.715.5.9
16.0<16.0.716.0.716.0.10
canariesbefore the fix15.6.0-canary.58 · 16.1.0-canary.1215.6.0-canary.60 · 16.1.0-canary.19
13.3 – 14.x stablenot RCE—14.2.35 (DoS)
React server packages and the vendored Vite plugin
PackageVulnerableRCE fixed inSafe floor today
react-server-dom-*19.0.0 · 19.1.0–19.1.1 · 19.2.019.0.1 · 19.1.2 · 19.2.119.0.8 · 19.1.9 · 19.2.8
@vitejs/plugin-rsc≤0.5.20.5.30.5.26

React Router (RSC APIs), Waku and RedwoodSDK consume the React packages directly: upgrade those packages and the framework. On a 14.3.0-canary.77 or later canary, Next.js advises downgrading to next@14 stable. Sources: GHSA-fv66-9v8q-g76r, GHSA-9qr9-h5gf-34mp, GHSA-fmh4-wr37-44fp, react.dev.

Do this now

  1. Upgrade the package that carries the decoder. For Next.js that is next itself: it bundles its own copy of React's server code, so bumping react changes nothing. Vercel ships npx fix-react2shell-next for this. Elsewhere, move react-server-dom-* to 19.0.8, 19.1.9 or 19.2.8.3
  2. Rotate secrets if the app was online and unpatched as of 4 Dec 2025, 13:00 PT (Next.js and Vercel guidance). Code execution exposes every environment variable the server can read.5
  3. Look for signs of compromise using the field marks below.
  4. Don't rely on a WAF rule. Every major provider shipped one, and React and Vercel both said they cannot catch every variant.
  5. Keep patching. The same decoder produced seven follow-up CVEs through July 2026.2
Contents
  1. Field marks
  2. How it works
  3. The hidden copy
  4. Timeline
  5. The moving floor
  6. Exploitation
  7. Defenses
  8. Related
  9. Sources

An attempt looks like a normal Server Function call. A hit leaves marks.

In requests

  • POST requests carrying the next-action or rsc-action-id headers, which AWS advises looking for. Normal Server Function calls carry them too, so treat them as a filter, not a signature.11
  • Scanning traffic from many addresses with randomized user agents, as AWS observed.

On a server that was hit

  • The Node.js process spawning shells, curl or wget.
  • Unexpected files in /tmp, and new systemd or cron entries for persistence.
  • Hands-on reconnaissance such as whoami, id and reading /etc/passwd, which AWS saw one cluster do by hand for about an hour.
  • Cryptocurrency miners (XMRig), backdoors and tunnelers, reported by Google, Microsoft and Wiz.12

Beware look-alikes

A proof of concept (PoC) is code published to show a flaw is real. Many early public PoCs for this one did not work, and some were AI-generated. Davidson, Google's threat intelligence group and others warned that scanners built on them gave false positives and false negatives. Check your version numbers rather than trusting a scanner's verdict.10

One request walked out of the data and into JavaScript's own machinery.

React2Shell was a flaw in deserialization: the step where a server rebuilds live JavaScript objects from the text a browser sent. The browser's message could steer that rebuilding into JavaScript's own machinery, and from there into running code. Nothing in the chain needed a login, and none of it ran the app's own code.

Scroll, or focus the stage and use the arrow keys. The amber dot is the request; the camera moves to the part of the server each step is about. Field names are conceptual; there is no exploit here.

  • Own propertyA property set on the object itself, not inherited. hasOwnProperty tells the two apart.
  • CWE-502The catalogue entry for deserialization of untrusted data: unsafe when the data controls more than it should.
Path of a React2Shell request through a Next.js serverAn HTTP client sends a POST with numbered Flight chunks. Inside the Node.js server process, the Next.js router hands it to React's Flight reply decoder, which runs before the app's own Server Function and auth checks. The decoder's reference resolver walks property paths with value = value[name]. Without an own-property check, that walk reaches inherited properties (__proto__, constructor, then). A planted then loops back into the parser with a fake chunk built from caller data, and constructor leads to the Function constructor, which runs code with the process's access to environment variables, cloud metadata, files, network and child processes. The fix adds a hasOwnProperty gate on the resolver's path.HTTP clientbrowser or any scriptno login, no session neededPOSTNext-Action: <id>multipart formchunk 0chunk 1chunk 2arguments{ a: { b } }ref 1 → a → bNode.js server processNext.js routermatches Next-Actionpicks which function to callYour Server FunctionYour auth checkswould run after the decoderFlight reply decoderrebuilds live objects from the chunksReactFlightReplyServer0 arguments1 { a: { b } }2 ref 1 → a → bform field 0an objecta referenceReference resolvervalue = value[name]for each name the caller sent: no own-property checkhasOwnProperty(value, name)Inherited propertieswhat value[name] also finds__proto__its prototypeconstructorwhat built itthenhow it resumesFunctionstring → coderuns as the serverprocessWithin reachenv variables · cloud metadata · files · network · child processesFake chunkbuilt from caller datainternal fields includedre-enters the parser
Diagram of the React2Shell request path. Focus it and use the left and right arrow keys to move between the eight steps listed after it; each step's text describes that part in full.

  1. A Server Function call is just an HTTP POST.

    React Server Components let part of a React app run on the server. A Server Function (also called a Server Action) is server code the browser can call over the network. Next.js sends that call as a POST request marked with a Next-Action header.

    Nothing about the request proves it came from your own page. Anyone can send one.

    method
    POST
    header
    Next-Action: <action id>
    body
    multipart form data
    login needed
    no
  2. The arguments travel in React's Flight format.

    The Flight protocol is React's own wire format between browser and server. It can say more than JSON can. Each numbered form field is a chunk, and a value inside one chunk can be a reference: roughly “chunk 1, then property a, then property b”.

    A value can also be marked as a promise, or point at an export of a server module.

    chunk 0
    the call's arguments
    chunk 1
    an object
    chunk 2
    a reference: 1 → a → b
    promises
    allowed
    module refs
    allowed
  3. The decoder runs before any of your code.

    To know which function to call and with what, the server first rebuilds live JavaScript objects from the chunks. That happens inside React's Flight reply decoder.

    It runs before your Server Function and before any login check you wrote. Datadog and the discoverer both confirmed that a stock create-next-app production build was exploitable.14

    your functions
    none needed
    auth checks
    not run yet
    app code
    not run yet
    decoder
    ReactFlightReplyServer
  4. A reference is resolved by walking names.

    For “chunk 1 → a → b” the resolver starts at chunk 1 and steps through each name in turn: value = value[name].

    In the vulnerable versions, nothing checked that each name was the object's own data. Any name the caller sent was followed. A second lookup, for server-module exports, had the same gap.6

    for each name
    value = value[name]
    own-property check
    none
    who picks names
    the caller
  5. In JavaScript, value[name] also finds inherited properties.

    Every JavaScript object has a prototype: a shared object it inherits behaviour from, which has its own prototype, and so on. That is the prototype chain. Ask an object for __proto__, constructor or a promise's then and you get that shared machinery, not the object's data.

    Because the resolver accepted any name, a reference could walk out of the data and into the machinery. Datadog describes this as prototype-chain traversal.

    obj["a"]
    the object's own data
    obj["__proto__"]
    its prototype
    obj["constructor"]
    the function that built it
    promise["then"]
    how it is resumed
  6. The decoder's own chunk is promise-like. That is the lever.

    A thenable is any object with a then method; JavaScript automatically calls it when a promise resolves to that object. According to the discoverer's write-up, React's internal chunk type inherited from Promise.prototype and had a then.10

    Using references, an attacker could reach a chunk and plant a then. Automatic promise unwrapping then re-entered the parser with a fake chunk built entirely from attacker data, internal fields included.

    chunk prototype
    Promise.prototype
    then
    reachable by reference
    fake chunk fields
    attacker data
    unwrapping
    automatic
  7. From constructor to Function, and the server runs code.

    Following constructor properties eventually reaches JavaScript's Function constructor, which turns a string into runnable code. The decoder calls it while it believes it is resolving an ordinary value.

    The code runs as the Node.js server process, with its environment variables, files and network. Attackers used it to read secrets and cloud credentials, then install crypto miners, backdoors and tunnels.13

    runs as
    the server process
    reach
    env vars, files, cloud metadata
    seen in the wild
    miners, backdoors, tunnelers
    login needed
    no
  8. The fix: only an object's own properties count.

    Sebastian Markbåge's PR #35277, merged 3 Dec 2025 at 15:41 UTC and released as 19.0.1, 19.1.2 and 19.2.1, steps into a name only when the value is an object and hasOwnProperty says the name is its own. Module-export lookups got the same guard.

    The chunk type was restructured, and the server's reply decoder was brought back in line with the client-side decoder it had drifted from.6

    step into name
    only if own property
    inherited names
    resolve to nothing
    module exports
    own exports only
    RCE
    closed; DoS follow-ups kept coming

The numbered markers on the diagram match the steps.

Next.js carried its own copy, so upgrading React changed nothing.

Next.js does not depend on React's server packages through npm. It ships its own bundled copy, a practice called vendoring. Upgrading react-server-dom-webpack in your package.json changes nothing for Next.js: you have to upgrade next. That is why Next.js published its own advisory, which “tracks the downstream impact”.3

@vitejs/plugin-rsc also vendors react-server-dom-webpack and got its own advisory. Frameworks that consume React's packages directly (Waku, React Router, RedwoodSDK) had no advisory of their own: the fix there is to upgrade the React packages.

Where the vulnerable decoder hides in a dependency treeYour package.json leads to three kinds of package. next contains a bundled copy of React's server runtime that a lockfile does not list. @vitejs/plugin-rsc contains a vendored react-server-dom-webpack. Waku, React Router and RedwoodSDK depend on react-server-dom packages that appear in the lockfile.Your apppackage.json · lockfilenextbundled React server runtimeUpgrade next itself.Bumping react-server-dom-* does nothing here.GHSA-9qr9-h5gf-34mp@vitejs/plugin-rscvendored react-server-dom-webpackIts own advisory.≤ 0.5.2 vulnerable, fixed in 0.5.3GHSA-fmh4-wr37-44fpwaku · react-routerrwsdkreact-server-dom-*listed in the lockfileUpgrade the React packages.No framework advisory was issued.solid: a lockfile entry you can seedashed: code hidden inside another package
Fig. 1. Where the decoder hides. A scanner keyed on react-server-dom-* versions sees only the bottom row. The two upper rows carry the vulnerable code inside another package, so each needed its own advisory and its own upgrade.

If your app's React code does not use a server, your app is not affected.

React, “Critical Security Vulnerability in React Server Components”, 3 Dec 2025
  • VendoringCopying a dependency's code into your own package instead of declaring it. The copy does not appear as its own entry in a lockfile.
  • GHSAA GitHub Security Advisory ID. Each vendoring layer needed its own.

Six hours from fix to scanning. Two days to the government's list.

  • 4days

    from the private report (29 Nov) to the public fix (3 Dec).

  • ~6h

    from the CVE's publication to scanning, seen by Datadog around 22:00 UTC.

  • 2days

    until CISA added it to the Known Exploited Vulnerabilities catalog.

  • 7

    follow-up CVEs in the same decoder, through July 2026.

React2Shell timeline, 29 November 2025 to July 2026Reported 29 November, confirmed 30 November, fix created 1 December. On 3 December at 15:40 UTC the CVE was published and the fix merged a minute later; advisories followed at 19:07; Datadog saw scanning from about 22:00. On 4 December AWS saw exploitation attempts within hours. On 5 December, 95 IPs were exploiting by 04:00, a public proof of concept and victims followed by 06:00, CISA added the flaw to KEV and the first miners appeared. Next.js later advised rotating secrets for apps unpatched at 13:00 PT on 4 December. Follow-up CVEs came on 11 December, Microsoft reported several hundred compromised machines on 15 December, and four more denial-of-service CVEs followed between January and July 2026.Nov 29Nov 30Dec 1Dec 3, 12:00Dec 4Dec 5Dec 6JanAprJul 2026Reported to MetaMeta confirmsFix createdCVE published 15:40 UTC; fix merged 15:41Advisories published, 19:07Scanning begins (Datadog), ~22:0095 IPs exploiting, 04:00Public PoC; victims, 06:00Dec 11: follow-up CVEsDec 15: several hundredmachines hit (Microsoft)4 more DoS CVEsAttempts withinhours (AWS)CISA adds to KEVFirst miners seen (Google)Duplicate CVE-2025-66478 rejectedNext.js: rotate secrets if still unpatched at this hourDec 4, 13:00 PT (21:00 UTC), advised Dec 6daysmonths, compressedhours, to scale (UTC)
Fig. 2. Report to mass exploitation. The middle is drawn to scale in hours; the ends are compressed. Brown marks attack activity. Sources: React (report, fix), CVE.org (publication, rejection), GitHub (advisories), Datadog (scanning), AWS (attempts), GreyNoise as reported by Wiz (95 IPs), Wiz (PoC and victims), CISA (KEV), Google Threat Intelligence (miners), Next.js (rotation advice), Microsoft (machines).

The headline fix held. The safe floor kept moving.

The remote code execution was closed on day one. But researchers looking hard at the same decoder kept finding ways to exhaust it: infinite loops, memory exhaustion and CPU burn. The first denial-of-service fix was itself incomplete. Seven follow-up CVEs landed over eight months.2

The lesson for anyone pinning versions: track the latest patch on your line, not “the version that fixed the headline CVE”.

The patch for React2Shell remains fully effective.

Next.js, “Security update, 11 December 2025”
Line55182
RCE · Dec 3
55183/4
Dec 11
67779
Dec 11
23864
Jan 26
23869
Apr 8
23870
May 6
44907
Jul 21
19.019.0.119.0.219.0.319.0.419.0.519.0.619.0.8
19.119.1.219.1.319.1.419.1.519.1.619.1.719.1.9
19.219.2.119.2.219.2.319.2.419.2.519.2.619.2.8
Fig. 3. First fixed version of react-server-dom-*, per CVE. Column headings abbreviate the CVE numbers (CVE-2025-55182, -55183, -55184, -67779; CVE-2026-23864, -23869, -23870, -44907). CVE-2025-55183 is source-code exposure (CVSS 5.3); the rest after the first column are denial of service (7.5). CVE-2025-67779 exists because the 55184 fix was incomplete. The last column is today's floor. Source: CVE.org records and the React GHSAs.

Next.js shipped matching releases. The 11 December 2025 fixes (14.2.35, 15.0.7, 15.1.11, 15.2.8, 15.3.8, 15.4.10, 15.5.9, 16.0.10) also reached Next.js 13.3 and later, so 13.x and 14.x users who were safe from the RCE still had to move to 14.2.35. Further releases followed on 28 January, 10 April and 11 May 2026. As of Next.js's 20 July 2026 security release, the newest patches are 15.5.21 and 16.2.11.4

  • Denial of service (DoS)Making a service unavailable, here by sending input that makes the decoder loop, run out of memory or burn CPU.

Attackers arrived within hours, from many directions.

Each line is one organization's own report. Counts come from different vantage points and don't add up to a single total.

  • AWS (4 Dec 2025): exploitation attempts within hours of disclosure from China-nexus groups it tracks as Earth Lamia and Jackpot Panda, chained with other recent CVEs.11
  • Google Threat Intelligence (12 Dec): several China-nexus clusters deploying tunnelers, downloaders and backdoors; Iran-nexus actors; and financially motivated XMRig miners from 5 December.12
  • Wiz: victims from 5 December, mainly internet-facing Next.js apps and Kubernetes containers; credential harvesting from environment variables, files and cloud metadata; at least six miner campaigns.13
  • Datadog: more than 800 scanning IPs, with payloads moving from broken proofs of concept to working ones.14
  • Palo Alto Unit 42: activity overlapping North Korean “Contagious Interview” tooling, and Linux backdoors.15
  • Microsoft (15 Dec): “several hundred machines across a diverse set of organizations”, on Windows and Linux.16
  • CISA: the KEV entry currently marks known ransomware campaign use. The feed does not say when that flag was set.9

Exposure estimates come from vendor telemetry: Wiz found vulnerable React or Next.js in 39% of the cloud environments it sees, and Unit 42 counted over 968,000 React and Next.js instances. Press figures such as “more than 60 organizations” could not be confirmed on the primary pages and are left out here.

Only upgrading stopped it. Everything else limited the damage.

Which controls would have stopped or limited this one. A bug in trusted code is a different animal from a malicious package: most supply-chain controls do not apply.

ControlEffect hereWhy
Upgrade speedStops itThe only fix. No workaround existed. Scanning started about six hours after publication, so a monthly patch cycle was far too slow.
Edge firewall rulesStopgapVercel, AWS, Google Cloud Armor, Azure, Fastly and Akamai all shipped rules, and all said not to rely on them. Vercel paid bounties for bypasses of its own.
Secret rotationLimits damageCode execution in the server process exposes every environment variable it can read (step 7).
Lockfile scanningPartlyFinds you only if it matches the right package. The copies inside next and @vitejs/plugin-rsc are invisible to a scan keyed on react-server-dom-*.
Minimum release age, provenance, 2FA, pinned actionsDoesn't applyThese guard against malicious publishes. Here the code was published honestly by its maintainers; the bug was in it.

[Firewall rules] cannot guarantee protection against all possible variants.

Vercel, React2Shell security bulletin, Dec 2025

Sources

Primary sources are the vendors, advisory databases and governments responsible for the facts. Secondary sources are researchers' own analyses.

Primary · vendors and maintainers

Primary · advisory databases and government

Secondary · discoverer and researchers

Written from sources checked on 2026-09-30. Every fact above traces to one of them; claims found only in press coverage are left out. Pull quotes are verbatim from the linked primary pages; the bracketed words in Vercel's are ours.