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.

Prevention & Best Practices

1. Audit Transitive Dependencies Regularly

Direct dependencies are only part of the story. Use npm audit, Trivy, or similar tools to scan your entire dependency tree:

npm audit
trivy fs --scanners vuln .

2. Use npm Overrides for Stubborn Transitive Dependencies

When a direct dependency hasn't updated its own dependency, overrides in package.json lets you force a safe version:

"overrides": {
  "vulnerable-package": ">=patched-version"
}

3. Pin Lock Files and Review Changes

Always commit package-lock.json and review dependency version changes in PRs. Automated tools like Dependabot or Orbis AppSec can flag these proactively.

4. Understand Cache-Control Semantics

If your application implements any caching layer, ensure your parser handles:
- Malformed directives (extra whitespace, null bytes, trailing commas)
- Case variations (Private vs private)
- Quoted-string values with unusual content

5. Defense in Depth

Don't rely solely on Cache-Control for security. Implement additional safeguards:
- Use Vary headers appropriately
- Set no-store for truly sensitive responses
- Validate cached responses before serving

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.

References

Frequently Asked Questions

What is a Cache-Control parsing bypass vulnerability?

It occurs when an HTTP client or proxy fails to correctly interpret Cache-Control directives, potentially caching responses that should remain private or serving stale/sensitive data to unauthorized recipients.

How do you prevent cache poisoning in Node.js?

Use up-to-date HTTP client libraries, validate Cache-Control headers strictly, implement defense-in-depth with response validation, and regularly audit transitive dependencies for known vulnerabilities.

What CWE is cache mishandling?

CWE-525 (Use of Web Browser Cache Containing Sensitive Information) and CWE-444 (Inconsistent Interpretation of HTTP Requests) are both relevant to cache-related vulnerabilities.

Is upgrading the direct dependency enough to prevent this vulnerability?

Not always — transitive dependencies may pin older vulnerable versions. Using npm overrides (as done in this fix) ensures all instances in the dependency tree are patched.

Can static analysis detect cache handling vulnerabilities?

Yes, tools like Trivy can detect known vulnerable package versions in lock files, and SAST tools can flag improper cache header handling patterns in application code.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1231

Related Articles

high

How Octal IP Address Parsing Inconsistency Enables SSRF in Node.js and How to Fix It

A critical parsing inconsistency in the `ip-address` npm package (version 10.2.0) allowed attackers to bypass SSRF protections by exploiting how leading-zero octets are interpreted differently—decimal by the library versus octal by system resolvers. This vulnerability (CVE-2026-69192) was fixed by upgrading to version 10.3.1 using an npm override, ensuring consistent IP address validation across the application.

high

How Command Injection Happens in Node.js Child Process Calls and How to Fix It

A Node.js library was vulnerable to command injection through unsafe use of `execSync()` with shell string interpolation in the `index.js` file. By switching to `execFileSync()` with argument arrays, the fix eliminates the ability for attackers to inject shell metacharacters through file paths. This change demonstrates a critical security hardening pattern for any Node.js code that spawns child processes.

high

How NO_PROXY bypass via crafted URL happens in Node.js axios and how to fix it

A high-severity vulnerability (CVE-2026-42043) in the axios HTTP client library allowed attackers to bypass NO_PROXY environment variable restrictions using specially crafted URLs. This could route sensitive internal traffic through attacker-controlled proxy servers. The fix upgrades axios from 1.13.6 to 1.18.0, which includes a rewritten proxy resolution mechanism using `proxy-from-env` v2.1.0 and the `https-proxy-agent` package.

high

How Denial of Service via Infinite Loop happens in Node.js dependencies and how to fix it

A high-severity vulnerability in the nanoid package (CVE-2026-67213) allowed attackers to trigger infinite loops through the customAlphabet function, potentially causing complete denial of service. This fix upgrades nanoid from version 3.3.16 to 3.3.17 in the app_store dependency tree, eliminating the DoS risk through a simple version override.

high

How Denial of Service via Deeply Nested Field Names Happens in Node.js Multer and How to Fix It

A high-severity Denial of Service vulnerability (CVE-2026-5079) was discovered in the multer package, a popular Node.js middleware for handling multipart form data. Attackers could craft malicious requests with deeply nested field names to exhaust server resources. The fix upgrades multer from version 2.0.2 to 2.2.0, which implements proper limits on field name parsing depth.

critical

How SQL Injection happens in PHP PDO queries and how to fix it

A critical SQL injection vulnerability was discovered in the `getOfficialContests()` method of ContestRepository.php, where the `$site_id` parameter was directly interpolated into a SQL query string instead of using prepared statements. This vulnerability allowed attackers to inject arbitrary SQL commands and potentially access or manipulate the entire contest database. The fix replaced `pdo->query()` with `pdo->prepare()` and proper parameter binding.