← Back to Feed CACHED · 2026-07-30 08:48:07 · CACHE_KEY CVE-2026-15157
CVE-2026-15157 · CWE-93 · Disclosed 2026-07-29

undici does not validate the type property of a duck-typed blob-like request body before using it as the…

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

Someone left one sink unsanitized in an otherwise well-plumbed kitchen, but you need a very specific wrench to reach it

CVE-2026-15157 is a CRLF injection flaw in undici, the HTTP/1.1 client bundled into Node.js and downloaded ~139 million times per week on npm. When an application uses undici's low-level dispatcher APIs (request(), stream(), pipeline(), dispatch()) and passes a hand-rolled "duck-typed" blob-like body whose .type property is derived from untrusted input, an attacker can inject \r\n sequences into the outgoing Content-Type header. This lets them append arbitrary HTTP headers and, in the worst case, smuggle a second HTTP request to the upstream server. The flaw exists because body.type is pushed directly into the wire format without passing through isValidHeaderValue() — every other header sink in undici already has this check. Native Blob objects are safe (they strip CRLF in the constructor), and fetch() is unaffected because it validates all headers independently. Affected versions: undici < 6.28.0, 7.0.0 – 7.28.x, and 8.0.0 – 8.8.x. Fixed in 6.28.0, 7.29.0, and 8.9.0.

The vendor rated this MEDIUM / 4.2 and that is directionally correct. The CVSS vector (AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N) already encodes most of the friction: high attack complexity, user interaction required, unchanged scope, and low confidentiality/integrity impact with no availability impact. In practice, real-world exploitability is *even narrower* than what the vector suggests. The attacker doesn't target undici — they target an *application* that happens to build duck-typed blob bodies from user input and feed them to undici's non-fetch APIs. That coding pattern is uncommon. Most Node.js applications use fetch() (safe), native Blob (safe), or simply never construct blob-like objects from user-controlled MIME types. The 4.2 is fair; if anything, the true risk for most enterprises sits a fraction lower.

"Narrow code pattern + no PoC + no exploitation = patch in normal cycle, not an emergency."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Identify a target application using undici dispatcher APIs

The attacker must locate a Node.js service that calls undici.request(), undici.stream(), undici.pipeline(), or undici.dispatch()not fetch(). These are lower-level APIs used in custom HTTP clients, proxy layers, and orchestration tooling. The attacker needs to identify that the application accepts user-supplied data that flows into the body of one of these calls.
Conditions required:
  • Target application uses undici's non-fetch dispatcher APIs
  • Application is reachable by attacker (directly or via user interaction)
Where this breaks in practice:
  • Most Node.js applications default to fetch() or use axios/got, never touching undici's low-level API directly
  • No banner or fingerprint reveals which HTTP client library a backend uses
Detection/coverage: No scanner currently fingerprints this code-level pattern. Static analysis tools (Semgrep, CodeQL) could flag body.type usage in undici dispatcher calls.
STEP 02

Find an untrusted input path into a duck-typed blob's .type property

The application must construct a plain JavaScript object with { type, arrayBuffer() } or similar blob-like shape, where the .type value comes from attacker-controlled input (e.g., a user-supplied MIME type from an upload form, API parameter, or webhook payload). Native Blob objects strip CRLF in their constructor, so only hand-rolled duck-typed blobs are exploitable.
Conditions required:
  • Application constructs duck-typed blob-like bodies (not native Blob)
  • The .type property is derived from untrusted user input
Where this breaks in practice:
  • Duck-typed blob bodies are a niche pattern — most developers use native Blob, Buffer, or string bodies
  • Even custom blob-like objects rarely derive .type from raw user input; most hardcode a MIME string
STEP 03

Inject CRLF sequences into the .type value

The attacker supplies a crafted string like text/plain\r\nX-Injected: malicious\r\n\r\nGET /admin HTTP/1.1\r\nHost: internal as the MIME type. Because undici's client-h1.js writes body.type directly into the Content-Type header without calling isValidHeaderValue(), the CRLF bytes pass through to the TCP socket verbatim.
Conditions required:
  • Application does not independently sanitize the .type value
  • undici version is < 6.28.0, 7.0.0–7.28.x, or 8.0.0–8.8.x
Where this breaks in practice:
  • Many frameworks and middleware strip or reject control characters in user input before it reaches application logic
  • WAFs with HTTP smuggling rule sets may detect anomalous Content-Type values
Detection/coverage: WAF rules targeting \r\n in Content-Type headers can catch this. No specific IDS/IPS signature for CVE-2026-15157 exists yet.
STEP 04

Achieve header injection or HTTP request smuggling

The injected CRLF bytes let the attacker append arbitrary headers to the outgoing request or terminate the first request early and smuggle a second request to the upstream server. Depending on the upstream's parsing behavior, this can lead to cache poisoning, auth bypass on internal APIs, or routing requests to unintended endpoints. Impact is bounded by what the upstream server does — the CVSS correctly limits this to C:L/I:L/A:N.
Conditions required:
  • Upstream server interprets the smuggled headers/request
  • No TLS-terminating proxy normalizes the outgoing request before it reaches the upstream
Where this breaks in practice:
  • Modern reverse proxies (nginx, envoy, HAProxy) often normalize or reject malformed HTTP requests
  • HTTP/2 upstreams are immune — smuggling only works on HTTP/1.1 connections
  • Impact is confined to the single upstream connection, not lateral movement
Detection/coverage: Upstream access logs may show unexpected requests. HTTP desync detection tools (e.g., Burp's HTTP Request Smuggler, Albinowax's desync techniques) can validate.
03 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNone confirmed. No campaigns, no CISA KEV listing, no vendor acknowledgment of active exploitation. Disclosed 2026-07-29 (yesterday).
Proof-of-ConceptNo public PoC. The advisory by maintainer mcollina (Matteo Collina) describes the vulnerable code path in lib/dispatcher/client-h1.js but does not include a working exploit. The same defect class was demonstrated in CVE-2022-35948 — that prior PoC is conceptually transferable but targets a different sink.
EPSS ScoreNot yet scored. CVE was published 2026-07-29; FIRST EPSS typically populates within 7–14 days. For the similar CVE-2022-35948 the EPSS settled around the 5th–10th percentile — expect a similar range here given the high AC and UI:R.
KEV StatusNot listed. No CISA Known Exploited Vulnerabilities catalog entry as of 2026-07-30.
CVSS VectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N — Network-accessible but high complexity, requires user interaction, no scope change, low C+I, no availability impact. This is an honest vector.
Affected Versionsundici < 6.28.0, 7.0.0 – 7.28.x, 8.0.0 – 8.8.x. Node.js 24 bundles undici 7.x — check process.versions.undici at runtime. Node.js 22 LTS bundles undici 6.x.
Fixed Versions6.28.0, 7.29.0, 8.9.0. Expect Node.js 22.x and 24.x point releases to bundle the fix shortly; watch nodejs/node releases.
Scanning / Exposureundici is a client-side library — it has no listening port and is invisible to Shodan/Censys/GreyNoise. Exposure depends entirely on application code patterns. npm reports ~139M weekly downloads across ~6,865 dependent packages, but the vulnerable code path (duck-typed blob + non-fetch API + untrusted .type) is a small fraction of that surface.
Disclosure TimelineCVE reserved 2026-07-08; GHSA GHSA-m8rv-5g2x-5cg5 and fix published 2026-07-29.
Reportermcollina (Matteo Collina, undici core maintainer). Remediation reviewed by UlisesGascon. This was found internally by the project, not by an external attacker or bug-bounty researcher.
04 · The Call

noisgate verdict.

Final Verdict
= UNCHANGED to MEDIUM (3.8/10)

The single most decisive factor is the narrow application-level code pattern required: the attacker must find a Node.js app that constructs duck-typed blob bodies from untrusted input and feeds them to undici's non-fetch dispatcher APIs — a coding pattern rare enough that the vast majority of undici's 139M-download installed base is unexposed. The vendor's MEDIUM is directionally correct; the real-world friction compresses the score slightly below 4.2.

HIGH Vulnerability mechanism and affected versions
HIGH Verdict direction (MEDIUM is correct bucket)
MEDIUM Fraction of installed base using the vulnerable code path
LOW EPSS prediction (CVE too new for scoring)

Why this verdict

  • Attack-complexity friction: The CVSS vector already reflects AC:H and UI:R, but the real-world narrowing is even steeper — the attacker must chain three application-level prerequisites (non-fetch API, duck-typed blob, untrusted .type) that have no external fingerprint and no reliable way to enumerate remotely.
  • Safe-by-default paths dominate: Native Blob objects (which strip CRLF in the constructor) and fetch() (which validates all headers) are the overwhelmingly common usage patterns for undici. The vulnerable path through request()/stream()/pipeline() with a hand-rolled blob-like body is a niche power-user pattern.
  • No exploitation evidence or PoC: Disclosed yesterday by the project maintainer, no public exploit code, no campaigns, no KEV listing. The prior same-class CVE-2022-35948 never saw meaningful in-the-wild exploitation either.
  • Role multiplier: undici is a general-purpose HTTP *client* library, not infrastructure-role software. (a) *Low-value role:* dev tooling, CLI scripts — chain succeeds if code pattern exists, blast radius is a single outgoing HTTP connection, local impact only. (b) *Typical role:* backend microservice making upstream API calls — chain succeeds if pattern exists, blast radius is request smuggling against one upstream, limited to that connection. (c) *High-value role:* Node.js-based API gateway or proxy layer — chain succeeds if the gateway constructs duck-typed blob bodies from user-supplied Content-Type (uncommon; most gateways pass headers through or use native Blob). Blast radius could be request smuggling against backend services, but scope remains unchanged (S:U) and impact is C:L/I:L. This does NOT reach domain/fleet/supply-chain scale. Conclusion: No high-value role produces a blast radius that triggers the HIGH floor — the worst plausible outcome is header injection against a single upstream connection.
  • Installed-base share in high-value roles: While undici has massive download counts, the *component itself* is not canonically a high-value-role component (it is not a hypervisor, IdP, PAM, backup system, kernel agent, or network edge appliance). The ≥10% canonical threshold for CRITICAL floor does not apply. The ≥1% threshold for HIGH floor does not apply because even in gateway roles the blast radius does not reach fleet/domain/supply-chain scale.

Why not higher?

Upgrading to HIGH would require either active exploitation, a public PoC lowering the bar for attackers, or a plausible attack path that reaches fleet-scale or identity-scale compromise. None of these conditions exist. The vulnerability requires an uncommon application-level coding pattern, has no scope change, and the worst-case outcome is header injection on a single outgoing HTTP/1.1 connection — not lateral movement, not privilege escalation, not code execution.

Why not lower?

Dropping to LOW would undercount the *potential* severity of HTTP request smuggling when it does land — cache poisoning and auth bypass against internal APIs are real outcomes even if the path is narrow. The 139M weekly download base means even a small percentage of vulnerable applications is a non-trivial absolute number. The bug class (CRLF injection) is well-understood and, once a PoC drops, exploitation is trivial for the specific applications that match the pattern.

05 · Compensating Control

What to do — in priority order.

  1. Pin an explicit Content-Type header in request options — Setting headers: { 'content-type': 'application/octet-stream' } (or any validated value) on the undici request options object causes undici to skip reading body.type entirely. This is a one-line code change per call site and fully neutralizes the vulnerability. No mitigation SLA applies for MEDIUM — go straight to the 365-day remediation window.
  2. Replace duck-typed blobs with native Blob objects — Native Blob constructors strip \r\n from the type parameter. Swapping { type: userInput, arrayBuffer() { ... } } to new Blob([data], { type: userInput }) eliminates the injection vector at the source. Audit your codebase with grep -rn 'arrayBuffer\|type.*blob' --include='*.js' --include='*.ts'.
  3. Use fetch() instead of undici dispatcher APIs — The fetch() path validates all headers independently and is not affected. If your application does not need the low-level streaming/pipeline features, switching to fetch() removes the vulnerable code path entirely.
  4. Upgrade undici to 6.28.0 / 7.29.0 / 8.9.0 — The definitive fix. For Node.js 22 LTS and 24 Current, monitor for a point release that bundles the patched undici version — check process.versions.undici after upgrading Node.js.
What doesn't work
  • WAF rules alone — undici is a client-side library making *outgoing* requests; your WAF inspects *incoming* requests to your server. The CRLF injection occurs on the wire between your app and its upstream, which is typically not WAF-inspected.
  • Upgrading Node.js without checking the bundled undici version — Node.js point releases don't always bump undici. Verify with node -e 'console.log(process.versions.undici)' after any Node.js update.
  • Input validation at the API gateway layer — The injection happens in application code constructing the blob body, not in HTTP request headers arriving at your gateway. Gateway-level header sanitization doesn't reach this code path.
06 · Verification

Crowdsourced verification payload.

Run this on any host with node and npm installed to check whether the installed undici version is vulnerable. Works for both standalone npm install undici and the version bundled in Node.js. No special privileges needed. Example: bash check_cve_2026_15157.sh

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_15157.sh
# Checks whether the installed undici version is vulnerable to CVE-2026-15157
# (CRLF injection via duck-typed blob .type property)
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN

set -euo pipefail

VULNERABLE=false
CHECKED=false

# Helper: compare semver "$1 < $2" (major.minor.patch)
ver_lt() {
  local IFS='.'
  read -ra A <<< "$1"
  read -ra B <<< "$2"
  for i in 0 1 2; do
    local a=${A[$i]:-0}
    local b=${B[$i]:-0}
    if (( a < b )); then return 0; fi
    if (( a > b )); then return 1; fi
  done
  return 1  # equal
}

# 1. Check Node.js bundled undici
if command -v node &>/dev/null; then
  BUNDLED=$(node -e 'try { console.log(process.versions.undici || "none") } catch(e) { console.log("none") }' 2>/dev/null || echo "none")
  if [[ "$BUNDLED" != "none" ]]; then
    CHECKED=true
    MAJOR=$(echo "$BUNDLED" | cut -d. -f1)
    if [[ "$MAJOR" -le 6 ]]; then
      if ver_lt "$BUNDLED" "6.28.0"; then
        echo "[!] Node.js bundled undici $BUNDLED is VULNERABLE (need >= 6.28.0)"
        VULNERABLE=true
      else
        echo "[+] Node.js bundled undici $BUNDLED is PATCHED"
      fi
    elif [[ "$MAJOR" -eq 7 ]]; then
      if ver_lt "$BUNDLED" "7.29.0"; then
        echo "[!] Node.js bundled undici $BUNDLED is VULNERABLE (need >= 7.29.0)"
        VULNERABLE=true
      else
        echo "[+] Node.js bundled undici $BUNDLED is PATCHED"
      fi
    elif [[ "$MAJOR" -ge 8 ]]; then
      if ver_lt "$BUNDLED" "8.9.0"; then
        echo "[!] Node.js bundled undici $BUNDLED is VULNERABLE (need >= 8.9.0)"
        VULNERABLE=true
      else
        echo "[+] Node.js bundled undici $BUNDLED is PATCHED"
      fi
    fi
  fi
fi

# 2. Check npm-installed undici in current project and global
for DIR in "$(pwd)/node_modules/undici" "$(npm root -g 2>/dev/null)/undici"; do
  PKG="$DIR/package.json"
  if [[ -f "$PKG" ]]; then
    CHECKED=true
    VER=$(node -e "console.log(require('$PKG').version)" 2>/dev/null || echo "unknown")
    if [[ "$VER" == "unknown" ]]; then continue; fi
    MAJOR=$(echo "$VER" | cut -d. -f1)
    LABEL="npm undici $VER ($DIR)"
    if [[ "$MAJOR" -le 6 ]]; then
      if ver_lt "$VER" "6.28.0"; then
        echo "[!] $LABEL is VULNERABLE"
        VULNERABLE=true
      else
        echo "[+] $LABEL is PATCHED"
      fi
    elif [[ "$MAJOR" -eq 7 ]]; then
      if ver_lt "$VER" "7.29.0"; then
        echo "[!] $LABEL is VULNERABLE"
        VULNERABLE=true
      else
        echo "[+] $LABEL is PATCHED"
      fi
    elif [[ "$MAJOR" -ge 8 ]]; then
      if ver_lt "$VER" "8.9.0"; then
        echo "[!] $LABEL is VULNERABLE"
        VULNERABLE=true
      else
        echo "[+] $LABEL is PATCHED"
      fi
    fi
  fi
done

# 3. Verdict
if [[ "$CHECKED" == false ]]; then
  echo "UNKNOWN — no undici installation found"
  exit 2
elif [[ "$VULNERABLE" == true ]]; then
  echo "VULNERABLE"
  exit 1
else
  echo "PATCHED"
  exit 0
fi
07 · Bottom Line

If you remember one thing.

TL;DR
This is a MEDIUM by noisgate reassessment — no mitigation SLA applies, so go straight to the noisgate remediation SLA of ≤ 365 days. That said, the fix is trivial (bump undici to 6.28.0 / 7.29.0 / 8.9.0 or set an explicit content-type header), so there's no reason to wait a full year. Practically: add the undici bump to your next scheduled Node.js dependency update cycle. If you run Node.js 22 LTS or 24 Current, watch for the next point release that bundles the patched undici — verify with node -e 'console.log(process.versions.undici)'. In the meantime, audit your codebase for the specific vulnerable pattern: grep for duck-typed blob-like bodies whose .type comes from user input in undici.request() / stream() / pipeline() / dispatch() calls. If you find any, apply the one-line compensating control (set an explicit content-type header) today. If you use only fetch() or native Blob objects, you are already safe and this CVE is informational for your environment.

Sources

  1. GHSA-m8rv-5g2x-5cg5 — GitHub Security Advisory
  2. CVE-2026-15157 — THREATINT
  3. Vigilance.fr — Node.js undici header injection via Content-Type
  4. SecurityOnline — Four undici Vulnerabilities Affect 133M Weekly Downloads
  5. Tenable — Node.js Module Undici Multiple Vulnerabilities
  6. undici npm package
  7. Node.js 24.0.0 Release Notes (bundled undici)
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.