A whitespace typo in someone else's kitchen lets your fridge serve the wrong leftovers, but only if you installed a shelf you never asked for
CVE-2026-14643 is a parsing flaw in undici's cache interceptor — a feature introduced in v7 that is not enabled by default. When the interceptor is explicitly activated in *shared* cache mode, it fails to strip optional whitespace around the = sign in qualified Cache-Control directives like private=" authorization" or no-cache="\tauthorization". Because the padded field name no longer matches the literal string authorization, undici incorrectly classifies the response as fully cacheable. A subsequent unauthenticated caller whose request maps to the same cache key can then receive the first user's authenticated response body. Affected version ranges are undici 7.0.0 – 7.27.x and 8.0.0 – 8.4.x; fixes ship in 7.28.0 and 8.5.0.
The vendor's MEDIUM / 5.9 score is *generous* relative to real-world exploitability. The CVSS vector's AC:H acknowledges difficulty, but even that undersells the friction: three independent opt-in conditions must align — the developer must enable interceptors.cache(), set type: 'shared', and the upstream server must already emit non-canonical whitespace-padded directives that the attacker cannot control. No proof-of-concept code is public, no in-the-wild exploitation has been observed, and the vulnerability is not KEV-listed. The real-world population of deployments meeting all three conditions is vanishingly small, making the practical risk significantly lower than a network-reachable 5.9 suggests.
5 steps from start to impact.
Vulnerable undici version in use
npm install undici explicitly. Earlier Node.js LTS releases (v20, v22) ship undici v6, which lacks the cache interceptor feature entirely and is not affected.- Application runs undici 7.0.0–7.27.x or 8.0.0–8.4.x
- Node.js v20 and v22 LTS ship undici v6 — not affected
- Cache interceptor is a v7+ feature; many production fleets are still on LTS
npm ls undici or check node -e "console.log(require('undici/package.json').version)" across your fleet.Cache interceptor explicitly enabled in shared mode
interceptors.cache({ type: 'shared' }) to their undici dispatcher configuration. The cache interceptor is off by default and type defaults to 'private' when it is turned on. Only 'shared' mode is vulnerable, because RFC 9111 shared caches are designed to serve multiple users.- Code explicitly calls
interceptors.cache()withtype: 'shared'
- Feature is opt-in and relatively new; adoption data suggests a tiny fraction of the >133M-weekly-download base uses it
- Default mode is 'private', which is not affected
- Most Node.js applications use external caching layers (Redis, CDN) rather than undici's built-in interceptor
interceptors.cache and inspect the type parameter.Authorization headers forwarded upstream
Authorization (or other sensitive) headers in outbound requests made through the cached dispatcher. This is typical of BFF (Backend-for-Frontend) or API gateway patterns but is not universal across all undici consumers.- Outbound requests include Authorization headers
- Many microservice architectures use service-to-service tokens (mTLS, JWT in a separate header) rather than forwarding end-user Authorization headers
Upstream sends non-canonical whitespace-padded Cache-Control
Cache-Control header with whitespace *inside* the quoted field-name of a qualified private or no-cache directive — e.g., private=" authorization". This is technically permitted by RFC 9110 optional whitespace (OWS) rules, but is extremely uncommon in practice. Crucially, the attacker cannot cause the upstream to emit this header; it is a server-side configuration property.- Upstream server emits whitespace-padded qualified Cache-Control directives
- Virtually no mainstream web server, CDN, or framework emits this pattern
- Attacker has zero control over upstream header formatting
- Requires a specific, non-standard server configuration that most teams would never intentionally create
Attacker requests the same cache key
Vary-relevant headers). The cached response — containing the victim's authenticated data — is served to the attacker.- Attacker can reach the same application endpoint
- Request matches the stored cache key
- If
Vary: Authorizationis set upstream, cache keys will differ per user and the attack fails - Application-level access controls (API keys, session validation) may gate the endpoint independently of the cache
The supporting signals.
| In-the-wild exploitation | None observed. Not listed in CISA KEV. No campaign or threat-actor reporting references this CVE. |
|---|---|
| Proof-of-concept | No public PoC. The advisory was reported responsibly by AndrewMohawk (Andrew MacPherson). The fix was developed by mcollina and reviewed by UlisesGascon. No exploit code has been published. |
| EPSS | Not yet scored (disclosed 2026-07-29). Expected to be very low given the multi-condition prerequisite chain and absence of weaponization. |
| KEV status | Not listed. No CISA KEV entry as of 2026-07-30. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N — Network-reachable, high attack complexity, no privileges required, high confidentiality impact, no integrity or availability impact. The AC:H reflects the multi-condition chain but still overestimates practical exploitability. |
| Affected versions | undici 7.0.0 – 7.27.x and 8.0.0 – 8.4.x. Node.js v20/v22 LTS ship undici v6 (not affected). Node.js v24 bundles undici v7 (potentially affected if cache interceptor is enabled in shared mode). |
| Fixed versions | undici 7.28.0 and undici 8.5.0. No distro backports needed — undici is an npm package, not a system library. |
| Scanning / exposure data | Not fingerprint-able via Shodan/Censys — undici is a client-side HTTP library, not a listening service. Exposure depends entirely on application-level code inspection. No internet-wide scan data is applicable. |
| Disclosure timeline | Reported by AndrewMohawk → patched by mcollina → advisory published 2026-07-29 via GHSA-pr7r-676h-xcf6. |
| Affected install base | undici has ~133M weekly npm downloads, but the cache interceptor in shared mode is an opt-in feature with negligible adoption relative to the total package footprint. Estimated vulnerable population: <0.1% of undici installs. |
noisgate verdict.
The single most decisive factor is the triple opt-in prerequisite chain: the developer must explicitly enable the cache interceptor, explicitly set shared mode, AND the upstream server must independently emit non-canonical whitespace-padded Cache-Control directives that the attacker cannot influence. This compounding friction reduces the practically-exploitable population to well under 0.1% of undici installations, placing the real-world risk firmly in the LOW bucket.
Why this verdict
- Triple opt-in chain compresses exploitable population to near zero. Three independent developer decisions — enabling the cache interceptor, selecting shared mode, and forwarding Authorization headers — must all be true simultaneously. Each is a minority configuration; their intersection is vanishingly small.
- Attacker cannot control the critical prerequisite. The whitespace-padded
Cache-Controldirective must originate from the upstream server. The attacker has no mechanism to inject or modify upstream response headers, making this a passive, opportunistic flaw rather than an actively weaponizable one. - No exploitation evidence, no PoC, no KEV listing. The CVE was disclosed <48 hours ago with no public exploit code. The attack complexity is genuinely high even by the vendor's own assessment (
AC:H), and real-world conditions make it harder still. - Role multiplier: undici is a general-purpose HTTP client library. The cache interceptor in shared mode is not a canonically high-value-role component (it is not an IdP, DC, hypervisor, backup system, kernel-mode agent, or network edge appliance). In theory, a Node.js-based API gateway using shared caching could leak authenticated data between users (tenant-level blast radius), but this represents <0.1% of the installed base. The high-value-role floor does not apply because the fraction of installs occupying that role is far below the 1% threshold.
- Impact ceiling is confidentiality-only. Even in a worst-case scenario, the attacker obtains cached response bodies — no code execution, no persistence, no integrity compromise, no availability impact. Blast radius is limited to responses sharing a single cache key.
Why not higher?
The vendor's MEDIUM (5.9) already accounts for high attack complexity, but it does not adequately penalize the three independent opt-in conditions, the attacker's inability to control the upstream header, and the near-zero real-world exposure. Upgrading to MEDIUM or higher would require either active exploitation evidence, a public PoC lowering the bar, or data showing meaningful adoption of shared-mode caching — none of which exist.
Why not lower?
An IGNORE verdict would be inappropriate because the *mechanism* is real: if the conditions align, authenticated data genuinely leaks between users with no privilege required. The fix is a simple version bump, and defenders should still track it in their dependency hygiene backlog rather than pretend it doesn't exist.
What to do — in priority order.
- Add
Vary: Authorizationon upstream origins — If the upstream response includesVary: Authorization, the cache key incorporates the Authorization header value and responses are never shared across users. This is the single most effective server-side mitigation and should be verified on all origins serving authenticated content. No mitigation SLA applies at LOW severity — treat as backlog hygiene. - Switch cache interceptor to private mode — Change
interceptors.cache({ type: 'shared' })tointerceptors.cache({ type: 'private' })or omit thetypeparameter entirely (default is private). Private caches are scoped to a single user context and are not affected by this vulnerability. - Disable cache interceptor for authenticated traffic — If shared caching is required for performance, configure the interceptor to bypass caching when the outbound request includes an
Authorizationheader. This eliminates the information-disclosure vector entirely. - Upgrade undici to ≥7.28.0 or ≥8.5.0 — The definitive fix. The patched versions correctly strip optional whitespace from qualified
privateandno-cachefield names before comparison. At LOW severity, apply within the 365-day noisgate remediation window during your next dependency update cycle.
- WAF rules — the malformed whitespace is in the *upstream origin's response header*, not in the inbound request. No WAF inspects or normalizes outbound response Cache-Control directives before they reach undici's parser.
- CDN-level caching controls — if a CDN sits in front of the Node.js application, it handles its own cache logic. The undici cache interceptor operates on the *outbound* leg (Node.js → upstream API), which CDN rules do not govern.
- Network segmentation / firewall rules — this is an application-layer logic bug in header parsing. Network controls have no visibility into Cache-Control directive formatting.
Crowdsourced verification payload.
Run this on each application host (or in CI) where your Node.js services are deployed. It checks undici's installed version and greps for shared-mode cache interceptor usage. No special privileges needed — any user who can read node_modules and the application source. Example: bash check_cve_2026_14643.sh /opt/myapp
#!/usr/bin/env bash
# check_cve_2026_14643.sh — Detect undici cache interceptor whitespace bypass
# Usage: bash check_cve_2026_14643.sh <app_root_directory>
# Exit codes: 0 = PATCHED/NOT_AFFECTED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
APP_DIR="${1:-.}"
if [ ! -d "$APP_DIR" ]; then
echo "UNKNOWN — directory $APP_DIR does not exist"
exit 2
fi
# --- Step 1: Find undici version ---
PKG_JSON="$APP_DIR/node_modules/undici/package.json"
if [ ! -f "$PKG_JSON" ]; then
echo "PATCHED — undici not installed (or not in node_modules); not affected"
exit 0
fi
VERSION=$(node -e "console.log(require('$PKG_JSON').version)" 2>/dev/null || echo "")
if [ -z "$VERSION" ]; then
echo "UNKNOWN — could not read undici version"
exit 2
fi
echo "[*] undici version detected: $VERSION"
# --- Step 2: Check version range ---
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
VULN_VERSION=false
if [ "$MAJOR" -eq 7 ] && [ "$MINOR" -lt 28 ]; then
VULN_VERSION=true
elif [ "$MAJOR" -eq 8 ] && [ "$MINOR" -lt 5 ]; then
VULN_VERSION=true
fi
if [ "$VULN_VERSION" = false ]; then
echo "PATCHED — undici $VERSION is not in the affected range (7.0.0-7.27.x / 8.0.0-8.4.x)"
exit 0
fi
echo "[!] undici $VERSION is in the affected range"
# --- Step 3: Check if shared-mode cache interceptor is used in source ---
echo "[*] Scanning source for interceptors.cache() in shared mode..."
if grep -rn --include='*.js' --include='*.ts' --include='*.mjs' --include='*.cjs' \
-E 'interceptors\.cache|cache.*shared|type.*shared' "$APP_DIR/src" "$APP_DIR/lib" "$APP_DIR/app" "$APP_DIR/server" 2>/dev/null | \
grep -qi 'shared'; then
echo "VULNERABLE — undici $VERSION with shared-mode cache interceptor detected in source"
exit 1
else
echo "PATCHED — undici $VERSION is in the affected range but no shared-mode cache interceptor usage found in source (check manually if source is elsewhere)"
exit 0
fiIf you remember one thing.
interceptors.cache({ type: 'shared' }) in production, verify immediately and either switch to type: 'private' or add Vary: Authorization upstream as a same-day fix. For everyone else — and that is the vast majority of Node.js shops — log this in your dependency-hygiene backlog and move on to higher-priority work.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.