Oct 24, 2026·11 min read·3 visits
Node.js undici versions prior to 6.29.1, 7.30.1, and 8.12.2 contain an unbounded memory allocation flaw in the EventSource stream parser. Remote SSE servers can crash client applications via heap exhaustion by streaming fragmented chunks or continuous data lines without event boundaries.
A resource allocation vulnerability (CWE-770) exists in the Server-Sent Events (SSE) parser within the Node.js undici HTTP client library. In affected versions, the EventSourceStream implementation fails to restrict the count of internal Buffer chunk references and accumulated string nodes prior to event dispatch boundaries. A malicious or compromised SSE endpoint can exploit this behavior by streaming fragmented, unterminated lines or continuous sequences of short data fields without empty separator lines, bypassing configured byte-size limits and causing a fatal Out-of-Memory (OOM) crash in the client Node.js process.
undici is the official HTTP/1.1 and HTTP/2 client library maintained by the Node.js project. Beyond serving as a standalone package in the npm ecosystem, undici powers the native fetch and EventSource web standards implementations integrated directly into the Node.js runtime. The EventSource interface enables client applications to establish persistent HTTP connections to receive Server-Sent Events (SSE) streamed using the text/event-stream MIME type.
In undici versions 6.5.0 through 6.29.0, 7.0.0 through 7.30.0, and 8.0.0 through 8.12.1, the streaming SSE parser located in lib/web/eventsource/eventsource-stream.js contains an allocation of resources without limits vulnerability (CWE-770). While the parser enforces raw byte-length limits via configuration parameters such as maxEventSize and maxResponseSize, it does not constrain the quantity of discrete JavaScript object wrappers, Buffer instances, or V8 internal string nodes created while processing incoming network streams.
When a Node.js application connects via EventSource to an untrusted or compromised remote server, the remote peer can structure its HTTP response stream to maximize object allocation overhead while remaining well below the raw byte thresholds. Because each small chunk or individual field line incurs substantial metadata overhead in the V8 JavaScript engine, the host process exhausts its available heap memory and terminates abruptly with an unrecoverable Out-of-Memory (OOM) fatal error.
The root cause of CVE-2026-109924 lies in how the EventSourceStream class manages state across stream transforms before an SSE message is finalized. According to the W3C Server-Sent Events specification, an SSE stream consists of text lines separated by newline characters (\r\n, \n, or \r). Individual fields (data:, event:, id:, retry:) are parsed line by line and accumulated into a pending event state object. The pending event is only dispatched to the application listener when the parser encounters a blank line (two consecutive newline sequences, such as \n\n).
Three specific architectural flaws in EventSourceStream allow memory consumption to grow unbounded prior to event dispatch:
First, the parser buffers incoming stream chunks in an internal array (this.chunks) until a newline delimiter is encountered. When checking whether an unterminated line exceeds permitted limits, the parser previously evaluated only this.currentLineSize, which tracks the sum of payload bytes across chunks. If a remote peer transmits an unfinished line fragmented across millions of 1-byte TCP segments or stream chunks, this.chunks.push(chunk) retains a full Node.js Buffer object wrapper and array reference for every single byte. The V8 heap memory consumed by these object wrappers is orders of magnitude larger than the raw byte count tracked by this.currentLineSize.
Second, when processing completed data: lines, the parser appends each line to the pending event's data property using string concatenation (event.data += \\n${value}`). In V8, repeated concatenation constructs a tree of internal ConsString(concatenated string) heap nodes rather than a flat byte array. Furthermore,this.maxEventSizedefaulted tobuffer.kStringMaxLength(up to 1 GB on 64-bit architectures). If a server continuously transmits minimaldata:\n lines without ever sending a blank line (\n\n) to flush the event, the accumulation of V8 string nodes and garbage collection tracking structures exhausts the Node.js heap long before the raw character count approaches maxEventSize. In addition, non-data fields (id:, event:, retry:`) were not accounted for properly against the unified event size ceiling.
Third, wire-level size enforcement via maxResponseSize operates on the compressed HTTP payload prior to content decoding. When a server responds with Content-Encoding: gzip or deflate, a highly compressible sequence of repeated data:\n lines measuring only a few kilobytes on the network inflates into hundreds of megabytes of uncompressed stream chunks inside the client process. This decompression amplification bypasses wire-level byte checks and feeds directly into the vulnerable EventSourceStream parser.
Prior to the security patch, lib/web/eventsource/eventsource-stream.js relied exclusively on byte counters to bound memory usage during line buffering and field parsing. In the pre-patch implementation, incoming chunks lacking a newline delimiter were appended directly to this.chunks as long as this.currentLineSize remained under the byte limit:
// Vulnerable pre-patch chunk buffering and data field handling
if (this.currentLineSize > this.getMaxEventSizeLineLength()) {
callback(createMaxEventSizeExceededError())
return
}
// When no newline is found in the chunk, the Buffer reference is pushed without counting items
this.chunks.push(chunk)
// During line processing, only raw byte length of 'data' fields was checked
if (isFieldName(line, fieldLength, DATA)) {
const valueBytes = line.length - valueStart
const eventDataSize = this.eventDataSize + (event.data === undefined ? 0 : 1) + valueBytes
if (this.maxEventSize > 0 && eventDataSize > this.maxEventSize) {
throw createMaxEventSizeExceededError()
}
this.eventDataSize = eventDataSize
const value = line.subarray(valueStart).toString('utf8')
if (event.data === undefined) {
event.data = value
} else {
event.data += `\n${value}`
}
}The remediation, implemented in commits such as 789ef26461c648480433846607154f52d74ec9ab, addresses these issues by introducing strict structural item limits alongside comprehensive byte accounting across all field types.
// Patched implementation in lib/web/eventsource/eventsource-stream.js
const minParserItems = 1024
const maxParserItems = 131072
const bytesPerParserItem = 128
// Inside EventSourceStream constructor:
const parserItemLimit = this.maxEventSize > 0
? Math.min(maxParserItems, Math.max(minParserItems, Math.floor(this.maxEventSize / bytesPerParserItem)))
: 0
// Cap both retained data lines per event and buffered chunks per line
this.maxDataLines = parserItemLimit
this.maxBufferedChunks = parserItemLimit
this.dataLineCount = 0
this.eventFieldSizes = {
data: 0,
event: 0,
id: 0,
retry: 0
}When buffering incoming chunks or appending data: lines, the patched code now verifies both the structural item count and the combined byte size across all event metadata fields:
// Enforce maximum buffered chunk count before pushing to this.chunks
if (this.maxBufferedChunks > 0 && this.chunks.length >= this.maxBufferedChunks) {
callback(createMaxEventSizeExceededError())
return
}
this.chunks.push(chunk)
// Enforce maximum data line count per event during field processing
if (this.maxDataLines > 0 && this.dataLineCount >= this.maxDataLines) {
throw createMaxEventSizeExceededError()
}
this.dataLineCount++Additionally, the patch adds a _destroy lifecycle method to EventSourceStream that explicitly zeroes this.chunks.length = 0 and resets parser counters when the stream is aborted or encounters an error. This ensures that retained Buffer references are immediately eligible for garbage collection. The fix is complete against both chunk fragmentation and field accumulation vectors because it bounds both the total byte volume and the discrete object count (1024 to 131072 items depending on maxEventSize).
Exploiting CVE-2026-109924 requires the target Node.js application to initiate an outbound EventSource connection to an HTTP endpoint controlled or compromised by the attacker. No authentication or user interaction on the client host is required once the connection is established. This scenario commonly arises in microservice architectures, webhook consumers, AI/LLM streaming API clients, or federated applications that allow users to configure custom remote URLs or subscribe to external event streams.
In the first exploitation method, the attacker forces the client to accumulate millions of Buffer wrapper objects inside this.chunks without ever triggering line parsing:
EventSource request to the attacker's HTTP server.200 OK and header Content-Type: text/event-stream, disabling Nagle's algorithm (TCP_NODELAY) or using HTTP chunked transfer encoding.0x41) without sending 0x0A (\n) or 0x0D (\r).EventSourceStream appends each Buffer instance to this.chunks. The V8 heap is exhausted by object metadata long before this.currentLineSize reaches the default maximum byte threshold.In the second exploitation method, the attacker leverages HTTP compression to rapidly flood the parser with completed data: lines while withholding the event dispatch delimiter:
Accept-Encoding: gzip, deflate).Content-Type: text/event-stream and Content-Encoding: gzip.data:\n lines (6 bytes per line uncompressed) without any blank lines (\n\n).maxResponseSize filter.data: lines, allocating V8 string references and concatenating them onto this.event.data until the process terminates with Fatal error: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory.> [!NOTE]
> While no standalone weaponized exploit scripts have been published on ExploitDB or PacketStorm, the regression tests committed alongside the official patch in the nodejs/undici repository directly demonstrate the mechanics of both gzip expansion and multi-chunk split-line exhaustion.
The security impact of CVE-2026-109924 is strictly confined to system availability. The vulnerability does not permit arbitrary code execution, memory disclosure, or data tampering, which is reflected in the Confidentiality (C:N) and Integrity (I:N) metrics of its CVSS v3.1 vector (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H, Base Score 5.9).
However, the Availability impact is High (A:H). In the Node.js single-threaded event loop architecture, an unhandled Out-of-Memory exception in the V8 JavaScript engine causes immediate and fatal termination of the entire OS process. Unlike standard JavaScript Error exceptions or rejected Promises that can be caught via try/catch blocks or error event listeners on the EventSource instance, V8 heap exhaustion cannot be intercepted or recovered from within application code.
If a multi-tenant backend service uses a single Node.js process to handle concurrent requests for many users and establishes an EventSource connection to an untrusted endpoint on behalf of one user, triggering this vulnerability terminates the server process for all active tenants. Repeated exploitation against services configured with automatic process restarts (such as systemd, PM2, or Kubernetes pod liveness probes) results in a sustained denial-of-service loop.
From a threat intelligence perspective, CVE-2026-109924 currently carries a low Exploit Prediction Scoring System (EPSS) score of 0.00321 (23.84th percentile) and is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog. The High Attack Complexity (AC:H) rating accounts for the prerequisite that the victim client must be induced to connect to a malicious SSE server.
The OpenJS Foundation and the undici maintainers have released patched versions across all supported major release branches (v6, v7, and v8) that enforce strict caps on buffered chunks and retained data lines.
Development and operations teams should upgrade undici to the fixed version corresponding to their active release branch:
>= 6.5.0 < 6.29.1): Upgrade to 6.29.1 or later.>= 7.0.0 < 7.30.1): Upgrade to 7.30.1 or later.>= 8.0.0 < 8.12.2): Upgrade to 8.12.2 or later.To update the dependency using npm and verify the resolved dependency tree, execute the following commands:
# Update undici to the latest patched release within semver constraints
npm update undici
# Verify that no vulnerable transitive versions remain installed
npm list undici> [!IMPORTANT]
> Because undici is bundled internally within the Node.js runtime to provide global EventSource and fetch APIs, applications relying on the built-in Node.js EventSource global (rather than importing undici from node_modules) should also track and apply corresponding Node.js runtime security releases that incorporate these updated undici versions.
According to the official vendor advisory (GHSA-4qv8-2p9j-2685), no application-level code workarounds exist for affected versions of undici when parsing streams from untrusted SSE servers. Because V8 heap exhaustion occurs inside the internal transform stream before events are emitted to userland callbacks, application code cannot inspect or abort the pending event accumulation directly.
Where immediate dependency patching is blocked, organizations can reduce risk through the following operational controls:
EventSource clients only connect to verified, trusted internal or partner endpoints.AbortSignal with a strict timeout to outbound connections or monitor process resident set size (process.memoryUsage().heapUsed) to proactively abort long-running streams before fatal heap limits are reached.Accept-Encoding: gzip, deflate on text/event-stream requests (mitigating decompression amplification) and enforce maximum connection duration limits.CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
undici Node.js / OpenJS Foundation | >= 6.5.0, < 6.29.1 | 6.29.1 |
undici Node.js / OpenJS Foundation | >= 7.0.0, < 7.30.1 | 7.30.1 |
undici Node.js / OpenJS Foundation | >= 8.0.0, < 8.12.2 | 8.12.2 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Allocation of Resources Without Limits or Throttling (CWE-770) |
| Affected Component | undici EventSourceStream (lib/web/eventsource/eventsource-stream.js) |
| Attack Vector | Network (Malicious Server-Sent Events HTTP Response Stream) |
| CVSS v3.1 Score | 5.9 Medium (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H) |
| EPSS Score | 0.32% (23.84th Percentile) |
| Primary Impact | Denial of Service via V8 JavaScript Heap Out-of-Memory Crash |
| Exploit Status | Proof-of-Concept in Repository Regression Tests |
| CISA KEV Status | Not Listed |
The software allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
CVE-2026-109869 is a high-severity denial-of-service vulnerability in undici, the default HTTP/1.1 client and Fetch implementation for Node.js. When a server responds with more than five Content-Encoding directives and prematurely closes the connection before transmitting the body, undici allocates an internal Readable stream before enforcing the content-encoding limit. Upon rejecting the fetch promise, the stream is orphaned without an error listener or abort signal, causing the premature socket closure to emit an unhandled stream error that terminates the Node.js process.
LiteLLM versions 1.87.0 through 1.104.0-rc.1 contain a cryptographic domain separation vulnerability (CWE-269, CWE-345, CWE-441) that allows an authenticated low-privilege user to escalate privileges to full proxy administrator. The application reuses a single symmetric cryptographic context and key derivation path for both at-rest secret encryption and ephemeral UI/CLI session authentication tokens without providing Associated Authenticated Data (AAD). By submitting crafted JSON session claims into user-accessible encrypted metadata fields, an attacker can use the API-key generation flow as an encryption oracle, extract the resulting ciphertext, and replay it as a bearer token to achieve administrative control and subsequent remote command execution via the Model Context Protocol (MCP) stdio interface.
An origin validation vulnerability in @azure/msal-node allows attackers to process unchecked authorization code response payloads or request query parameters in ConfidentialClientApplication, potentially directing sensitive client credentials to external endpoints.
A path traversal vulnerability in PyPA virtualenv prior to version 21.14.9 allows local attackers to cause arbitrary file and directory deletion when a virtual environment target directory containing untrusted distribution metadata is reused or reseeded.
In virtualenv versions prior to 21.14.9, creating isolated Python virtual environments using the standard library creator (`--creator venv`) allows configuration key injection into `pyvenv.cfg` if an attacker can control or influence the destination directory path containing Unicode line break characters.
A command injection vulnerability in PyPA virtualenv prior to version 21.14.9 allows local attackers to execute arbitrary commands when a user sources a generated shell activation script containing a crafted prompt value.