Listen to this Post
CVE-2026-84961
undici’s BalancedPool constructor passes its entire options object through an internal deep-clone that serializes and reparses the value as JSON. Because JSON cannot represent functions, any function-valued TLS option, such as a caller-supplied checkServerIdentity callback or a custom connector inside the connect option, is silently discarded before it reaches the TLS layer. As a result, a peer whose certificate the application’s custom checkServerIdentity was written to reject, but which still passes Node’s default hostname and chain checks, is accepted when reached through BalancedPool. The Client, Pool, and Agent dispatchers are not affected because they extract the connect and tls options before cloning. This affects undici versions from 7.24.1 up to 7.29.1 and from 8.0.0 up to 8.10.2, and only when the application supplies a function-valued connect or tls option to BalancedPool. Users should upgrade to undici 7.29.1 or 8.10.2.
DailyCVE Form:
Platform: undici
Version: 7.24.1-7.29.1, 8.0.0-8.10.2
Vulnerability : TLS validation bypass
Severity: High (7.4)
date: 2026-09-04
Prediction: 2026-09-04
What Undercode Say:
Analytics
Bash commands and codes related to the blog
Check undici version in your project
npm list undici
Check if BalancedPool is used in your codebase
grep -r "BalancedPool" ./node_modules/undici/lib/
Inspect the vulnerable deep-clone logic
grep -n "JSON.parse(JSON.stringify" ./node_modules/undici/lib/dispatcher/balanced-pool.js
Demonstrate the function-dropping behaviour
node -e "
const opts = { connect: { checkServerIdentity: () => { throw new Error('rejected'); } } };
const cloned = JSON.parse(JSON.stringify(opts));
console.log(cloned);
// Output: { connect: {} } — the function is gone
"
// Vulnerable pattern: function-valued options are lost
const { BalancedPool } = require('undici');
const pool = new BalancedPool('https://example.com', {
connect: {
checkServerIdentity: (hostname, cert) => {
// Custom pinning logic — silently ignored
if (cert.fingerprint !== 'expected-fingerprint') {
throw new Error('Certificate pinning failed');
}
}
}
});
// The custom checkServerIdentity never reaches the TLS layer.
// A certificate that should be rejected is accepted by default Node checks.
// Non-vulnerable alternative: use Agent instead
const { Agent } = require('undici');
const agent = new Agent({
connect: {
checkServerIdentity: (hostname, cert) => {
if (cert.fingerprint !== 'expected-fingerprint') {
throw new Error('Certificate pinning failed');
}
}
}
});
// Agent destructures connect before any cloning — callback is preserved.
Exploit: (Educational Purposes!)
Educational demonstration: how a dropped callback leads to bypass
Step 1: Set up a TLS server with a certificate that passes default checks
but fails a custom checkServerIdentity callback.
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 \
-nodes -subj "/CN=example.com"
Step 2: Run a Node TLS server using that certificate
node -e "
const tls = require('tls');
const fs = require('fs');
const server = tls.createServer({
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
}, (socket) => {
socket.end('hello');
});
server.listen(8443);
"
Step 3: In a separate process, make a request through BalancedPool
with a custom checkServerIdentity that would reject the cert.
node -e "
const { BalancedPool } = require('undici');
const pool = new BalancedPool('https://localhost:8443', {
connect: {
checkServerIdentity: () => { throw new Error('should reject'); }
}
});
pool.request({ path: '/' }).then(res => {
console.log('Request succeeded — callback was dropped');
}).catch(err => {
console.log('Request failed — callback was preserved');
});
"
The request succeeds because the function was stripped by the JSON clone.
Protection: from this CVE
Immediate mitigation: upgrade undici to a fixed version npm install [email protected] or for v8 users npm install [email protected]
Verify the upgrade took effect npm list undici Expected output includes [email protected] or [email protected]
// Workaround: replace BalancedPool with Agent or Pool until upgrade
// Before (vulnerable):
const { BalancedPool } = require('undici');
const pool = new BalancedPool('https://example.com', {
connect: { checkServerIdentity: myCallback }
});
// After (safe workaround):
const { Agent } = require('undici');
const agent = new Agent({
connect: { checkServerIdentity: myCallback }
});
Impact:
Applications using undici’s BalancedPool with a function-valued connect or tls option — such as a custom checkServerIdentity callback or connector — are affected. The custom TLS verification logic is silently dropped, causing a TLS peer whose certificate the callback was written to reject, but which passes Node’s default hostname and chain checks, to be accepted. This bypasses certificate pinning and custom TLS validation, potentially allowing a man-in-the-middle attacker to present a certificate that should have been rejected. Client, Pool, Agent, and RoundRobinPool are not affected because they destructure connect/tls before the JSON clone. The vulnerability is rated High with a CVSS score of 7.4.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

