Back to Blog
high SEVERITY5 min read

How Cache-Control Header Parsing Vulnerabilities Happen in Node.js HTTP Clients and How to Fix Them

A high-severity vulnerability (CVE-2026-13697) was discovered in undici, the popular Node.js HTTP client, where malformed Cache-Control directives could lead to information disclosure and denial of service. The cache interceptor failed to properly validate the `private` directive in Cache-Control headers, potentially exposing sensitive cached data. This fix upgrades undici to versions 7.29.0 and 8.9.0 to address the parsing flaw.

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

Answer Summary

CVE-2026-13697 is a high-severity information disclosure and denial of service vulnerability in undici, a Node.js HTTP client library. The flaw exists in undici's cache interceptor, which mishandles malformed Cache-Control `private` directives, potentially exposing cached responses to unauthorized parties. The fix involves upgrading undici to version 7.29.0 or 8.9.0, which properly validates Cache-Control header parsing to prevent cache poisoning attacks.

Vulnerability at a Glance

cweCWE-444 (Inconsistent Interpretation of HTTP Requests)
fixUpgrade undici to 7.29.0 or 8.9.0
riskSensitive cached data exposure and service disruption
languageJavaScript/Node.js
root causeMalformed Cache-Control private directive not properly validated
vulnerabilityCache-Control Header Parsing / Information Disclosure

Introduction

The package-lock.json file in this repository declared undici as a dependency—a high-performance HTTP/1.1 client that powers many Node.js applications' networking capabilities. However, a critical flaw lurked in undici's cache interceptor: when processing Cache-Control headers containing malformed private directives, the parser failed to properly reject or handle the invalid input.

This vulnerability, tracked as CVE-2026-13697, meant that an attacker could craft HTTP responses with specially malformed Cache-Control headers to either extract information from the cache that should have remained private, or trigger a denial of service condition. For applications using undici's caching features to improve performance, this created an unexpected attack surface where the very mechanism designed to optimize responses became a liability.

The Vulnerability Explained

What Went Wrong in Undici's Cache Interceptor

HTTP caching relies heavily on the Cache-Control header to determine how responses should be stored and served. The private directive specifically indicates that a response is intended for a single user and must not be stored by shared caches. When this directive is malformed—perhaps through extra characters, improper quoting, or unexpected formatting—a robust parser should either reject the header entirely or fail safely.

Undici's cache interceptor, prior to versions 7.29.0 and 8.9.0, did not properly handle these edge cases. The malformed private directive parsing could result in:

  1. Information Disclosure: Responses marked as private might be incorrectly cached and served to other users
  2. Denial of Service: Malformed headers could cause parsing errors that disrupt normal cache operations

Attack Scenario

Consider an application using undici to fetch user-specific data from an API:

import { request, cacheInterceptor } from 'undici';

const client = new Client('https://api.example.com', {
  interceptors: [cacheInterceptor()]
});

// Fetch user-specific dashboard data
const response = await client.request({
  path: '/api/user/dashboard',
  method: 'GET',
  headers: { 'Authorization': `Bearer ${userToken}` }
});

An attacker controlling a malicious upstream server (or performing a man-in-the-middle attack) could return a response with a malformed Cache-Control header:

HTTP/1.1 200 OK
Cache-Control: private="malformed\x00value", max-age=3600
Content-Type: application/json

{"user": "alice", "balance": "$10,000", "ssn": "123-45-6789"}

With the vulnerable undici version, this malformed private directive might be misinterpreted, causing the sensitive response to be cached and potentially served to other users making similar requests.

The Fix

The fix involved upgrading undici from version 7.28.0 to 7.29.0 (and ensuring compatibility with 8.9.0). Looking at the package-lock.json changes, we can see the dependency tree was restructured:

Before (Vulnerable)

{
  "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==",
  "devOptional": true,
  "license": "MIT",
  "peer": true,
  "engines": {
    "node": ">= 20.19.0"
  }
}

After (Fixed)

{
  "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==",
  "devOptional": true,
  "license": "MIT",
  "engines": {
    "node": ">= 20.19.0"
  }
}

The diff shows several changes to the dependency resolution:

  1. Removed peer: true flags from multiple packages including undici itself, ensuring the fixed version is directly installed rather than relying on peer dependency resolution
  2. Added peer: true flags to optional dependencies like fsevents and fs-extra to properly isolate them
  3. Updated the dependency tree to ensure the patched undici version is resolved consistently

These changes ensure that:
- The application uses the fixed undici version directly
- The cache interceptor now properly validates Cache-Control directives
- Malformed private values are rejected or handled safely

Prevention & Best Practices

1. Keep Dependencies Updated

Use automated dependency scanning tools to catch known CVEs:

# Using npm audit
npm audit

# Using Trivy for comprehensive scanning
trivy fs --scanners vuln .

2. Implement Defense-in-Depth for Caching

Don't rely solely on upstream Cache-Control headers. Add application-level controls:

// Add explicit cache controls for sensitive routes
app.get('/api/user/sensitive-data', (req, res) => {
  res.set({
    'Cache-Control': 'no-store, no-cache, must-revalidate, private',
    'Pragma': 'no-cache',
    'Expires': '0'
  });
  // ... handle request
});

3. Validate HTTP Headers at the Application Level

Consider adding header validation before processing:

function validateCacheControl(header) {
  if (!header) return true;

  // Reject headers with null bytes or other suspicious characters
  if (/[\x00-\x1f]/.test(header)) {
    console.warn('Suspicious Cache-Control header detected');
    return false;
  }
  return true;
}

4. Use Lock Files and Pin Versions

Ensure your package-lock.json is committed and use exact version pinning for critical security dependencies:

{
  "dependencies": {
    "undici": "7.29.0"
  }
}

Key Takeaways

  • Undici's cache interceptor required specific validation for malformed Cache-Control private directives—a parsing edge case that created both information disclosure and DoS risks
  • The peer: true flag changes in package-lock.json ensured direct dependency resolution rather than relying on potentially vulnerable peer versions
  • HTTP header parsing is security-critical—even well-established libraries can have subtle parsing flaws that create exploitable conditions
  • Dependency scanning tools like Trivy caught this CVE before it could be exploited in production, demonstrating the value of automated security scanning
  • Cache-related vulnerabilities can have severe privacy implications when user-specific data is incorrectly shared between requests

How Orbis AppSec Detected This

  • Source: HTTP response headers from upstream servers, specifically the Cache-Control header value
  • Sink: undici's cache interceptor parsing logic that processes and stores responses based on Cache-Control directives
  • Missing control: Proper validation and rejection of malformed private directive values in Cache-Control headers
  • CWE: CWE-444 (Inconsistent Interpretation of HTTP Requests)
  • Fix: Upgraded undici from 7.28.0 to 7.29.0/8.9.0, which includes proper validation of Cache-Control header parsing

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 serves as a reminder that even fundamental HTTP operations like cache header parsing require rigorous validation. The undici library is widely used across the Node.js ecosystem, making this vulnerability particularly impactful. By upgrading to the patched versions (7.29.0 or 8.9.0) and implementing defense-in-depth caching strategies, developers can protect their applications from both this specific vulnerability and similar header parsing issues in the future.

Always treat HTTP headers as untrusted input, even when they come from seemingly trusted sources. Malformed headers can slip through at any point in the request chain, and your application's parsing logic must be prepared to handle them safely.

References

Frequently Asked Questions

What is Cache-Control header parsing vulnerability?

A Cache-Control parsing vulnerability occurs when HTTP caching logic fails to properly interpret cache directives, potentially storing or serving responses incorrectly. In undici's case, malformed `private` directives weren't properly validated, allowing cached responses to be improperly shared.

How do you prevent Cache-Control parsing vulnerabilities in Node.js?

Use well-maintained HTTP client libraries with strict header parsing, keep dependencies updated, validate all cache-related headers before processing, and implement defense-in-depth with application-level cache controls.

What CWE is Cache-Control parsing vulnerability?

This type of vulnerability typically maps to CWE-444 (Inconsistent Interpretation of HTTP Requests) or CWE-525 (Use of Web Browser Cache Containing Sensitive Information), depending on the specific exploitation vector.

Is upgrading undici enough to prevent this vulnerability?

Yes, upgrading to undici 7.29.0 or 8.9.0 addresses CVE-2026-13697. However, you should also review your caching strategy and ensure sensitive responses include proper Cache-Control headers at the application level.

Can static analysis detect Cache-Control parsing vulnerabilities?

Yes, static analysis tools like Trivy can detect known CVEs in dependencies. However, detecting novel parsing logic flaws typically requires specialized security testing and code review of HTTP header handling routines.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #9

Related Articles

high

How Client-Side Denial of Service happens in Node.js FTP clients and how to fix it

CVE-2026-44240 is a client-side Denial of Service vulnerability in the `basic-ftp` Node.js package (versions prior to 5.3.1) caused by improper handling of unterminated multiline FTP server responses. An attacker controlling an FTP server—or capable of intercepting FTP traffic—could send a malformed response that causes the client to hang indefinitely. Upgrading `basic-ftp` to 5.3.1 and adding a package override in `package.json` closes the attack surface entirely.

high

How javascript.express.security.audit.express-check-csurf-middleware-usage.express-check-csurf-middleware-usage happens in Express.js and how to fix it

A publicly accessible Express.js API endpoint in `app/api/cameras.js` was missing CSRF protection, leaving state-changing requests (POST, PUT, DELETE, PATCH) vulnerable to cross-site request forgery attacks. The fix introduces Origin/Referer header validation middleware in `app/index.js` and removes a redundant Express instance from `cameras.js` that bypassed the application's middleware chain.

critical

How Command Injection via Unescaped Line Terminators Happens in Node.js and How to Fix It

A critical command injection vulnerability (CVE-2026-9277) was discovered in the shell-quote npm package version 1.8.3, where unescaped line terminators could allow attackers to execute arbitrary code. This fix upgrades shell-quote to version 1.9.0 using npm overrides to ensure all instances in the dependency tree are patched, eliminating the attack vector across the entire application.

critical

How Distributed Lock Takeover Happens in Node.js and How to Fix It

A critical vulnerability in `redis-lock/server.mjs` allowed any authenticated client to release another client's lock by guessing predictable holder identifiers like process IDs or hostnames. The fix implements cryptographically random `lockId` values that are minted on lock acquisition and validated on release, eliminating the exploit primitive entirely.

high

How Denial of Service via Infinite Loop happens in JavaScript (nanoid) and how to fix it

A high-severity denial of service vulnerability (CVE-2026-67213) was discovered in nanoid versions before 5.1.6 and 3.3.18, where the `customAlphabet` function could enter an infinite loop during random ID generation. The fix upgrades the transitive nanoid dependency from 3.3.16 to 3.3.18 using pnpm overrides, ensuring the vulnerable code path is eliminated from the entire dependency tree including PostCSS.

high

How Information Disclosure via Unstripped Credential Headers Happens in Electron Apps and How to Fix It

A high-severity vulnerability (CVE-2026-54673) in the builder-util-runtime package allowed sensitive credential headers to leak during HTTP redirects in Electron applications. The fix upgrades builder-util-runtime from version 9.5.1 to 9.7.0, which properly strips authentication headers before following redirects to prevent information disclosure.