This is not a crack in the paint, it is a hidden service door that opens straight into your Node.js process
CVE-2025-55182 is an unsafe deserialization bug in React Server Components handling for react-server-dom-webpack, react-server-dom-parcel, and react-server-dom-turbopack. Affected versions are 19.0.0, 19.1.0, 19.1.1, and 19.2.0; fixed versions are 19.0.1, 19.1.2, and 19.2.1. The bug is triggered over HTTP against server-side React deployments that support React Server Components, and React states apps can be vulnerable even when they do not explicitly implement Server Function endpoints.
The vendor's CRITICAL 10.0 is basically fair in enterprise reality. The one real downward pressure is exposure population: this does not hit pure client-side React SPAs, and it requires an RSC-capable server-side stack. But once that condition is met, the rest of the chain is brutally simple: unauthenticated, remote, no user interaction, full server-side code execution, public PoCs, active exploitation, and CISA KEV status. That keeps it squarely in CRITICAL.
4 steps from start to impact.
Find an RSC-capable target with Nuclei, Censys, or Shodan-like enumeration
Nuclei usage in the early wave. This step is low skill because the ecosystem, headers, routes, and framework fingerprints are easy to harvest at scale.- Target application is reachable over the internet or from attacker-controlled network position
- Application uses server-side React with RSC-capable components or a downstream framework that embeds the vulnerable packages
- Pure client-side React deployments are unaffected
- Internal-only apps or apps behind VPN/private ingress shrink reachable population
- Some hosting/WAF providers added detection quickly, reducing blind internet-wide success
Deliver a malicious Flight/Server Action payload with a public PoC
ejpir/CVE-2025-55182-research document working chains and payload construction, so attackers do not need to reverse the bug from scratch. This is the weaponization step that converts a framework fingerprint into an actual code path hit.- Application processes React Server Components traffic on the vulnerable package versions
- Attacker can send HTTP requests to the relevant application endpoint
- Apps fronted by managed WAFs may block commodity payloads
- Custom routing, body limits, or nonstandard deployment patterns can break copy-paste exploit reliability
Abuse object traversal and fake chunk handling to reach dangerous runtime behavior
- Target is running one of the vulnerable
react-server-dom-*packages - The crafted payload is accepted and decoded by the server-side RSC implementation
- Exploit reliability can vary by framework wrapper and deployment hardening
- Some edge controls block obvious forms of the payload before it reaches Node.js
Execute arbitrary code in the Node.js app context and pivot
- Application runtime has access to secrets, internal services, or cloud metadata worth stealing
- Post-exploitation controls do not immediately kill the process or isolate the workload
- Strong workload isolation, minimal IAM roles, read-only containers, and good EDR on hosts can reduce blast radius
- Short-lived containers can limit persistence if secrets and identities are also tightly scoped
The supporting signals.
| In-the-wild status | Yes. CISA KEV-listed, AWS observed exploitation within hours of disclosure, and GreyNoise/Cloudflare both reported opportunistic exploitation and mass scanning. |
|---|---|
| KEV | Listed on CISA KEV on 2025-12-05 with 2025-12-12 due date for federal remediation. |
| Proof-of-concept availability | Public PoCs exist. ejpir/CVE-2025-55182-research documents a working RCE chain; Cloudflare also observed attackers using commodity tooling including Nuclei. |
| EPSS | 0.82011 supplied in the prompt, which is an extremely high exploitation-likelihood signal and consistent with the observed abuse pattern. |
| CVSS interpretation | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H means unauthenticated network reachability, no user clicks, and full CIA impact with scope change. Technically that is maximum-severity territory. |
| Affected versions | react-server-dom-webpack, react-server-dom-parcel, and react-server-dom-turbopack versions 19.0.0, 19.1.0, 19.1.1, and 19.2.0. |
| Fixed versions | Upstream React fixes are 19.0.1, 19.1.2, and 19.2.1. Censys also notes downstream Next.js fixes at 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, and 16.0.7; no distro-style backports were identified because this is primarily an npm package issue. |
| Exposure data | Censys reported ~2.15 million internet-facing services that *may* be affected, while Cloudflare saw 582.10M React2Shell WAF hits from 2025-12-03 through 2025-12-11 17:00 UTC. |
| Disclosure | Publicly disclosed on 2025-12-03; React says the report came from Lachlan Davidson on 2025-11-29. |
| Reporting researcher / org | Lachlan Davidson reported it to the React team; public threat reporting came quickly from React, AWS, Cloudflare, GreyNoise, and Censys. |
noisgate verdict.
The decisive factor is active exploitation against internet-facing app servers with no authentication requirement. The main friction is that only server-side React/RSC deployments are in scope, but that narrowing is not enough to drag a KEV-listed pre-auth RCE out of the CRITICAL bucket.
Why this verdict
- Vendor baseline holds:
10.0is not inflated for environments actually running vulnerable RSC packages because the bug is unauthenticated remote RCE with full server impact. - First friction adjustment down: this is not all React. Pure client-side SPAs are unaffected, so the reachable population is narrower than the product brand name suggests.
- Second friction adjustment stops there: requiring an RSC-capable server stack is a real filter, but it does not imply prior compromise, internal access, authentication, or user interaction.
- Amplifier overwhelms the narrowing: CISA KEV listing and observed exploitation by AWS/GreyNoise/Cloudflare mean defenders are racing active operators, not hypothetical researchers.
- Blast radius is server-side, not cosmetic: compromise lands in the Node.js application trust zone where secrets, database access, cloud roles, and CI/CD tokens commonly live.
Why not higher?
There is no honest room to score this at a perfect theoretical 10.0 in real-world prioritization without acknowledging deployment friction. The most important limiter is exposure population: only server-side React estates using vulnerable RSC-capable components are affected, and many enterprises still run pure SPA React front ends that are completely out of scope.
Why not lower?
A lower severity would ignore the facts that matter most operationally: pre-auth remote code execution, public PoCs, active exploitation, and KEV status. This is not a post-auth admin bug or an internal-only chain; if the vulnerable app is reachable, the attacker can be outside your perimeter and still get code execution.
What to do — in priority order.
- Patch exposed RSC stacks now — Upgrade affected React packages to
19.0.1,19.1.2, or19.2.1, and upgrade downstream frameworks like Next.js to fixed builds where applicable. Because this is KEV-listed and actively exploited, do this immediately, within hours, not on the normal CRITICAL queue. - Turn on managed WAF coverage — Enable vendor-managed rules at the edge for React2Shell coverage, including Cloudflare or AWS WAF protections if those platforms front the app. This is a same-day emergency brake within hours that buys time while patching rolls through.
- Block Server Action traffic if unused — If the application does not need Server Actions or RSC write paths, block the relevant request patterns and headers at the reverse proxy or WAF. This is a within-hours containment move that meaningfully reduces exposed attack surface.
- Reduce public exposure — Put admin, preview, staging, and low-business-value RSC apps behind VPN, access proxy, or IP allowlists. For a KEV-listed pre-auth RCE, shrinking the exposed population within hours is worth more than arguing about exact exploit syntax.
- Hunt for app-server secret abuse — Review environment variables, cloud-role use, outbound connections, child-process execution, and unusual Next.js/Node.js activity on exposed app servers. Run this immediately and continue during patch rollout, because successful exploitation often turns into credential theft and cloud pivoting.
MFAdoes not help because the exploit is unauthenticated and hits the application server before any user login flow matters.Regex-only custom WAF signaturesare unreliable because public research already discusses evasions and protocol-compliant payload mutation.SAST on your own app codemisses the point if the vulnerable behavior lives in transitive framework packages already shipped to production.Patching onlyreactandreact-dom`is insufficient if the vulnerablereact-server-dom-*` package remains present.
Crowdsourced verification payload.
Run this on the application repo checkout, build workspace, or target host filesystem where package-lock.json and/or node_modules exist. Invoke it as python3 verify_cve_2025_55182.py /path/to/app; no admin rights are required for read access. It checks package-lock.json, npm-shrinkwrap.json, and installed node_modules for vulnerable react-server-dom-* package versions and prints VULNERABLE, PATCHED, or UNKNOWN.
#!/usr/bin/env python3
# verify_cve_2025_55182.py
# Detect vulnerable React Server Components package versions for CVE-2025-55182.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN / error
import json
import os
import sys
from typing import Dict, List, Optional, Tuple
TARGETS = {
'react-server-dom-webpack': {
'vuln': [('19.0.0', '19.0.0'), ('19.1.0', '19.1.1'), ('19.2.0', '19.2.0')],
'fixed': ['19.0.1', '19.1.2', '19.2.1']
},
'react-server-dom-parcel': {
'vuln': [('19.0.0', '19.0.0'), ('19.1.0', '19.1.1'), ('19.2.0', '19.2.0')],
'fixed': ['19.0.1', '19.1.2', '19.2.1']
},
'react-server-dom-turbopack': {
'vuln': [('19.0.0', '19.0.0'), ('19.1.0', '19.1.1'), ('19.2.0', '19.2.0')],
'fixed': ['19.0.1', '19.1.2', '19.2.1']
}
}
def norm(v: str) -> Optional[Tuple[int, int, int]]:
if not v or not isinstance(v, str):
return None
v = v.strip()
if v.startswith('v'):
v = v[1:]
v = v.split('-')[0].split('+')[0]
parts = v.split('.')
if len(parts) < 3:
return None
try:
return tuple(int(x) for x in parts[:3])
except ValueError:
return None
def cmp_ver(a: str, b: str) -> Optional[int]:
na, nb = norm(a), norm(b)
if na is None or nb is None:
return None
return (na > nb) - (na < nb)
def in_range(v: str, start: str, end: str) -> bool:
c1 = cmp_ver(v, start)
c2 = cmp_ver(v, end)
return c1 is not None and c2 is not None and c1 >= 0 and c2 <= 0
def is_vulnerable(pkg: str, version: str) -> bool:
if pkg not in TARGETS:
return False
for start, end in TARGETS[pkg]['vuln']:
if in_range(version, start, end):
return True
return False
def flatten_lock_deps(obj, found: Dict[str, List[str]]):
if isinstance(obj, dict):
if 'name' in obj and 'version' in obj:
name = obj.get('name')
ver = obj.get('version')
if name in TARGETS and isinstance(ver, str):
found.setdefault(name, []).append(ver)
if 'packages' in obj and isinstance(obj['packages'], dict):
for path, meta in obj['packages'].items():
if not isinstance(meta, dict):
continue
name = meta.get('name')
ver = meta.get('version')
if not name and isinstance(path, str) and 'node_modules/' in path:
name = path.split('node_modules/')[-1]
if name in TARGETS and isinstance(ver, str):
found.setdefault(name, []).append(ver)
if 'dependencies' in obj and isinstance(obj['dependencies'], dict):
for name, meta in obj['dependencies'].items():
if name in TARGETS and isinstance(meta, dict) and isinstance(meta.get('version'), str):
found.setdefault(name, []).append(meta['version'])
flatten_lock_deps(meta, found)
for v in obj.values():
flatten_lock_deps(v, found)
elif isinstance(obj, list):
for item in obj:
flatten_lock_deps(item, found)
def inspect_lockfile(path: str, found: Dict[str, List[str]]):
try:
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
flatten_lock_deps(data, found)
except Exception:
pass
def inspect_node_modules(root: str, found: Dict[str, List[str]]):
nm = os.path.join(root, 'node_modules')
if not os.path.isdir(nm):
return
for pkg in TARGETS:
pkg_json = os.path.join(nm, pkg, 'package.json')
if os.path.isfile(pkg_json):
try:
with open(pkg_json, 'r', encoding='utf-8') as f:
data = json.load(f)
ver = data.get('version')
if isinstance(ver, str):
found.setdefault(pkg, []).append(ver)
except Exception:
pass
def uniq_versions(found: Dict[str, List[str]]) -> Dict[str, List[str]]:
out = {}
for k, vals in found.items():
dedup = sorted(set(vals), key=lambda x: norm(x) or (999, 999, 999))
out[k] = dedup
return out
def main():
root = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
if not os.path.isdir(root):
print('UNKNOWN - path does not exist or is not a directory: {}'.format(root))
sys.exit(2)
found: Dict[str, List[str]] = {}
for lf in ['package-lock.json', 'npm-shrinkwrap.json']:
p = os.path.join(root, lf)
if os.path.isfile(p):
inspect_lockfile(p, found)
inspect_node_modules(root, found)
found = uniq_versions(found)
if not found:
print('UNKNOWN - no target packages found in package lockfiles or node_modules under {}'.format(root))
sys.exit(2)
vulnerable = []
patched = []
unknown = []
for pkg, versions in found.items():
for ver in versions:
n = norm(ver)
if n is None:
unknown.append((pkg, ver))
elif is_vulnerable(pkg, ver):
vulnerable.append((pkg, ver))
else:
patched.append((pkg, ver))
if vulnerable:
details = ', '.join(['{}@{}'.format(p, v) for p, v in vulnerable])
print('VULNERABLE - {}'.format(details))
sys.exit(1)
if patched and not vulnerable:
details = ', '.join(['{}@{}'.format(p, v) for p, v in patched])
print('PATCHED - {}'.format(details))
sys.exit(0)
details = ', '.join(['{}@{}'.format(p, v) for p, v in unknown]) if unknown else 'unable to classify versions'
print('UNKNOWN - {}'.format(details))
sys.exit(2)
if __name__ == '__main__':
main()
If you remember one thing.
CRITICAL timing. Under the noisgate mitigation SLA, mitigation is immediate in this case; under the noisgate remediation SLA, the actual vendor patch must land within 90 days, but for public-facing production apps the practical expectation is same-day or next-change-window patching, not leisurely quarter-end cleanup.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.