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.
4 steps from start to impact.
Attacker operates a malicious WebSocket server
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.- Attacker controls or compromises a WebSocket server endpoint
- Target Node.js application connects to that endpoint as a WebSocket client
- 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
Victim application initiates WebSocket handshake
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.- 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)
- 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
Invalid window bits bypass validation
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.- Vulnerable validation logic present (pre-6.24.0 / pre-7.24.0)
- None at this step — the code path is deterministic once the handshake succeeds
First compressed frame triggers uncaught RangeError → process crash
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.- Server sends at least one compressed frame after the handshake
- 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
RangeError originating from zlib.createInflateRaw in the undici WebSocket code path. APM tools (Datadog, New Relic, Sentry) will surface the uncaught exception immediately.The supporting signals.
| In-the-wild exploitation | None observed. Not listed in CISA KEV. No campaigns or threat-actor usage reported as of September 2026. |
|---|---|
| Proof-of-concept | No 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. |
| EPSS | Not yet scored or below reporting threshold. Expected to remain low given client-side-only, DoS-only impact. |
| KEV status | Not listed. No CISA KEV entry. |
| CVSS vector | CVSS: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 versions | undici < 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 versions | undici 6.24.0, 7.24.0. Distro backports: RHSA-2026:7302, RHSA-2026:7310, RHSA-2026:7670. |
| Scanning / exposure | Not 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 date | 2026-03-13 (GitHub Security Advisory published). |
| Reporter | aisle-research via HackerOne (report #3487486). |
noisgate verdict.
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.
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 theWebSocketconstructor is used withpermessage-deflateenabled. - Impact ceiling is availability-only. The bug produces a
RangeErrorcrash — 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.
What to do — in priority order.
- Pin or upgrade undici to ≥ 6.24.0 or ≥ 7.24.0 — The only complete fix. Update
package.json/package-lock.jsonor 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. - 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. - Audit outbound WebSocket connections to untrusted endpoints — Inventory which services use undici's
WebSocketconstructor to connect to external or user-controlled endpoints. Services that only usefetch()for HTTP are not affected. Prioritize patching the services that consume untrusted WebSocket feeds. - Ensure process manager auto-restart is configured — Kubernetes liveness probes, PM2
--restart-delay, or systemdRestart=alwayslimit the blast radius of a crash to a brief interruption (typically < 5 seconds). This does not prevent the crash but bounds the impact.
- 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: websocketheaders 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.
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
#!/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 $RESULTIf you remember one thing.
WebSocket — most of your Node.js fleet probably only uses HTTP fetch() and is not exercising the vulnerable code path at all.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.