Like leaving the crash-test dummy in the driver's seat — the car only wrecks if you steer it into the wall yourself
CVE-2026-85014 is an uncaught-exception (CWE-248) denial-of-service in undici's WebSocketStream implementation. When a Node.js application initiates a WebSocketStream connection and the remote server drops the TCP socket without completing the WebSocket close handshake (an "unclean close"), the #onSocketClose callback attempts to read this.#parser.closingInfo before the parser has been initialized. The resulting TypeError propagates unhandled, crashing the Node.js process. The bug affects applications that use the newer WebSocketStream API — not the older new WebSocket(…) constructor — and specifically requires the *client* to connect to a server under attacker influence. Affected versions span undici 7.x prior to the WebSocketStream close-handling fix (aligned with Node.js v24 line); the 6.x branch is unaffected because WebSocketStream was not backported to it.
The vendor's MEDIUM / 5.9 rating is *fair but slightly generous*. The CVSS vector already encodes AC:H, reflecting the need for specific conditions, but it doesn't capture two real-world narrowing factors: (1) WebSocketStream adoption is a small fraction of all undici WebSocket usage — most applications use the standard WebSocket class, and (2) the crash is a single-process DoS that is auto-recoverable under any modern process manager (PM2, systemd, Kubernetes liveness probe). noisgate trims the effective score to 4.5 while keeping the MEDIUM bucket.
4 steps from start to impact.
Identify a target Node.js service using WebSocketStream
new WebSocketStream(url) to connect outbound. This is the critical prerequisite — the victim application must *initiate* the connection to a server the attacker controls or can compromise. Applications that only *accept* inbound WebSocket connections (typical server pattern) are not affected.- Target application uses undici
WebSocketStreamAPI (not standardWebSocket) - Target connects outbound to an endpoint the attacker can influence
- WebSocketStream is a newer, less-adopted API; most Node.js WebSocket code uses
ws,socket.io, or undici'sWebSocketclass - Applications rarely connect to fully untrusted WebSocket endpoints without URL allowlisting
Attacker-controlled server accepts the WebSocket upgrade
- Attacker controls the WebSocket server endpoint (own infrastructure, DNS hijack, or SSRF to redirect)
- DNS hijack or SSRF is a separate, higher-difficulty prerequisite that compounds the attack cost
Server performs unclean TCP close before parser initialization completes
#onConnectionEstablished() assigns the parser. This leaves this.#parser as undefined. The timing window is narrow, which is why the CVSS vector carries AC:H.- Attacker must time the TCP reset to land in the initialization window
- The race window is small; many attempts may result in a normal connection error rather than the crash path
- Network latency and OS TCP stack behavior add non-determinism
Uncaught TypeError crashes the Node.js process
#onSocketClose() fires and tries to read this.#parser.closingInfo. Since the parser is undefined, a TypeError is thrown. Because undici does not wrap this call in a try/catch and does not use a domain or unhandledRejection handler, the exception is fatal — the process exits with code 1. Under Kubernetes or PM2, the process restarts within seconds.- No application-level
uncaughtExceptionhandler catches TypeError
- Most production Node.js deployments run behind a process manager that auto-restarts on crash
- A global
process.on('uncaughtException', …)handler — common in production — swallows this crash entirely
CrashLoopBackOff alerts) surface this immediately.The supporting signals.
| In-the-wild exploitation | None observed. No public reports of active exploitation. Not listed in CISA KEV. |
|---|---|
| Proof-of-concept | No public weaponized PoC. The triggering condition (server-side unclean close during init window) is documented in undici issue #4732 with a reproduction scenario, but no standalone exploit tool exists. |
| EPSS | Estimated < 1% probability of exploitation in 30 days (below 20th percentile), consistent with similar undici DoS CVEs (CVE-2026-2229 EPSS 0.49%, CVE-2026-1526 EPSS ~0.1%). |
| KEV status | Not listed. No CISA KEV entry as of 2026-09-05. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H — Network-reachable but high attack complexity (race window), availability-only impact, no scope change. The AC:H drives the 5.9 score down from the 7.5 that similar AC:L undici WebSocket bugs receive. |
| Affected versions | undici 7.x series (WebSocketStream implementation) prior to the close-handling fix. The 6.x branch does not ship WebSocketStream. The 8.x branch likely inherits the fix from the 7.x patch. |
| Fixed versions | undici ≥ 7.24.0 (or later patch release addressing WebSocketStream lifecycle). Node.js v24.x users should update the bundled undici or upgrade Node. |
| Scanning / exposure data | undici has ~150M+ weekly npm downloads, but the vulnerable surface is limited to the subset using WebSocketStream outbound connections — estimated at < 1% of the install base based on API novelty. |
| Disclosure date | ~2026 (aligned with Node.js v24 / undici 7.18+ timeframe, per issue #4732). |
| Reporting researcher | Community-reported via GitHub issue tracker; fix delivered in PR #4748. |
noisgate verdict.
The single most decisive downgrade factor is the extremely narrow exposure population: only applications using the WebSocketStream API (not standard WebSocket) that also connect outbound to attacker-influenced servers are reachable, representing well under 1% of undici's installed base. The availability-only impact (process crash) is auto-recoverable in any production deployment using a process supervisor.
Why this verdict
- Narrow API surface:
WebSocketStreamis a newer, less-adopted API. The overwhelming majority of Node.js WebSocket usage goes throughws,socket.io, or undici's standardWebSocketclass — none of which are affected. - Attacker-influenced server required: The victim app must *initiate* the WebSocket connection to an endpoint the attacker controls or can redirect to (via DNS hijack / SSRF). Server-side WebSocket listeners — the dominant pattern — are not vulnerable.
- Race condition (AC:H): The TCP close must land in the narrow initialization window before the parser is assigned. This is non-deterministic and many attempts will fail silently.
- Auto-recoverable crash: Process managers (PM2, systemd, Kubernetes) restart the process in seconds. The blast radius is a brief availability blip, not persistent compromise.
- Role multiplier: undici ships with Node.js (~150M weekly downloads), so the *theoretical* install base is enormous. However,
WebSocketStream-based outbound connections are vanishingly rare in high-value roles. In CI/CD pipelines, API servers, and backend services, the standardWebSocketor HTTP client paths dominate. No high-value-role scenario (IdP, hypervisor, PAM, backup, EDR) canonically relies on undiciWebSocketStreamoutbound connections. The blast radius in any deployment is single-process DoS — it does not escalate to host, domain, or fleet compromise. The floor rule does not elevate this beyond MEDIUM.
Why not higher?
Elevating to HIGH would require either active exploitation evidence, a broader affected API surface, or an impact beyond single-process availability disruption. None of these conditions are met. The AC:H race window, the requirement for an attacker-controlled server destination, and the auto-recoverability of the crash all argue against a higher rating.
Why not lower?
Dropping to LOW would undercount the fact that this *is* a remotely triggerable, unauthenticated process crash with no user interaction required. If a vulnerable application does connect to untrusted WebSocket endpoints (e.g., a proxy, aggregator, or federation service), the crash is reliable enough after repeated attempts. The 4.5 score preserves the signal that this bug deserves a patch, just not an emergency one.
What to do — in priority order.
- Add a global
uncaughtExceptionhandler to Node.js services — Aprocess.on('uncaughtException', handler)catches the fatalTypeErrorand keeps the process alive. This neutralizes the crash path entirely while you schedule the undici upgrade within the noisgate remediation SLA of 365 days. - Allowlist outbound WebSocket destinations — If your application uses
WebSocketStream, restrict the target URLs to a known-good allowlist via environment config or a network-layer egress policy. This eliminates the attacker-controlled-server prerequisite. - Ensure process-manager auto-restart is configured — Verify that PM2, systemd
Restart=always, or Kubernetes liveness probes are active for all Node.js services. This limits blast radius to a momentary restart even if the crash fires. No additional SLA — this should already be in place. - Upgrade undici to ≥ 7.24.0 or pin Node.js to a patched release — The definitive fix. Schedule within the 365-day noisgate remediation SLA for MEDIUM findings. If you track Node.js LTS releases, confirm the bundled undici version includes the WebSocketStream close-handling patch.
- WAF / reverse proxy in front of the Node.js app — The vulnerability is in the *outbound* WebSocket client, not in inbound request handling. A WAF inspecting inbound traffic has no visibility into the app's outbound WebSocket connections.
- Rate limiting inbound requests — The crash is triggered by the remote *server's* behavior (unclean close), not by the volume or shape of inbound client requests to your app.
- Disabling WebSocket compression (permessage-deflate) — This mitigates CVE-2026-2229 and CVE-2026-1526 but not this bug, which is in the close-handling lifecycle regardless of compression settings.
Crowdsourced verification payload.
Run on any host with Node.js installed to check whether the bundled or installed undici version is vulnerable. No special privileges required. Example: bash check_undici.sh
#!/usr/bin/env bash
# check_undici.sh — Detect CVE-2026-85014 (undici WebSocketStream unclean close DoS)
# Run on any host with node installed. No privileges required.
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
# Try to get undici version from node's built-in
UNDICI_VER=$(node -e "try { const v = process.versions.undici || require('undici/package.json').version; console.log(v); } catch(e) { console.log('NONE'); }" 2>/dev/null || echo "NONE")
if [ "$UNDICI_VER" = "NONE" ]; then
echo "UNKNOWN — undici not found (not bundled or not installed)"
exit 2
fi
echo "Detected undici version: $UNDICI_VER"
# Parse major and minor
MAJOR=$(echo "$UNDICI_VER" | cut -d. -f1)
MINOR=$(echo "$UNDICI_VER" | cut -d. -f2)
# 6.x is not affected (no WebSocketStream)
if [ "$MAJOR" -le 6 ]; then
echo "PATCHED — undici $UNDICI_VER (6.x branch does not include WebSocketStream)"
exit 0
fi
# 7.x: fixed in 7.24.0+
if [ "$MAJOR" -eq 7 ]; then
if [ "$MINOR" -ge 24 ]; then
echo "PATCHED — undici $UNDICI_VER (>= 7.24.0)"
exit 0
else
echo "VULNERABLE — undici $UNDICI_VER (7.x < 7.24.0)"
exit 1
fi
fi
# 8.x+: assume patched (fix forward-ported)
if [ "$MAJOR" -ge 8 ]; then
echo "PATCHED — undici $UNDICI_VER (8.x includes close-handling fix)"
exit 0
fi
echo "UNKNOWN — unexpected version $UNDICI_VER"
exit 2If you remember one thing.
uncaughtException handler as a zero-cost safety net. If you operate any services that use WebSocketStream to connect to *untrusted* external endpoints (aggregators, federation relays, proxy services), prioritize those for earlier patching — but for the vast majority of your 10,000-host fleet, this CVE will not be reachable.Sources
- undici GitHub issue #4732 — WebSocketStream internal error on connection failure
- GHSA-v9p9-hfj2-hcw8 — Unhandled Exception in WebSocket Client (CVE-2026-2229)
- Snyk — Uncaught Exception in undici (CVE-2026-1528)
- GHSA-vxpw-j846-p89q — WebSocket fragment count bypass DoS (CVE-2026-12151)
- undici releases — GitHub
- NVD — CVE-2026-12151 Detail
- undici npm package
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.