How Cache-Control Header Injection Happens in Node.js HTTP Libraries and How to Fix It
Introduction
In the node-app repository, a high-severity vulnerability (CVE-2026-13697) was discovered in the undici HTTP client library's cache interceptor. The vulnerability stems from inadequate parsing and validation of the Cache-Control HTTP header, allowing attackers to craft malformed directives that bypass cache validation logic. This could result in sensitive data being cached inappropriately or cache poisoning attacks that cause denial of service.
The vulnerable code path handles user-influenced HTTP responses, and the cache interceptor processes the Cache-Control header without sufficiently strict validation. When an attacker sends a response with a specially crafted Cache-Control header containing malformed directives, the interceptor's parsing logic fails to properly reject or sanitize these directives, leading to unexpected caching behavior.
This matters for developers because:
- Sensitive data exposure: Cached responses containing authentication tokens, personal information, or API keys could be served to unintended clients
- Cache poisoning: Attackers can manipulate cached responses to serve malicious content to subsequent requests
- Denial of service: Malformed directives could cause the cache to behave unexpectedly, leading to performance degradation or crashes
The Vulnerability Explained
What Happens During the Attack
The undici cache interceptor is responsible for respecting HTTP caching semantics as defined in RFC 7234. The Cache-Control header contains directives that control caching behavior—directives like max-age=3600, private, no-store, etc.
The problem: When the cache interceptor receives a response with a malformed Cache-Control header—for example, one with improperly formatted directives or unexpected syntax—it fails to properly validate or reject the malformed input. Instead of treating the header as invalid and refusing to cache, the interceptor may:
- Partially parse the header, ignoring malformed portions while caching based on the valid portions
- Misinterpret directives, treating
privateaspublicor vice versa due to parsing errors - Cache when it shouldn't, storing responses that should never be cached according to strict RFC compliance
Attack Scenario
Consider this real-world attack:
HTTP/1.1 200 OK
Cache-Control: max-age=3600, private=invalid-syntax, no-store
Content-Type: application/json
{
"user_id": 12345,
"api_token": "secret_token_xyz",
"email": "user@example.com"
}
A strict parser should reject this header entirely because private=invalid-syntax is malformed (the private directive takes no parameters). However, if undici's cache interceptor doesn't validate this properly, it might:
- Ignore the malformed private=invalid-syntax directive
- Cache the response based on max-age=3600
- Result: Sensitive user data with an API token gets cached and served to other users or requests
An attacker could also craft headers that exploit the parsing logic in reverse:
Cache-Control: max-age=3600, public, no-store=ignored
If the parser processes directives left-to-right without proper precedence handling, it might cache the response (seeing max-age and public) while ignoring the no-store directive.
The Real Impact
For applications using undici (which is the HTTP client for many Node.js frameworks and tools):
- API responses containing authentication tokens could be cached and leaked to other users
- Personal data from API responses could persist in the cache longer than intended
- Cache poisoning attacks could serve stale or malicious data to clients
- Performance degradation if the cache behaves unexpectedly due to malformed directives
The Fix
What Changed
The fix involves upgrading undici from 7.28.0 to 7.29.0 (or 8.9.0 for v8 users). The upgrade includes stricter validation of Cache-Control header directives in the cache interceptor.
Before the fix (package.json and package-lock.json with undici 7.28.0):
"undici": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
"integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
}
After the fix (undici 7.29.0):
"undici": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
"integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
}
Additionally, a dependency override was added to package.json to ensure all transitive dependencies use the patched version:
{
"homepage": "https://github.com/lovasoa/dezoomify",
"overrides": {
"undici": "7.29.0"
}
}
Why These Changes Matter
-
Version bump (7.28.0 → 7.29.0): The undici maintainers fixed the cache interceptor's validation logic to properly reject malformed
Cache-Controldirectives. The new version implements RFC 7234 compliance more strictly. -
Dependency override: By adding the override, we ensure that even if other dependencies in the project specify an older version of undici, npm will use 7.29.0. This prevents transitive dependency conflicts from reintroducing the vulnerability.
-
Integrity hash update: The
sha512hash changed because the package contents changed. This is expected and confirms we're using a different (patched) version.
How the Fix Prevents the Vulnerability
The patched version (7.29.0) includes:
- Stricter directive parsing: Malformed directives like private=invalid-syntax are now properly rejected
- Proper directive precedence: Directives are processed according to RFC 7234 specifications, preventing contradictory directives from causing unexpected behavior
- Validation of directive values: Parameters to directives are validated (e.g., max-age must be a numeric value)
- Fail-safe caching: If the Cache-Control header is invalid or malformed, the response is treated as non-cacheable by default
Now, when the cache interceptor encounters the malformed header from our attack scenario:
Cache-Control: max-age=3600, private=invalid-syntax, no-store
It will:
1. Parse max-age=3600 ✓ (valid)
2. Parse private ✓ (valid, no parameters expected)
3. Reject private=invalid-syntax ✗ (invalid syntax)
4. Reject the entire header as malformed and treat the response as non-cacheable
This ensures sensitive data is never cached inappropriately.
Prevention & Best Practices
1. Keep Dependencies Updated
- Regularly update HTTP client libraries and other security-critical dependencies
- Use tools like
npm auditandnpm updateto identify and patch vulnerabilities - Consider automated dependency management tools like Dependabot
2. Implement Strict Cache-Control Validation
Even when using updated libraries, implement application-level validation:
// Example: Validate Cache-Control headers in your middleware
app.use((req, res, next) => {
const originalSend = res.send;
res.send = function(data) {
const cacheControl = res.get('Cache-Control');
// Reject responses with invalid Cache-Control headers
if (cacheControl && !isValidCacheControl(cacheControl)) {
res.set('Cache-Control', 'no-store, no-cache, must-revalidate');
}
return originalSend.call(this, data);
};
next();
});
function isValidCacheControl(header) {
// Implement RFC 7234 validation logic
const directives = header.split(',').map(d => d.trim());
return directives.every(d => /^[\w-]+=?[\w-]*$/.test(d));
}
3. Use Security Headers for Sensitive Data
For responses containing sensitive information, always set explicit cache directives:
// For authentication responses, API tokens, personal data
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.set('Pragma', 'no-cache');
res.set('Expires', '0');
4. Monitor Cache Behavior
- Log cache hits/misses to detect anomalies
- Monitor for unexpected cached responses
- Implement cache invalidation strategies for sensitive data
5. Use Security Scanning Tools
- Trivy: Scans for known CVEs in dependencies (as used in this fix)
- npm audit: Built-in vulnerability scanner for npm packages
- Snyk: Continuous vulnerability monitoring
- OWASP Dependency-Check: Identifies known vulnerable components
6. Implement Defense in Depth
- Don't rely solely on caching directives for security
- Use authentication tokens with short expiration times
- Implement server-side session management
- Use HTTPS to prevent man-in-the-middle cache poisoning attacks
Key Takeaways
- Cache-Control header injection is subtle: Malformed directives can bypass validation logic in HTTP clients, leading to unexpected caching behavior
- Undici versions before 7.29.0 are vulnerable: If you're using undici 7.28.0 or earlier (or 8.x versions before 8.9.0), you must upgrade immediately
- Dependency overrides are crucial: Using
"overrides"inpackage.jsonensures all transitive dependencies use the patched version, preventing version conflicts - Sensitive data requires explicit cache headers: Never rely on default caching behavior for responses containing authentication tokens or personal information
- Static analysis caught this early: Security scanners like Trivy detected this vulnerability by matching package versions against the CVE database, preventing exploitation in production
How Orbis AppSec Detected This
Source: HTTP response headers from external APIs and services (specifically the Cache-Control header field)
Sink: The cache interceptor in undici's HTTP client library that processes the Cache-Control directive without strict RFC 7234 validation
Missing control: Insufficient validation of Cache-Control header syntax; malformed directives were not properly rejected, allowing them to influence caching decisions
CWE:
- CWE-345: Insufficient Verification of Data Authenticity
- CWE-444: Inconsistent Interpretation of HTTP Requests ('HTTP Request Smuggling')
Fix: Upgrade undici to version 7.29.0 or 8.9.0, which implements stricter Cache-Control header parsing and validation according to RFC 7234 specifications, and add a dependency override to ensure all transitive dependencies use the patched version.
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 how subtle parsing vulnerabilities in HTTP libraries can lead to serious security issues. By mishandling malformed Cache-Control directives, undici's cache interceptor created an opportunity for information disclosure and cache poisoning attacks. The fix—upgrading to undici 7.29.0 or 8.9.0—implements stricter validation that prevents these attacks.
The key lesson for developers: never assume that third-party libraries handle untrusted input safely. Always keep dependencies updated, monitor security advisories, and implement application-level validation for critical security decisions like caching. By combining dependency management with defense-in-depth strategies, you can significantly reduce your attack surface.
If you're using undici in your Node.js applications, upgrade now. If you're using other HTTP clients, check their security advisories and ensure you're running the latest patched versions.
References
- CWE-345: Insufficient Verification of Data Authenticity
- CWE-444: Inconsistent Interpretation of HTTP Requests
- RFC 7234: Hypertext Transfer Protocol (HTTP/1.1): Caching
- OWASP: HTTP Response Splitting
- OWASP: Cache Poisoning
- Undici GitHub Repository
- NPM Audit Documentation
- GitHub PR: fix: upgrade undici to 7.29.0, 8.9.0 (CVE-2026-13697)