← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2026-23869 · CWE-400 · Disclosed 2026-04-08

A denial of service vulnerability exists in React Server Components

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
01 · The Real Story

This is a jammed airport gate, not a runway explosion

CVE-2026-23869 is a CPU-exhaustion denial of service in React Server Components. A remote attacker can send specially crafted HTTP requests to Server Function endpoints and drive excessive CPU usage for up to about a minute before the server throws a catchable error. Per Meta's April 8, 2026 advisory, the affected packages are react-server-dom-webpack, react-server-dom-parcel, and react-server-dom-turbopack in versions 19.0.0-19.0.4, 19.1.0-19.1.5, and 19.2.0-19.2.4; fixes were backported to 19.0.5, 19.1.6, and 19.2.5.

The vendor's HIGH 7.5 score is technically fair in a vacuum: unauthenticated, remote, low-complexity, availability impact. In enterprise reality, though, this is not every React app and not even every server-rendered React app. The vulnerable path only exists when the application actually uses a server and a framework, bundler, or plugin that supports React Server Components / Server Functions, which cuts the reachable population hard enough that I would downgrade to MEDIUM for fleet prioritization.

"Easy to trigger, but only on the subset of internet-facing apps that actually use React Server Components."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Find an exposed RSC-capable app

The attacker first needs a public-facing application that actually exposes React Server Function endpoints. In practice this usually means a modern framework build using RSC/App Router semantics rather than a plain SPA. Tools like httpx, ffuf, or simple browser devtools can help identify likely endpoint patterns and request shapes.
Conditions required:
  • Target app is internet reachable
  • App uses a server plus a framework/bundler/plugin that supports React Server Components
  • Server Function endpoints are reachable from the attacker position
Where this breaks in practice:
  • Plain client-side React apps are not affected
  • Server-rendered React apps without RSC support are not affected
  • Many enterprises expose only a small subset of Node/Next workloads directly to the internet
Detection/coverage: Runtime exposure is poorly covered by network scanners; SCA/SBOM/lockfile scanning is the reliable way to identify installed vulnerable packages.
STEP 02

Send a crafted request to a Server Function endpoint

Using curl, Burp Suite, or vegeta, the attacker submits a specially crafted HTTP payload that reaches the vulnerable deserialization path. No authentication or user interaction is required according to the CNA CVSS vector and advisory.
Conditions required:
  • Attacker can reach the endpoint over the network
  • Request body is accepted through reverse proxy/CDN/WAF layers
  • Target route maps into the React Server Function handling path
Where this breaks in practice:
  • Upstream WAF/body-validation rules may block malformed or anomalous payloads
  • CDN edge protections and request normalization can reduce trivial abuse
  • Some deployments hide these endpoints behind application-specific routing or origin restrictions
Detection/coverage: Look for bursts of unusual POST traffic to Server Function routes, request body anomalies, and WAF hits. Traditional vuln scanners are unlikely to safely prove exploitation.
STEP 03

Burn a worker for up to a minute

When the payload is deserialized, the vulnerable code path consumes excessive CPU and eventually throws an error. A single request is disruptive but not catastrophic; the real problem is tying up enough Node workers or serverless concurrency to degrade service.
Conditions required:
  • Application is running a vulnerable package version
  • Origin has limited worker/process/concurrency headroom
  • Autoscaling or queueing does not fully absorb the spike
Where this breaks in practice:
  • Autoscaling, serverless burst capacity, and per-IP rate limits can blunt impact
  • The error is catchable rather than a guaranteed process-kill every time
  • Multi-layer edge caching can shield unrelated traffic if the vulnerable path is narrow
Detection/coverage: APM, origin CPU telemetry, Node process metrics, and 5xx/latency spikes should show this quickly. EDR is not the primary control here; application observability is.
STEP 04

Repeat for service degradation

An attacker operationalizes the issue by replaying the crafted request in parallel with hey, ab, or vegeta to exhaust workers and increase queueing delay. This turns a single expensive request into a practical denial-of-service event against a public app.
Conditions required:
  • Attacker can sustain repeated requests
  • No aggressive rate limiting or bot controls on the affected route
  • Target service has finite CPU or concurrency budget
Where this breaks in practice:
  • Bot management, CDN rate limiting, and origin quotas materially reduce exploit scale
  • This does not grant persistence, privilege, or lateral movement
  • Impact is usually bounded to the specific application/tenant path being attacked
Detection/coverage: Traffic analytics and CDN/origin logs will usually catch repetition. There is good operational detection, but weak pre-exploitation fingerprinting.
03 · Intelligence Metadata

The supporting signals.

In the wildNo credible public evidence of active exploitation found in primary sources, and CISA KEV does not list CVE-2026-23869 as of this assessment. That matters because this is an internet-triggerable bug, but there is no exploitation amplifier yet.
Proof-of-concept availabilityI found no vendor-published or trusted-researcher PoC in the primary sources reviewed. Public exploit chatter exists in third-party aggregators, but I could not validate a reputable PoC, so treat exploit availability as unconfirmed.
EPSS0.00841 (~0.84%) from the user-supplied upstream intel. That's a low forecast for near-term exploitation and supports a downgrade from the raw CVSS baseline.
KEV statusNot KEV-listed. CISA's Known Exploited Vulnerabilities Catalog shows no entry for this CVE in current public results.
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H = remotely reachable, easy to trigger, no auth, availability-only. That's why the vendor lands at 7.5/HIGH, but CVSS does not account for the narrow deployment subset.
Affected versionsMeta's April 8, 2026 GHSA says react-server-dom-webpack, react-server-dom-parcel, and react-server-dom-turbopack are affected in 19.0.0-19.0.4, 19.1.0-19.1.5, and 19.2.0-19.2.4.
Fixed versionsBackported fixes landed in 19.0.5, 19.1.6, and 19.2.5 upstream. Downstream, Vercel states patched framework releases are required for affected App Router users, with examples including Next.js 15.5.15 and 16.2.3 in its April 8, 2026 summary.
Scanning / exposure realityThere is no clean Shodan/Censys-style product fingerprint for this bug because it lives in application code paths, not a bannered appliance. Exposure has to be inferred from package inventory + whether the app actually uses RSC/Server Functions, not from internet census alone.
DisclosurePublished by Meta on 2026-04-08 via GHSA-479c-33wc-g2pg; NVD is still Awaiting Enrichment.
Reporter / researcherThe April 8, 2026 GHSA and NVD entry do not name a reporting researcher for this specific CVE. I would not invent attribution here.
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to MEDIUM (5.9/10)

The decisive factor is deployment narrowing: exploitation only matters on the subset of public applications that actually use React Server Components / Server Functions, not on every host with React in the estate. This is still an easy unauthenticated DoS against exposed apps, but the reachable population and blast radius are much smaller than the vendor's generic network-attack baseline implies.

HIGH Affected version ranges and fixed versions
MEDIUM Real-world exposure population across enterprise deployments
HIGH No current KEV listing or primary-source exploitation evidence

Why this verdict

  • Start at vendor 7.5, then subtract for feature gating: Meta explicitly says apps are unaffected if they do not use a server, or do not use a framework/bundler/plugin that supports React Server Components. That sharply narrows the exposed population versus a generic 'network reachable' service flaw.
  • Subtract again for app-specific reachability: the attacker needs a reachable Server Function endpoint, which implies a public RSC-capable application path rather than just 'React somewhere in the stack.' This is a meaningful real-world friction point for large estates.
  • Availability-only keeps the ceiling down: there is no confidentiality, integrity, privilege, or persistence outcome here. Even successful exploitation burns compute and user experience, but it does not hand over the box.

Why not higher?

I am not calling this HIGH because the vendor score overstates how many enterprise deployments are truly reachable. There is also no KEV listing, no validated active exploitation, and low EPSS, which removes the biggest reasons to keep a pure pre-auth internet bug in the upper bucket.

Why not lower?

I am not calling this LOW because when the vulnerable feature set is exposed, the exploit path is straightforward: no auth, no user interaction, low complexity, public network reach. For customer-facing Next.js/RSC applications, repeated requests can absolutely create an operational incident even without code execution.

05 · Compensating Control

What to do — in priority order.

  1. Rate-limit Server Function endpoints — Apply route-specific rate limits and concurrency caps at the CDN, WAF, or ingress for RSC / Server Function request paths. For a MEDIUM verdict there is no mitigation SLA, but this is the best bridge control if patching is delayed before the 365-day remediation window closes.
  2. Enable body-anomaly filtering — Use WAF rules for abnormal request bodies, malformed serialization patterns, and sudden bursts to App Router / Server Function endpoints. This will not be perfect, but it raises attacker cost and helps stop low-effort exploitation where self-hosted deployments lack upstream provider protections.
  3. Constrain origin concurrency — Set sane worker, queue, and autoscaling guardrails so one hot endpoint cannot starve the whole app tier. This does not remove the bug, but it reduces single-route blast radius and buys time while application owners schedule the upgrade.
  4. Inventory RSC usage from SBOMs and lockfiles — Prioritize apps that actually include react-server-dom-webpack, react-server-dom-parcel, or react-server-dom-turbopack, then verify whether they expose Server Functions publicly. For this CVE, inventory accuracy is more important than generic host-counting.
What doesn't work
  • Generic MFA/login hardening does not solve this because the vendor rates it PR:N/UI:N and the vulnerable path can be hit pre-auth if the endpoint is exposed.
  • EDR on the Node host may show CPU spikes, but it is not a preventive control for this request-driven app-layer DoS.
  • Patching only react and react-dom is insufficient if the vulnerable react-server-dom-* packages or downstream framework versions remain in the affected ranges.
06 · Verification

Crowdsourced verification payload.

Run this on the application source tree, build workspace, or CI runner that contains the app's package.json / package-lock.json / node_modules. Invoke it as python3 check_cve_2026_23869.py /srv/app with read-only access only; no admin privileges are required.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
# check_cve_2026_23869.py
# Detects vulnerable react-server-dom-* package versions for CVE-2026-23869.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

import json
import os
import re
import sys

TARGETS = {
    'react-server-dom-webpack': [('19.0.0','19.0.4'), ('19.1.0','19.1.5'), ('19.2.0','19.2.4')],
    'react-server-dom-parcel': [('19.0.0','19.0.4'), ('19.1.0','19.1.5'), ('19.2.0','19.2.4')],
    'react-server-dom-turbopack': [('19.0.0','19.0.4'), ('19.1.0','19.1.5'), ('19.2.0','19.2.4')],
}

FIXED = ['19.0.5', '19.1.6', '19.2.5']


def normalize(v):
    if not isinstance(v, str):
        return None
    m = re.search(r'(\d+)\.(\d+)\.(\d+)', v)
    if not m:
        return None
    return tuple(int(x) for x in m.groups())


def cmp_ver(a, b):
    return (a > b) - (a < b)


def in_range(ver, start, end):
    v = normalize(ver)
    s = normalize(start)
    e = normalize(end)
    if not v or not s or not e:
        return False
    return cmp_ver(v, s) >= 0 and cmp_ver(v, e) <= 0


def vulnerable(pkg, ver):
    for start, end in TARGETS.get(pkg, []):
        if in_range(ver, start, end):
            return True
    return False


def collect_from_package_lock(lock_path):
    found = {}
    try:
        with open(lock_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
    except Exception:
        return found

    def record(name, ver, source):
        if name in TARGETS and isinstance(ver, str):
            found.setdefault(name, []).append((ver, source))

    packages = data.get('packages')
    if isinstance(packages, dict):
        for path, meta in packages.items():
            if not isinstance(meta, dict):
                continue
            name = meta.get('name')
            ver = meta.get('version')
            if name in TARGETS:
                record(name, ver, f'{os.path.basename(lock_path)}:{path or "."}')
            else:
                # Derive package name from path like node_modules/<name>
                if isinstance(path, str):
                    for target in TARGETS:
                        if path.endswith('node_modules/' + target):
                            record(target, ver, f'{os.path.basename(lock_path)}:{path}')

    deps = data.get('dependencies')
    if isinstance(deps, dict):
        def walk(dep_map, prefix='dependencies'):
            for name, meta in dep_map.items():
                if not isinstance(meta, dict):
                    continue
                ver = meta.get('version')
                if name in TARGETS:
                    record(name, ver, f'{os.path.basename(lock_path)}:{prefix}.{name}')
                child = meta.get('dependencies')
                if isinstance(child, dict):
                    walk(child, prefix=f'{prefix}.{name}.dependencies')
        walk(deps)

    return found


def collect_from_node_modules(root):
    found = {}
    nm = os.path.join(root, 'node_modules')
    if not os.path.isdir(nm):
        return found
    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, 'node_modules'))
            except Exception:
                pass
    return found


def merge(a, b):
    out = dict(a)
    for k, vals in b.items():
        out.setdefault(k, []).extend(vals)
    return out


def dedupe(found):
    out = {}
    for pkg, vals in found.items():
        seen = set()
        out[pkg] = []
        for ver, src in vals:
            key = (ver, src)
            if key not in seen:
                seen.add(key)
                out[pkg].append((ver, src))
    return out


def main():
    root = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
    if not os.path.isdir(root):
        print('UNKNOWN: target path does not exist or is not a directory')
        sys.exit(2)

    found = {}
    for filename in ('package-lock.json', 'npm-shrinkwrap.json'):
        path = os.path.join(root, filename)
        if os.path.isfile(path):
            found = merge(found, collect_from_package_lock(path))

    found = merge(found, collect_from_node_modules(root))
    found = dedupe(found)

    if not found:
        print('UNKNOWN: could not find target packages in package-lock.json, npm-shrinkwrap.json, or node_modules')
        sys.exit(2)

    vulnerable_hits = []
    checked = []
    for pkg, vals in sorted(found.items()):
        for ver, src in vals:
            checked.append(f'{pkg}@{ver} [{src}]')
            if vulnerable(pkg, ver):
                vulnerable_hits.append(f'{pkg}@{ver} [{src}]')

    if vulnerable_hits:
        print('VULNERABLE: ' + '; '.join(vulnerable_hits))
        sys.exit(1)

    print('PATCHED: checked ' + '; '.join(checked) + f'; fixed upstream versions include {", ".join(FIXED)}')
    sys.exit(0)


if __name__ == '__main__':
    main()
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning, do not spray this across every endpoint team just because the CVSS says 7.5. First, identify which internet-facing apps actually ship react-server-dom-webpack, react-server-dom-parcel, or react-server-dom-turbopack and expose RSC / Server Function endpoints; that scoping should happen immediately. Because this is a MEDIUM reassessment, the noisgate mitigation SLA is no mitigation SLA — go straight to the 365-day remediation window, and the noisgate remediation SLA is patch within 365 days by moving to upstream fixed package versions or patched downstream framework releases. For externally exposed revenue apps, I would still apply rate limits/WAF tuning in the current sprint even though the formal mitigation SLA does not require it.

Sources

  1. GitHub Security Advisory GHSA-479c-33wc-g2pg
  2. NVD CVE-2026-23869
  3. CVE Program record
  4. React blog: Denial of Service and Source Code Exposure in React Server Components
  5. React docs: Server Functions
  6. Vercel changelog: Summary of CVE-2026-23869
  7. CISA Known Exploited Vulnerabilities Catalog
  8. React releases
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.