Skip to content

Instantly share code, notes, and snippets.

@matrosov
Last active December 8, 2025 23:21
Show Gist options
  • Select an option

  • Save matrosov/8472b1672a48554223e7448c02c09092 to your computer and use it in GitHub Desktop.

Select an option

Save matrosov/8472b1672a48554223e7448c02c09092 to your computer and use it in GitHub Desktop.
Static analysis rules for detecting React2Shell(CVE-2025-55182) exposure in JS/TS codebases.
rules:
# ========================================================================
# Detection of CVE-2025-55182 (React2Shell) exposure and exploitability
#
# TIER 1: Inventory-level (vulnerable deps or at-risk Next.js)
# TIER 2: Exposure-level (server imports of RSC libraries)
# TIER 3: Exploit chain (taint from HTTP → decodeReply/decodeAction/etc.)
# ========================================================================
# ------------------------------------------------------------------------
# TIER 1: Scope vulnerable RSC deps (package.json)
# Fast inventory check drives bulk patching
# ------------------------------------------------------------------------
- id: react2shell-vulnerable-rsc-dependency
languages: [json]
message: >-
VULNERABLE REACT SERVER COMPONENTS: This project depends on a
React Server Components package version affected by CVE-2025-55182
(React2Shell). Upgrade react-server-dom-* to 19.0.1 / 19.1.2 / 19.2.1
or later.
severity: ERROR
paths:
include:
- "package.json"
# Match react-server-dom-{webpack,parcel,turbopack} with vulnerable
# versions (19.0.0, 19.1.0, 19.1.1, 19.2.0). Adjust as needed.
pattern-regex: >
"react-server-dom-(webpack|parcel|turbopack)"\s*:\s*"[~^]?(19\.0\.0|19\.1\.0|19\.1\.1|19\.2\.0)"
metadata:
category: security
cwe: "CWE-502: Deserialization of Untrusted Data"
owasp: "A08:2021 - Software and Data Integrity Failures"
signal: inventory # inventory | exposure | exploit_chain
likelihood: high
impact: high
technology: [react, rsc]
product_area: web_frontend
ci_blocker: false # do not fail CI on inventory alone
# ------------------------------------------------------------------------
# TIER 1: Next.js Major Versions at Risk
# Identify projects using Next.js major versions known to embed RSC
# in ways affected by React2Shell (15.x/16.x).
# ------------------------------------------------------------------------
- id: react2shell-nextjs-major-version-at-risk
languages: [json]
message: >-
NEXT.JS 15.x / 16.x IN USE: This project uses Next.js in a major
version line affected by CVE-2025-55182 (React2Shell) in the App Router.
Verify whether the specific version is a patched release.
severity: WARNING
paths:
include:
- "package.json"
# Any 15.x.y or 16.x.y — this is a coarse filter for "at-risk".
pattern-regex: >
"next"\s*:\s*"[~^]?(15\.[0-9]+\.[0-9]+|16\.[0-9]+\.[0-9]+)"
metadata:
category: security
cwe: "CWE-502: Deserialization of Untrusted Data"
signal: inventory
likelihood: medium
impact: high
technology: [react, nextjs, rsc]
product_area: web_frontend
ci_blocker: false
# ------------------------------------------------------------------------
# TIER 2: Smart Import Detection
# Detect server-side imports of React Server Components libraries:
# react-server-dom-{webpack,parcel,turbopack}/server*.
# Find custom RSC integrations.
# ------------------------------------------------------------------------
- id: react2shell-import-detection
languages: [javascript, typescript]
message: >-
RSC SERVER IMPORT: Detected import of a React Server Components
server library (react-server-dom-* /server*). This indicates potential
exposure to CVE-2025-55182. Verify that this module is not processing
untrusted input, and confirm you are using patched versions.
severity: WARNING
# Avoid noise from compiled artifacts, node_modules, and tests.
paths:
exclude:
- "node_modules/**"
- "dist/**"
- "build/**"
- "out/**"
- "coverage/**"
- "test/**"
- "tests/**"
- "**/__tests__/**"
- "**/__mocks__/**"
patterns:
- pattern-either:
# ESM imports (named, default, or namespace)
- pattern: import ... from $LIB
# CommonJS require
- pattern: const ... = require($LIB)
# Dynamic import()
- pattern: const ... = await import($LIB)
metavariable-regex:
metavariable: $LIB
# Match webpack / parcel / turbopack RSC server entrypoints:
# 'react-server-dom-webpack/server', '.../server.edge', etc.
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
metadata:
category: security
cwe: "CWE-502: Deserialization of Untrusted Data"
signal: exposure
likelihood: high
impact: medium
technology: [react, rsc, nextjs]
product_area: web_frontend
ci_blocker: false # usually not a CI blocker by itself
# ------------------------------------------------------------------------
# TIER 3: Deep Reachability Analysis
# Confirm an actual exploit chain: untrusted HTTP data flows into
# RSC deserialization primitives: decodeReply/decodeAction/requireModule
# from react-server-dom-*.
# ------------------------------------------------------------------------
- id: react2shell-reachability-proof
mode: taint
languages: [javascript, typescript]
message: >-
CRITICAL EXPLOIT CHAIN: Untrusted network data is flowing into
React Server Components deserialization (decodeReply/decodeAction/
requireModule) from react-server-dom-*. This is a CONFIRMED RCE
vector for CVE-2025-55182 (React2Shell).
Short term: Add strict validation, schema checks, or
signature/allowlist enforcement before these calls.
Roll out patched React/Next.js RSC runtimes across environments.
Long term: Centralize all RSC decoding behind a shared, vetted helper
and forbid raw decodeReply/decodeAction/requireModule usage.
severity: ERROR
# Same path filters as import rule: focus on real application code.
paths:
exclude:
- "node_modules/**"
- "dist/**"
- "build/**"
- "out/**"
- "coverage/**"
- "test/**"
- "tests/**"
- "**/__tests__/**"
- "**/__mocks__/**"
# --------------------------------------------------------------------
# SOURCES: Common HTTP / server entrypoints for untrusted input
# --------------------------------------------------------------------
pattern-sources:
# Express / Node-style
- pattern: req.body
- pattern: req.rawBody
- pattern: req.query
- pattern: req.params
- pattern: req.headers
# Koa / generic context
- pattern: ctx.request.body
- pattern: ctx.query
- pattern: ctx.params
# Next.js / Fetch-style Request API
- pattern: request.body
- pattern: request.json()
- pattern: await request.json()
- pattern: request.text()
- pattern: await request.text()
- pattern: request.formData()
- pattern: await request.formData()
- pattern: request.nextUrl.searchParams.get(...)
# Generic URLSearchParams
- pattern: searchParams.get(...)
# Lambda / serverless payloads
- pattern: event.body
# FormData helpers used directly
- pattern: req.formData()
- pattern: await req.formData()
- pattern: formData.get(...)
- pattern: formData.getAll(...)
# --------------------------------------------------------------------
# SINKS: decodeReply/decodeAction/requireModule from
# react-server-dom-{webpack,parcel,turbopack}/server*
#
# Coverage:
# - ESM named imports (direct + aliased)
# - ESM namespace imports (import * as RSC ...)
# - CommonJS require (destructured + namespace)
# --------------------------------------------------------------------
pattern-sinks:
# ESM: aliased decodeReply
- patterns:
- pattern: $DECODE(...)
- pattern-inside: |
import { ..., decodeReply as $DECODE, ... } from $LIB
...
metavariable-regex:
metavariable: $LIB
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
# ESM: aliased decodeAction
- patterns:
- pattern: $DECODE(...)
- pattern-inside: |
import { ..., decodeAction as $DECODE, ... } from $LIB
...
metavariable-regex:
metavariable: $LIB
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
# ESM: direct named decodeReply
- patterns:
- pattern: decodeReply(...)
- pattern-inside: |
import { ..., decodeReply, ... } from $LIB
...
metavariable-regex:
metavariable: $LIB
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
# ESM: direct named decodeAction
- patterns:
- pattern: decodeAction(...)
- pattern-inside: |
import { ..., decodeAction, ... } from $LIB
...
metavariable-regex:
metavariable: $LIB
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
# ESM: namespace imports (import * as RSC from '...')
- patterns:
- pattern: $RSC.decodeReply(...)
- pattern-inside: |
import * as $RSC from $LIB
...
metavariable-regex:
metavariable: $LIB
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
- patterns:
- pattern: $RSC.decodeAction(...)
- pattern-inside: |
import * as $RSC from $LIB
...
metavariable-regex:
metavariable: $LIB
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
# CommonJS: destructured require with alias
- patterns:
- pattern: $DECODE(...)
- pattern-inside: |
const { decodeReply: $DECODE, ... } = require($LIB)
...
metavariable-regex:
metavariable: $LIB
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
- patterns:
- pattern: $DECODE(...)
- pattern-inside: |
const { decodeAction: $DECODE, ... } = require($LIB)
...
metavariable-regex:
metavariable: $LIB
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
# CommonJS: namespace require
- patterns:
- pattern: $RSC.decodeReply(...)
- pattern-inside: |
const $RSC = require($LIB)
...
metavariable-regex:
metavariable: $LIB
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
- patterns:
- pattern: $RSC.decodeAction(...)
- pattern-inside: |
const $RSC = require($LIB)
...
metavariable-regex:
metavariable: $LIB
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
# Optional: requireModule as an additional sink (seen in some PoCs)
- patterns:
- pattern: $REQMOD(...)
- pattern-inside: |
import { ..., requireModule as $REQMOD, ... } from $LIB
...
metavariable-regex:
metavariable: $LIB
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
- patterns:
- pattern: $RSC.requireModule(...)
- pattern-inside: |
const $RSC = require($LIB)
...
metavariable-regex:
metavariable: $LIB
regex: '^[''"]react-server-dom-(webpack|parcel|turbopack)(/server[^''"]*)?[''"]$'
metadata:
category: security
cwe: "CWE-502: Deserialization of Untrusted Data"
owasp: "A08:2021 - Software and Data Integrity Failures"
signal: exploit_chain
likelihood: high
impact: high
technology: [react, rsc, nextjs]
product_area: web_frontend
ci_blocker: true # safe to fail CI when this rule fires
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment