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:
- The application uses
@electron/get(which depends on undici) to download Electron binaries or assets over HTTP. - An attacker performing a man-in-the-middle attack (or controlling a CDN/proxy) injects a response with a malformed
Cache-Controlheader likeCache-Control: private="\x00"orCache-Control: private,(with irregular formatting). - Undici's cache interceptor fails to recognize this as a
privatedirective and caches the response. - 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 sonpm ciinstalls the patched versions.package.jsonoverrides: Ensures futurenpm installruns 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: privatedirectives 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
undiciwouldn't patch the@electron/get → undicipath without theoverridesfield inpackage.json. - Build-time HTTP clients are attack surfaces too — even dependencies used only during
npm installor Electron packaging can introduce cache poisoning if they fetch resources over HTTP. - Lock file integrity hashes changed from
sha512-YmfV3Y...tosha512-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-Controldirectives to determine cacheability - Missing control: Strict validation of malformed
Cache-Control: privatedirective 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
privatedirectives, 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.