← Back to Feed CACHED · 2026-09-04 16:50:01 · CACHE_KEY CVE-2026-85024
CVE-2026-85024 · CWE-248 · Disclosed 2026-03-13

undici vulnerable to Denial of Service via unhandled error in WebSocket permessage-deflate decompression

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

Like a mail bomb that only detonates if *you* walk into the bomber's house and open the package yourself

CVE-2026-85024 (tracking the same root cause as GHSA-v9p9-hfj2-hcw8 / CVE-2026-2229) is an uncaught-exception crash in undici's WebSocket client. When a Node.js process connects as a WebSocket client to a server that advertises permessage-deflate with an out-of-range server_max_window_bits value (outside zlib's valid 8–15), undici passes it straight to zlib.createInflateRaw() without a try-catch. The synchronous RangeError propagates uncaught and terminates the process immediately. Affected: undici < 6.24.0 and 7.0.0–7.23.x. Fixed in 6.24.0 and 7.24.0. Because undici ships bundled in Node.js 20.x, 22.x, and 24.x, any app on those runtimes using the built-in WebSocket global or importing undici directly is in scope.

The vendor's MEDIUM / 5.9 rating with AC:H is honest and accurate. The crucial constraint is directionality: this is a *client-side* bug. The attacker cannot scan you, cannot send you a packet, cannot exploit you remotely — your application must initiate an outbound WebSocket connection to an endpoint the attacker controls. That alone compresses the reachable population to Node.js services that act as WebSocket consumers of untrusted or externally-controlled endpoints. Most undici usage is plain HTTP fetch; WebSocket client usage is a fraction of that. The impact ceiling is a process crash (availability), with no path to code execution, data exfiltration, or lateral movement. NVD lists a parallel score of 7.5 HIGH (with AC:L), but the GitHub advisory's 5.9 MEDIUM with AC:H better reflects the real-world prerequisite that the victim must voluntarily connect outbound to the attacker's server.

"Client-side WebSocket DoS in undici — crash requires your app to call the attacker's server first."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Attacker operates a malicious WebSocket server

The attacker stands up a WebSocket server on a public endpoint (or compromises an existing one). The server is configured to negotiate the permessage-deflate extension with an invalid server_max_window_bits value such as 99 or 0. No special tooling beyond a simple WebSocket server library is needed — a 20-line Node.js or Python script suffices.
Conditions required:
  • Attacker controls or compromises a WebSocket server endpoint
  • Target Node.js application connects to that endpoint as a WebSocket client
Where this breaks in practice:
  • The attacker cannot force the victim to connect — the victim's code must already be configured to reach the attacker's endpoint, or the attacker must compromise/hijack a legitimate endpoint the victim uses
  • DNS hijacking or MITM to redirect an existing WS connection requires a separate, prior attack
STEP 02

Victim application initiates WebSocket handshake

The target Node.js process opens a WebSocket connection using undici (directly or via Node.js's built-in WebSocket global). During the HTTP Upgrade handshake, undici automatically advertises support for permessage-deflate. The server responds with Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=99.
Conditions required:
  • Application uses undici's WebSocket client with default settings (permessage-deflate is on by default)
  • Vulnerable undici version (< 6.24.0 or 7.0.0–7.23.x)
Where this breaks in practice:
  • Many Node.js applications never use WebSocket at all — they use undici for plain HTTP fetch()
  • Applications connecting only to trusted internal services are not exposed unless those internal services are compromised first
Detection/coverage: SCA/SBOM scanners (Snyk, Dependabot, Trivy, Grype) flag the vulnerable undici version. Tenable plugin 309957 detects vulnerable undici modules.
STEP 03

Invalid window bits bypass validation

undici's isValidClientWindowBits() function checks only that the value contains ASCII digits — it does not enforce the RFC 7692 / zlib requirement that the value be between 8 and 15. The value 99 passes the digit check and is stored for later use when the first compressed frame arrives.
Conditions required:
  • Vulnerable validation logic present (pre-6.24.0 / pre-7.24.0)
Where this breaks in practice:
  • None at this step — the code path is deterministic once the handshake succeeds
STEP 04

First compressed frame triggers uncaught RangeError → process crash

When the server sends any compressed WebSocket frame, undici calls zlib.createInflateRaw({ windowBits: 99 }). Node.js's zlib binding throws a synchronous RangeError because 99 is outside the valid range. Because the call is not wrapped in a try-catch, the exception propagates uncaught and terminates the Node.js process immediately via the default uncaughtException handler.
Conditions required:
  • Server sends at least one compressed frame after the handshake
Where this breaks in practice:
  • Process managers (PM2, systemd, Kubernetes liveness probes) will auto-restart the process, limiting sustained impact to a brief service interruption
  • A global process.on('uncaughtException') handler — while generally discouraged — would catch this and prevent the crash
Detection/coverage: Application crash logs will show RangeError originating from zlib.createInflateRaw in the undici WebSocket code path. APM tools (Datadog, New Relic, Sentry) will surface the uncaught exception immediately.
03 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. Not listed in CISA KEV. No campaigns or threat-actor usage reported as of September 2026.
Proof-of-conceptNo standalone PoC repository published. The advisory description (GHSA-v9p9-hfj2-hcw8) and HackerOne report #3487486 provide enough detail to reproduce trivially — a ~20-line WebSocket server script is sufficient.
EPSSNot yet scored or below reporting threshold. Expected to remain low given client-side-only, DoS-only impact.
KEV statusNot listed. No CISA KEV entry.
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H → 5.9 MEDIUM (GitHub advisory). NVD lists an alternate 7.5 HIGH score with AC:L. The AC:H rating is more realistic because the victim must be connecting outbound to an attacker-controlled server.
Affected versionsundici < 6.24.0 (all 6.x), 7.0.0 – 7.23.x. Bundled in Node.js 20.x (undici 6.x), Node.js 22.x (undici 6.x), Node.js 24.x (undici 7.x).
Fixed versionsundici 6.24.0, 7.24.0. Distro backports: RHSA-2026:7302, RHSA-2026:7310, RHSA-2026:7670.
Scanning / exposureNot externally scannable — this is a client-side library vulnerability, not a listening service. Asset inventory via SBOM or SCA is the only detection path. Tenable plugin 309957 covers undici < 6.24.0 / 7.x < 7.24.0.
Disclosure date2026-03-13 (GitHub Security Advisory published).
Reporteraisle-research via HackerOne (report #3487486).
04 · The Call

noisgate verdict.

Final Verdict
= UNCHANGED to MEDIUM (5.9/10)

The single most decisive factor is attack directionality: this is a client-side vulnerability where the victim application must initiate an outbound WebSocket connection to an attacker-controlled server, compressing the reachable population to the subset of Node.js services that consume untrusted WebSocket endpoints. The impact ceiling is a process crash (DoS) with no path to code execution, data theft, or lateral movement, which caps severity at MEDIUM even under worst-case role assumptions.

HIGH Vulnerability mechanics and affected versions
HIGH Severity assessment
MEDIUM Absence of in-the-wild exploitation (negative evidence is inherently lower confidence)

Why this verdict

  • Attack directionality is the dominant constraint. Unlike a server-side RCE, the attacker cannot scan the internet for victims. The victim's code must already be configured to open a WebSocket connection to an endpoint the attacker controls or has compromised. This is a meaningful, load-bearing prerequisite that filters out the vast majority of the undici install base.
  • WebSocket client usage is a fraction of undici's footprint. Most Node.js applications use undici (or Node's built-in fetch()) for HTTP requests, not WebSocket connections. The vulnerable code path is only reached when the WebSocket constructor is used with permessage-deflate enabled.
  • Impact ceiling is availability-only. The bug produces a RangeError crash — no memory corruption, no attacker-controlled data written, no code execution. Kubernetes, PM2, or systemd restart policies limit sustained outage to seconds.
  • Role multiplier: (a) *Low-value roles* (dev machines, test servers): crash is a minor annoyance, auto-restarted. (b) *Typical roles* (backend API services): most don't use WebSocket client at all; those that do typically connect to trusted internal services, not attacker-controlled endpoints. (c) *High-value roles* — CI/CD runners fetching from WebSocket feeds, real-time trading/monitoring services consuming external WS endpoints: a crash here causes brief service disruption but the blast radius is host-level DoS only, not domain/fleet/supply-chain compromise. No high-value role produces an outcome exceeding availability impact, so the floor rule does not elevate severity.

Why not higher?

Upgrading to HIGH would require either active exploitation, a path to code execution, or a blast radius beyond a single-process crash. None of these apply. The client-side directionality constraint means an attacker cannot mass-exploit this at scale — they must wait for victims to connect to them. The impact is strictly availability; there is no confidentiality or integrity impact.

Why not lower?

Downgrading to LOW would undervalue the zero-interaction aspect once the connection is established (no authentication, no user interaction required beyond the initial outbound connection) and the fact that undici is bundled in every Node.js 20+ runtime, creating a large latent attack surface. The process crash is immediate and deterministic, not probabilistic.

05 · Compensating Control

What to do — in priority order.

  1. Pin or upgrade undici to ≥ 6.24.0 or ≥ 7.24.0 — The only complete fix. Update package.json / package-lock.json or upgrade Node.js to a release that bundles the patched undici. Since this is MEDIUM, target the noisgate remediation SLA of 365 days, but earlier is better if your next Node.js update cycle is sooner.
  2. Add a global uncaughtException handler as a stopgap — Adding process.on('uncaughtException', (err) => { /* log and recover */ }) prevents immediate process termination from this specific crash. This is a tactical band-aid, not a real fix — it masks the root cause. Use only as a bridge until you can upgrade.
  3. Audit outbound WebSocket connections to untrusted endpoints — Inventory which services use undici's WebSocket constructor to connect to external or user-controlled endpoints. Services that only use fetch() for HTTP are not affected. Prioritize patching the services that consume untrusted WebSocket feeds.
  4. Ensure process manager auto-restart is configured — Kubernetes liveness probes, PM2 --restart-delay, or systemd Restart=always limit the blast radius of a crash to a brief interruption (typically < 5 seconds). This does not prevent the crash but bounds the impact.
What doesn't work
  • WAF / reverse proxy rules — The vulnerability is in the outbound WebSocket *client*, not in an inbound server path. No amount of ingress filtering helps because the malicious payload is in the server's response to the victim's outbound connection.
  • Disabling WebSocket at the network level — Blocking outbound Upgrade: websocket headers would prevent the attack but would also break all legitimate WebSocket client functionality, which is likely unacceptable for services that depend on it.
  • Rate limiting — The crash is triggered by a single frame in a single connection. Rate limiting has no effect.
06 · Verification

Crowdsourced verification payload.

Run this on each target host or in your CI pipeline to check the installed undici version. Requires node and npm (or npx) on $PATH. No elevated privileges needed. Example: bash check_undici.sh

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_undici.sh — Detect CVE-2026-85024 (undici WebSocket permessage-deflate DoS)
# Run on any host with Node.js installed. No root required.
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN

set -euo pipefail

CVE="CVE-2026-85024"
FIXED_6x="6.24.0"
FIXED_7x="7.24.0"

version_gte() {
  # Returns 0 if $1 >= $2 using sort -V
  [ "$(printf '%s\n%s' "$1" "$2" | sort -V | head -n1)" = "$2" ]
}

# Try to get undici version from the Node.js bundled copy first
BUNDLED_VER=$(node -e "try { console.log(require('undici/package.json').version) } catch(e) { console.log('none') }" 2>/dev/null || echo "none")

# Also check local node_modules
LOCAL_VER="none"
if [ -f node_modules/undici/package.json ]; then
  LOCAL_VER=$(node -e "console.log(require('./node_modules/undici/package.json').version)" 2>/dev/null || echo "none")
fi

check_version() {
  local ver="$1"
  local src="$2"
  if [ "$ver" = "none" ]; then
    return 2
  fi

  MAJOR=$(echo "$ver" | cut -d. -f1)

  if [ "$MAJOR" -le 6 ]; then
    if version_gte "$ver" "$FIXED_6x"; then
      echo "PATCHED — $src undici $ver >= $FIXED_6x ($CVE)"
      return 0
    else
      echo "VULNERABLE — $src undici $ver < $FIXED_6x ($CVE)"
      return 1
    fi
  elif [ "$MAJOR" -eq 7 ]; then
    if version_gte "$ver" "$FIXED_7x"; then
      echo "PATCHED — $src undici $ver >= $FIXED_7x ($CVE)"
      return 0
    else
      echo "VULNERABLE — $src undici $ver < $FIXED_7x ($CVE)"
      return 1
    fi
  else
    # Major version 8+ is assumed patched
    echo "PATCHED — $src undici $ver (major version $MAJOR, post-fix) ($CVE)"
    return 0
  fi
}

RESULT=2  # default UNKNOWN

if [ "$BUNDLED_VER" != "none" ]; then
  check_version "$BUNDLED_VER" "bundled" && RESULT=0 || RESULT=$?
fi

if [ "$LOCAL_VER" != "none" ]; then
  check_version "$LOCAL_VER" "local" && LOCAL_RESULT=0 || LOCAL_RESULT=$?
  # If local is vulnerable, override to vulnerable even if bundled is OK
  if [ "$LOCAL_RESULT" -eq 1 ]; then
    RESULT=1
  fi
fi

if [ "$BUNDLED_VER" = "none" ] && [ "$LOCAL_VER" = "none" ]; then
  echo "UNKNOWN — undici not found (not bundled, not in node_modules) ($CVE)"
  exit 2
fi

exit $RESULT
07 · Bottom Line

If you remember one thing.

TL;DR
This is a MEDIUM-severity client-side DoS in undici's WebSocket implementation — your app has to be connecting *outbound* to an attacker-controlled WebSocket server to be hit, and the worst outcome is a process crash (no RCE, no data leak). There is no noisgate mitigation SLA for MEDIUM — go straight to the noisgate remediation SLA of 365 days: roll the undici upgrade (≥ 6.24.0 or ≥ 7.24.0) into your next scheduled Node.js dependency update cycle. If you have services that consume WebSocket feeds from untrusted or external endpoints, prioritize those for earlier patching. Verify restart policies (Kubernetes liveness, PM2, systemd) are in place so a crash self-heals in seconds. Run an SBOM scan to identify which apps actually import undici's WebSocket — most of your Node.js fleet probably only uses HTTP fetch() and is not exercising the vulnerable code path at all.

Sources

  1. GitHub Security Advisory GHSA-v9p9-hfj2-hcw8
  2. GitLab Advisory Database — CVE-2026-2229
  3. OSV — GHSA-v9p9-hfj2-hcw8
  4. NVD — CVE-2026-2229
  5. Tenable Plugin 309957 — undici Multiple Vulnerabilities
  6. Red Hat Errata RHSA-2026:7302
  7. GHSA-vrm6-8vpv-qv8q — Related undici permessage-deflate advisory
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.