Back to Blog
high SEVERITY6 min read

How Cache-Control Header Mishandling Happens in Node.js HTTP Clients and How to Fix It

CVE-2026-13697 is a high-severity vulnerability in undici, the popular Node.js HTTP client, where the cache interceptor fails to properly validate malformed `Cache-Control: private` directives. This could allow sensitive cached responses to be served to unauthorized users. The fix upgrades undici from 7.28.0 to 7.29.0 (and 6.27.0 to 6.28.0) across the dependency tree, including using npm overrides to patch transitive dependencies.

O
By Orbis AppSec
Published August 5, 2026Reviewed August 5, 2026

Answer Summary

CVE-2026-13697 is a high-severity cache poisoning vulnerability in undici (Node.js HTTP client) where the cache interceptor mishandles malformed Cache-Control `private` directives, potentially serving sensitive cached responses to unauthorized parties. The fix is to upgrade undici to version 7.29.0 (or 6.28.0 for the v6 line) where the Cache-Control parsing logic correctly rejects malformed directives. This relates to CWE-525 (Use of Web Browser Cache Containing Sensitive Information).

Vulnerability at a Glance

cweCWE-525
fixUpgrade undici to 7.29.0/6.28.0 where Cache-Control parsing is corrected
riskSensitive cached responses served to unauthorized users due to malformed Cache-Control parsing
languageJavaScript (Node.js)
root causeundici's cache interceptor fails to correctly parse malformed `Cache-Control: private` directives
vulnerabilityCache-Control Header Parsing Bypass

Introduction

In the nix/native-modules dependency tree, Trivy flagged a high-severity vulnerability (CVE-2026-13697) in the undici HTTP client library. The issue sits in undici's cache interceptor — the component responsible for deciding whether an HTTP response should be stored and later served from cache based on Cache-Control headers.

The vulnerable versions (undici 7.28.0 and 6.27.0) were declared in nix/native-modules/package-lock.json, pulled in both as a direct optional dependency and as a transitive dependency through @electron/get via node-gyp. The flaw could allow an attacker who controls HTTP response headers to bypass the Cache-Control: private directive by sending a malformed variant, causing the cache interceptor to store and serve sensitive responses to other users.

The Vulnerability Explained

What Goes Wrong

Undici's cache interceptor parses Cache-Control response headers to determine caching behavior. When a server sends Cache-Control: private, the interceptor should never store that response in a shared cache — it's meant only for the specific user who requested it.

However, CVE-2026-13697 reveals that malformed Cache-Control: private directives (such as those with unusual whitespace, trailing characters, or non-standard formatting) were not being correctly identified by undici's parsing logic. Instead of rejecting or properly interpreting the malformed directive, the cache interceptor would fall through to its default behavior and cache the response anyway.

The Vulnerable Dependency Declaration

In the lock file, the vulnerable versions were pinned:

"node_modules/node-gyp/node_modules/undici": {
  "version": "6.27.0",
  "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
  "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg=="
}
"node_modules/undici": {
  "version": "7.28.0",
  "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
  "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="
}

Attack Scenario

Consider this scenario specific to this application:

  1. The application uses @electron/get (which depends on undici) to download Electron binaries or assets over HTTP.
  2. An attacker performing a man-in-the-middle attack (or controlling a CDN/proxy) injects a response with a malformed Cache-Control header like Cache-Control: private="\x00" or Cache-Control: private, (with irregular formatting).
  3. Undici's cache interceptor fails to recognize this as a private directive and caches the response.
  4. Subsequent requests from other contexts or users receive the cached response, which may contain session-specific data, authentication tokens, or tampered content.

For an Electron build pipeline, this could mean serving a poisoned binary from cache to subsequent builds — a supply chain attack vector.

The Fix

The fix involves two coordinated changes across package.json and package-lock.json:

1. Upgrading the Direct Dependency (package-lock.json)

The top-level optional undici dependency was bumped:

Before:

"node_modules/undici": {
  "version": "7.28.0",
  "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
  "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="
}

After:

"node_modules/undici": {
  "version": "7.29.0",
  "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
  "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="
}

The nested node-gyp dependency was also bumped from 6.27.0 to 6.28.0.

2. Forcing Transitive Dependency Resolution (package.json)

Critically, the fix adds an npm overrides section to ensure the transitive dependency through @electron/get also uses the patched version:

Before:

{
  "dependencies": {
    "electron": "42.3.0",
    "node-abi": "^4.31.0",
    "node-pty": "1.1.0"
  }
}

After:

{
  "dependencies": {
    "electron": "42.3.0",
    "node-abi": "^4.31.0",
    "node-pty": "1.1.0"
  },
  "overrides": {
    "@electron/get": {
      "undici": "7.29.0"
    }
  }
}

This is essential because without the override, @electron/get would continue resolving to the vulnerable undici version regardless of the top-level upgrade. The overrides field in package.json forces npm to substitute the specified version for any matching transitive dependency under @electron/get.

Why Both Changes Are Necessary

  • package-lock.json: Updates the resolved versions and integrity hashes so npm ci installs the patched versions.
  • package.json overrides: Ensures future npm install runs don't regress the transitive dependency back to a vulnerable version.

Key Takeaways

  • Malformed Cache-Control: private directives in undici < 7.29.0 bypass cache privacy, potentially exposing sensitive responses to unauthorized recipients.
  • Transitive dependencies require explicit overrides — upgrading only the top-level undici wouldn't patch the @electron/get → undici path without the overrides field in package.json.
  • Build-time HTTP clients are attack surfaces too — even dependencies used only during npm install or Electron packaging can introduce cache poisoning if they fetch resources over HTTP.
  • Lock file integrity hashes changed from sha512-YmfV3Y... to sha512-IDxfle..., confirming the actual binary content of the package was updated, not just metadata.
  • The fix is scoped and safe — only the version pins changed; no application logic was modified, preserving behavior for all valid inputs.

How Orbis AppSec Detected This

  • Source: HTTP response headers received by undici's fetch/request pipeline during dependency resolution and asset downloads
  • Sink: undici's internal cache interceptor parsing logic that evaluates Cache-Control directives to determine cacheability
  • Missing control: Strict validation of malformed Cache-Control: private directive variants before making cache storage decisions
  • CWE: CWE-525 (Use of Web Browser Cache Containing Sensitive Information)
  • Fix: Upgraded undici to 7.29.0 (and 6.28.0 for the v6 line) where the cache interceptor correctly identifies and rejects malformed private directives, and added npm overrides to patch the transitive dependency through @electron/get.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

CVE-2026-13697 demonstrates that even well-maintained libraries like undici can have subtle parsing flaws with significant security implications. A malformed Cache-Control: private header shouldn't be a vector for cache poisoning, but insufficient input validation in the cache interceptor made it one.

The fix is straightforward — upgrade to patched versions — but the execution requires attention to the full dependency tree. The use of npm overrides to patch transitive dependencies is a pattern every Node.js developer should know. Keep your dependencies current, audit your lock files, and don't assume that build-time dependencies are exempt from security scrutiny.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1231

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.