← Back to Feed CACHED · 2026-09-04 18:32:30 · CACHE_KEY CVE-2026-18149
CVE-2026-18149 · CWE-772 · Disclosed 2026-09-04

undici's retry handler can leave an already-exposed response body pending forever.

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

Like hanging up on someone mid-sentence, then pretending the new caller is the same person while the first one waits on hold forever

CVE-2026-18149 is a resource-leak flaw in undici's retry handler (CWE-772). When a server sends a partial response with a declared Content-Length, closes the connection, and the retry handler retries the request but receives a non-retryable status (e.g. 400), the handler replaces the internal stream with the new response but never ends or destroys the original response body the application is still holding. The body timeout never fires because its timer is bound to the connection parser, not the orphaned stream. Affected versions are undici 7.11.0 through 7.29.0 and 8.0.0 through 8.10.1. Fixed in 7.29.1 and 8.10.2.

The vendor's MEDIUM / 5.9 rating is honest and arguably slightly generous. The CVSS vector sets Attack Complexity to High (AC:H) because the attacker must control the HTTP server the victim application connects to, the retry interceptor must be explicitly configured (it is not on by default), and the impact ceiling is availability-only — no confidentiality or integrity impact. In real-world terms, this narrows the exploitable population to Node.js services that (a) use undici’s retry interceptor and (b) fetch URLs from untrusted or user-supplied origins. That’s a meaningful subset but far from universal.

"Retry handler orphans response streams; DoS requires attacker-controlled server target."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Victim app fetches attacker-controlled URL via undici with retry interceptor

The target Node.js application must be configured to use undici’s retry handler/interceptor and must make an outgoing HTTP request to a server the attacker controls. This could happen through SSRF-like functionality, webhook callbacks, URL preview features, or any endpoint that fetches user-supplied URLs.
Conditions required:
  • Application uses undici 7.11.0–7.29.0 or 8.0.0–8.10.1
  • Retry interceptor is explicitly enabled
  • Application fetches attacker-controlled URL
Where this breaks in practice:
  • Retry interceptor is opt-in, not enabled by default
  • Many Node.js apps use axios, node-fetch, or got instead of raw undici retry
  • Server-side request targets are often allowlisted in security-conscious deployments
STEP 02

Attacker sends partial response then closes connection

The attacker’s server returns a successful response declaring a Content-Length, sends only part of the body, then drops the connection. This is trivial to implement — a few lines of raw TCP or a minimal HTTP server. The retry handler sees the incomplete response and initiates a retry.
Conditions required:
  • Attacker controls the HTTP server endpoint
Where this breaks in practice:
  • Requires only two short responses; no persistent connection needed
Detection/coverage: Network-level anomaly detection could flag repeated partial responses from the same origin, but this is not a standard signature.
STEP 03

Retry returns non-retryable status; original stream orphaned

On retry, the attacker’s server returns a non-retryable status code such as 400. The retry handler forwards the new response downstream and swaps its internal stream reference, but the original response body the application is already reading is never ended or destroyed. The body timeout does not fire because the timer was tied to the now-dead connection parser.
Conditions required:
  • Retry handler receives non-retryable status on second attempt
Where this breaks in practice:
  • Application must be actively reading the original response body for the orphan to matter
STEP 04

Orphaned streams and promises accumulate; application DoS

Each poisoned request leaves behind a pending Promise and an open stream that will never resolve. Repeated requests accumulate these orphans, exhausting the Node.js event loop’s concurrency, file descriptors, or heap memory. Eventually the application becomes unresponsive or crashes with an out-of-memory error.
Conditions required:
  • Attacker can trigger multiple requests to the malicious server
Where this breaks in practice:
  • Rate limiting on outbound requests would slow accumulation
  • Process supervisors (PM2, systemd) will restart crashed processes, limiting sustained impact
  • Horizontal scaling behind a load balancer dilutes per-instance impact
Detection/coverage: APM tools (Datadog, New Relic) would surface growing event loop lag and memory pressure. Node.js --max-old-space-size limits will trigger OOM kills.
03 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNo known exploitation. Not listed in CISA KEV. No public reports of active campaigns as of 2026-09-05.
Proof-of-ConceptNo public PoC found. The advisory description provides enough detail to construct a triggering server, but no named researcher has published exploit code.
EPSS ScoreNot yet scored. CVE was disclosed 2026-09-04; EPSS data typically populates within 1–3 days. Expected to be low given AC:H and DoS-only impact.
KEV StatusNot listed. No CISA KEV entry as of 2026-09-05.
CVSS VectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H — Network-reachable but high complexity, no auth required, availability-only impact. Scope unchanged.
Affected Versionsundici 7.11.0 – 7.29.0 and 8.0.0 – 8.10.1. Earlier major versions (v5, v6) are not affected.
Fixed Versionsundici 7.29.1 and 8.10.2. Node.js core bundles undici; check node -e "require('undici/package.json').version" for the bundled version.
Exposure Dataundici has ~7,700+ direct dependents on npm and is bundled into Node.js core since v18. However, the retry interceptor is opt-in, limiting the exploitable subset.
Disclosure Date2026-09-04 via GitHub Security Advisory (GHSA).
ReporterReported via the OpenJS Foundation / Node.js security process. Specific researcher not named in public advisory.
04 · The Call

noisgate verdict.

Final Verdict
= UNCHANGED to MEDIUM (5.0/10)

The single most decisive factor is that exploitation requires the victim application to make outbound HTTP requests to an attacker-controlled server with the opt-in retry interceptor enabled, which compounds two prerequisites that exclude the vast majority of undici deployments. The impact ceiling is process-level denial of service with no path to code execution, data exfiltration, or lateral movement.

HIGH Vulnerability mechanics and affected versions
MEDIUM Exploitable population size (retry interceptor adoption rate unknown)
HIGH No active exploitation or public PoC

Why this verdict

  • Attacker-controlled server prerequisite: The attacker must control the HTTP endpoint the victim connects to. This is not a listening-service vulnerability — the application must reach out to a malicious origin, which requires SSRF-like functionality or user-supplied URL fetching.
  • Opt-in retry interceptor: undici’s retry handler is not enabled by default. Applications must explicitly configure it, narrowing the vulnerable population to a subset of undici users.
  • Availability-only impact: The CVSS vector confirms C:N/I:N/A:H. There is no path from this bug to code execution, privilege escalation, or data compromise. Process restarts fully remediate the DoS condition.
  • Role multiplier: undici is bundled in Node.js and used in CI/CD runners, API gateways, and production services. In high-value roles (CI/CD fetching package URLs, API gateways proxying upstream), a successful DoS would disrupt a single process instance. The blast radius is host-level at worst (process crash), not domain/fleet/supply-chain scale. No role produces an outcome above application-level DoS, so no floor override applies.
  • No exploitation evidence: No KEV listing, no public PoC, no campaign reports. EPSS is expected to be low.

Why not higher?

There is no path to code execution, privilege escalation, or data exfiltration. The impact is strictly availability and limited to the process level — a crashed Node.js process restarts in seconds via PM2 or systemd. The attacker cannot leverage this for lateral movement or persistence, and the two compounding prerequisites (attacker-controlled server + opt-in retry config) dramatically narrow the exploitable population.

Why not lower?

The vulnerability is still network-reachable (AV:N) and requires no authentication or user interaction. In applications that do fetch untrusted URLs with the retry interceptor (webhook handlers, URL preview services, RSS aggregators), the attack is low-effort for the attacker once preconditions are met. A sustained DoS against a single-instance service could cause real downtime. The 5.0 score acknowledges this residual risk.

05 · Compensating Control

What to do — in priority order.

  1. Disable or remove the retry interceptor where not business-critical — If your application does not strictly need undici’s retry interceptor, removing it from the dispatcher chain eliminates the vulnerability entirely. Audit your codebase for new RetryHandler() or retry: in dispatcher options. No mitigation SLA applies at MEDIUM — go straight to remediation.
  2. Allowlist outbound request targets — Restrict the set of URLs your application can fetch to known-good origins. Use an allowlist of domains or IP ranges for any user-influenced URL fetching (webhooks, previews, imports). This blocks the attacker-controlled-server prerequisite.
  3. Set aggressive timeouts and concurrency limits on outbound HTTP — Configure headersTimeout, bodyTimeout, and maxConcurrentStreams on undici pools. While the body timeout doesn’t fire for orphaned streams (that’s the bug), overall pool exhaustion limits will cap the blast radius.
  4. Upgrade undici to 7.29.1 or 8.10.2 — The definitive fix. Run npm audit fix or pin the patched version directly. For Node.js core bundled undici, upgrade Node.js to the next patch release that bundles the fix. Target completion within the noisgate 365-day remediation SLA for MEDIUM.
What doesn't work
  • WAF / reverse proxy in front of the application — This is an outbound request vulnerability. Inbound WAF rules do not inspect or control the application’s outgoing HTTP behavior.
  • Body timeout configuration — The advisory explicitly states the body timeout does not fire because the timer is tied to the connection parser, not the orphaned stream. Increasing or decreasing this value has no effect on the vulnerable code path.
  • Node.js --max-old-space-size — While this will eventually OOM-kill the process (limiting blast radius), it does not prevent the vulnerability from being triggered and will cause a hard crash rather than graceful degradation.
06 · Verification

Crowdsourced verification payload.

Run this on any host where a Node.js application using undici is deployed. Execute as the application user or any user with read access to node_modules. Example: bash check_cve_2026_18149.sh /opt/myapp

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_18149.sh - Check for CVE-2026-18149 (undici retry handler resource leak)
# Usage: bash check_cve_2026_18149.sh <project_dir>
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN

set -euo pipefail

PROJECT_DIR="${1:-.}"
RESULT="UNKNOWN"

if ! command -v node &>/dev/null; then
  echo "UNKNOWN - node not found in PATH"
  exit 2
fi

# Check bundled undici in node_modules
PKG="${PROJECT_DIR}/node_modules/undici/package.json"
if [ -f "$PKG" ]; then
  VERSION=$(node -e "console.log(require('${PKG}').version)" 2>/dev/null || echo "")
else
  # Fall back to Node.js built-in undici
  VERSION=$(node -e "try{console.log(require('undici/package.json').version)}catch(e){console.log('')}" 2>/dev/null || echo "")
fi

if [ -z "$VERSION" ]; then
  echo "UNKNOWN - undici not found in ${PROJECT_DIR} or Node.js built-in"
  exit 2
fi

echo "Detected undici version: ${VERSION}"

# Parse major.minor.patch
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"

# Affected: 7.11.0 - 7.29.0, 8.0.0 - 8.10.1
# Fixed: 7.29.1+, 8.10.2+
if [ "$MAJOR" -eq 7 ]; then
  if [ "$MINOR" -lt 11 ]; then
    RESULT="PATCHED"  # Below affected range
  elif [ "$MINOR" -lt 29 ]; then
    RESULT="VULNERABLE"
  elif [ "$MINOR" -eq 29 ] && [ "$PATCH" -eq 0 ]; then
    RESULT="VULNERABLE"
  else
    RESULT="PATCHED"
  fi
elif [ "$MAJOR" -eq 8 ]; then
  if [ "$MINOR" -lt 10 ]; then
    RESULT="VULNERABLE"
  elif [ "$MINOR" -eq 10 ] && [ "$PATCH" -lt 2 ]; then
    RESULT="VULNERABLE"
  else
    RESULT="PATCHED"
  fi
else
  RESULT="PATCHED"  # v5, v6, v9+ not affected
fi

echo "$RESULT"
if [ "$RESULT" = "VULNERABLE" ]; then
  exit 1
elif [ "$RESULT" = "PATCHED" ]; then
  exit 0
else
  exit 2
fi
07 · Bottom Line

If you remember one thing.

TL;DR
This is a MEDIUM-severity DoS bug in an opt-in undici feature. There is no mitigation SLA at the MEDIUM tier under the noisgate framework — go straight to the 365-day noisgate remediation SLA. That said, if you run Node.js services that fetch user-supplied URLs with the retry interceptor (webhook processors, URL unfurlers, RSS ingestors), bump undici to 7.29.1 or 8.10.2 in your next scheduled dependency update cycle. Run npm audit across your fleet to identify affected projects. For the majority of Node.js deployments that don’t use the retry interceptor or don’t fetch untrusted URLs, this CVE is low-urgency backlog work — bundle it into your next quarterly dependency refresh.

Sources

  1. THREATINT CVE-2026-18149 Detail
  2. GitHub Advisory - undici retry desync (GHSA-8xcm-r25x-g524)
  3. undici npm package
  4. undici GitHub repository
  5. CWE-772: Missing Release of Resource after Effective Lifetime
  6. FIRST EPSS FAQ
  7. CISA KEV Catalog
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.