Your custom lock was installed but the deadbolt was quietly left out during assembly
CVE-2026-84961 affects undici's BalancedPool class in versions 7.24.1 through 7.29.0 and 8.0.0 through 8.10.1. The constructor passes its options object through an internal deep-clone that uses JSON serialization. Because JSON.stringify cannot represent functions, any caller-supplied connect or tls option containing a checkServerIdentity callback, a custom ca certificate chain expressed via a function reference, or similar non-serializable TLS settings is silently dropped before reaching the TLS layer. The connection then falls back to Node.js's default Mozilla CA trust store, accepting certificates that the application explicitly intended to reject.
The vendor rated this HIGH (7.4) with AC:H, which is honest about the MITM prerequisite but overstates the real-world blast radius. The bug only fires when three conditions align simultaneously: the app uses BalancedPool specifically (not Pool, Client, or Agent), it passes function-valued TLS options, *and* an attacker holds a man-in-the-middle position. The intersection of those three conditions in production is narrow. Most undici consumers use fetch(), request(), or plain Pool — none of which route through the broken clone path. The vendor severity is fair for the theoretical ceiling but oversells the practical risk for typical Node.js deployments.
4 steps from start to impact.
Target uses BalancedPool with custom TLS validation
BalancedPool and pass a connect or tls option containing function-valued properties such as checkServerIdentity or a dynamic ca loader. This is the only class affected — Pool, Client, Agent, and the global fetch() API are not vulnerable. This pattern is most common in microservices doing certificate pinning or connecting to endpoints secured by an internal CA.- Target application uses undici BalancedPool
- Application passes function-valued TLS options (checkServerIdentity, ca callback)
- Affected undici version (7.24.1–7.29.0 or 8.0.0–8.10.1)
- BalancedPool is a specialized class; most undici usage goes through fetch(), request(), or Pool
- Custom TLS validation callbacks are a niche pattern, primarily used in zero-trust or cert-pinning architectures
- Default undici usage inherits Node.js's standard TLS behavior, which is unaffected
Attacker achieves man-in-the-middle position
- Network-level access between application and upstream origin
- Ability to intercept and modify TCP/TLS handshake
- MITM on production server-to-server traffic is substantially harder than client-to-server
- Cloud provider backbone and VPC isolation reduce the viable attack surface
- Network detection tools (IDS/IPS, anomaly detection) may flag ARP spoofing or DNS hijack attempts
Present valid-looking certificate from public CA
checkServerIdentity callback silently dropped, the TLS stack falls back to Node.js's default trust store (Mozilla CA bundle). The attacker presents a certificate for the target hostname signed by any publicly trusted CA. Since the application intended to pin to a specific CA or validate additional properties, this certificate would normally be rejected — but the dropped callback means it is accepted.- Attacker obtains a certificate for the target hostname from any public CA
- Domain validation or other CA issuance succeeds
- Obtaining a legitimate certificate for an internal hostname is non-trivial
- Many internal services use non-routable hostnames that public CAs will not issue certificates for
- Certificate Transparency logs would record the issuance, enabling detection
Intercept or modify TLS-protected traffic
BalancedPool connection — other connections in the same application are unaffected.- Successful MITM with accepted certificate
- Target connection carries sensitive data
- Blast radius is limited to the BalancedPool connections, not all application traffic
- Application-layer authentication (mutual TLS client certs, signed requests) may still detect tampering
- Response integrity checks or HMAC-signed payloads would fail
The supporting signals.
| In-the-wild exploitation | Not observed. No reports from CISA, GreyNoise, or vendor advisories indicate active exploitation. Not listed on CISA KEV. |
|---|---|
| Proof of concept | No public PoC. The advisory describes the technical mechanism (JSON serialization drops functions) but no weaponized exploit or reproduction script has been published. Reported by MegaManSec. |
| EPSS score | Not yet scored — CVE disclosed 2026-09-04; EPSS typically populates within 7-14 days. Expected to be low given the MITM + niche usage prerequisites. |
| KEV status | Not listed on CISA Known Exploited Vulnerabilities catalog as of 2026-09-05. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N — Network-accessible but high attack complexity (MITM required). No privileges or user interaction needed. High confidentiality and integrity impact, no availability impact. Scope unchanged. |
| Affected versions | undici 7.24.1–7.29.0 and 8.0.0–8.10.1. Earlier major versions (5.x, 6.x) do not contain the vulnerable deep-clone path in BalancedPool. |
| Fixed versions | undici 7.29.1 and 8.10.2. Node.js core bundles undici — check process.versions.undici to see if your Node.js build ships a patched version. |
| Exposure footprint | undici has ~150M+ weekly npm downloads. However, BalancedPool usage with custom TLS callbacks represents a small fraction. No Shodan/Censys exposure data applies (library-level bug, not a listening service). |
| Disclosure date | 2026-09-04 via GHSA-w293-vg96-wgc3. |
| Reporter / credits | Reported by MegaManSec; patched by mcollina, reviewed by UlisesGascon. |
noisgate verdict.
The single most decisive downgrade factor is the narrow intersection of prerequisites: the application must use BalancedPool specifically (not the far more common Pool, Client, or fetch()), must pass function-valued TLS options, and the attacker must hold an active MITM position. Each prerequisite compounds to shrink the reachable population to a small fraction of undici's 150M+ weekly install base.
Why this verdict
- Narrow affected surface: Only
BalancedPoolwith function-valuedconnect/tlsoptions is affected. The dominant undici usage patterns (fetch(),request(),Pool,Client,Agent) are unaffected. This shrinks the vulnerable population to a small percentage of the 150M+ weekly install base. - MITM prerequisite (AC:H): Exploiting this requires an active man-in-the-middle position on production server-to-server traffic. In cloud-native deployments (the dominant undici environment), this means compromising VPC networking or provider backbone — a high bar that implies the attacker already has significant access.
- No exploitation evidence: No in-the-wild exploitation, no public PoC, no KEV listing. The vulnerability was disclosed responsibly with patches available same-day. The theoretical attack is well-understood but has not been demonstrated.
- Role multiplier: undici's BalancedPool *could* be used in high-value roles (API gateways, service mesh proxies, CI/CD pipeline HTTP clients). In these roles, a successful MITM could expose secrets or enable supply-chain injection. However, (a) BalancedPool with custom TLS pinning is an uncommon pattern even in these roles, and (b) the MITM prerequisite means the attacker is already inside the network. The worst plausible high-value outcome is per-connection data interception, not fleet compromise or domain takeover — blast radius is connection-scoped, not host- or domain-scoped. This does not trigger the HIGH floor.
- Silent failure mode is concerning: The callback is dropped *silently* with no error or warning logged, which means affected applications have no indication their TLS validation is ineffective. This is the primary reason the verdict remains MEDIUM rather than LOW.
Why not higher?
The vendor's HIGH rating assumes the attacker can achieve MITM and the target uses the exact affected code path. In practice, the intersection of BalancedPool usage, custom TLS callbacks, and attainable MITM position is narrow. The blast radius is limited to specific connections within one application — there is no lateral movement, privilege escalation, or fleet-scale impact from this vulnerability alone. No high-value role deployment pattern triggers the HIGH floor because the outcome is connection-scoped interception, not domain or fleet compromise.
Why not lower?
The silent failure mode is genuinely dangerous for the applications that *do* use this pattern. Certificate pinning and custom CA validation are security controls deployed specifically in high-sensitivity contexts (financial APIs, healthcare data exchanges, zero-trust microservices). Silently removing those controls without any error undermines the application's security assumptions in exactly the scenarios where TLS validation matters most. The absence of any runtime warning makes detection difficult without explicit version auditing.
What to do — in priority order.
- Switch from BalancedPool to Pool or Agent for TLS-pinned connections — The vulnerability only affects
BalancedPool. Replacing it withPool(single origin) orAgent(automatic connection management) preserves your custom TLS callbacks. This is the fastest mitigation — deploy within the noisgate MEDIUM remediation window of 365 days, though earlier is better if you have confirmed BalancedPool + custom TLS in your codebase. - Audit your codebase for BalancedPool + function-valued TLS options — Search for
new BalancedPoolin your Node.js projects and check whether the options object containscheckServerIdentity,caas a function, or other non-serializable TLS properties. If none are found, this CVE does not affect you. - Pin undici version in package-lock.json and upgrade to 7.29.1 or 8.10.2 — The patch is available. Run
npm audit fixor explicitly update undici. Verify withnpm ls undicithat no transitive dependency pulls in a vulnerable version. - Enable Certificate Transparency monitoring for your domains — If your application relies on certificate pinning, CT monitoring (crt.sh, Google CT) provides a detection layer for unexpected certificate issuance that would be needed for exploitation.
- WAF / API Gateway in front of the application — This vulnerability is in the application's *outbound* connections to upstream origins, not in inbound request handling. A WAF inspects inbound traffic and cannot see or control the application's outbound TLS handshakes.
- Network-level TLS inspection (SSL proxy) — A TLS-inspecting proxy between the app and its upstreams would replace the TLS session entirely, but it would also break the custom certificate validation the app was trying to do in the first place. It masks the bug without fixing it.
- Upgrading Node.js alone — While Node.js bundles undici, many applications install undici as a direct dependency. Upgrading Node.js may not update the application's pinned undici version. Check
npm ls undicito confirm which version your application actually loads.
Crowdsourced verification payload.
Run this on any host where your Node.js application is deployed or in your CI pipeline. Execute with bash check_cve_2026_84961.sh /path/to/your/project. No special privileges required — it reads node_modules and package-lock.json.
#!/usr/bin/env bash
# check_cve_2026_84961.sh — Detect undici versions vulnerable to CVE-2026-84961
# Usage: bash check_cve_2026_84961.sh [/path/to/project]
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
PROJECT_DIR="${1:-.}"
VULNERABLE=0
if [ ! -d "$PROJECT_DIR/node_modules" ]; then
echo "UNKNOWN — no node_modules found in $PROJECT_DIR"
exit 2
fi
# Find all installed undici versions
find "$PROJECT_DIR/node_modules" -path '*/undici/package.json' -not -path '*/node_modules/.cache/*' | while read -r pkg; do
version=$(grep -o '"version": *"[^"]*"' "$pkg" | head -1 | grep -o '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*')
if [ -z "$version" ]; then
continue
fi
major=$(echo "$version" | cut -d. -f1)
minor=$(echo "$version" | cut -d. -f2)
patch=$(echo "$version" | cut -d. -f3)
vuln="no"
# Check 7.24.1 through 7.29.0
if [ "$major" -eq 7 ]; then
if [ "$minor" -gt 24 ] && [ "$minor" -lt 29 ]; then
vuln="yes"
elif [ "$minor" -eq 24 ] && [ "$patch" -ge 1 ]; then
vuln="yes"
elif [ "$minor" -eq 29 ] && [ "$patch" -eq 0 ]; then
vuln="yes"
fi
fi
# Check 8.0.0 through 8.10.1
if [ "$major" -eq 8 ]; then
if [ "$minor" -lt 10 ]; then
vuln="yes"
elif [ "$minor" -eq 10 ] && [ "$patch" -le 1 ]; then
vuln="yes"
fi
fi
if [ "$vuln" = "yes" ]; then
echo "VULNERABLE — undici $version found at $pkg"
exit 1
else
echo "PATCHED — undici $version found at $pkg"
fi
done
# If the loop didn't exit, check if undici was found at all
if ! find "$PROJECT_DIR/node_modules" -path '*/undici/package.json' -not -path '*/node_modules/.cache/*' | grep -q .; then
echo "UNKNOWN — undici not found in $PROJECT_DIR"
exit 2
fi
exit 0If you remember one thing.
BalancedPool with function-valued TLS options on versions 7.24.1–7.29.0 or 8.0.0–8.10.1. Monday morning: run npm ls undici across your Node.js fleet and grep -r 'BalancedPool' src/ in your codebases. If you find the affected pattern, upgrade to undici 7.29.1 or 8.10.2. At a noisgate-reassessed MEDIUM, there is no mitigation SLA — go straight to the 365-day noisgate remediation SLA for the patch. However, if your specific application uses BalancedPool with certificate pinning for a security-critical integration (payment processors, identity providers), treat it as locally elevated and patch within 30 days. For the majority of Node.js deployments that use fetch(), Pool, or Agent, this CVE requires no action beyond confirming you are not affected.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.