The Problem: Silent TLS Option Dropout
undici's BalancedPool is designed to distribute HTTP requests across multiple upstream connections, offering a load-balancing layer for Node.js applications that need to proxy or forward traffic. A critical flaw in version 7.29.0 (and earlier 7.x versions, as well as 8.x versions before 8.10.2) causes the pool to silently discard TLS certificate validation settings during connection initialization.
When an application passes TLS configuration—such as certificate pinning, custom CA bundles, or validation strictness settings—to BalancedPool, the pool accepts the options but fails to propagate them to the underlying socket connections. This means that even if your code correctly specifies that certificates must be validated, the actual HTTPS handshake proceeds without validation. An attacker positioned on the network path (such as on a shared corporate network, ISP infrastructure, or cloud environment) can intercept traffic with a self-signed certificate, and the validation will silently succeed instead of raising an error.
How the Vulnerability Works
BalancedPool maintains a list of upstream addresses and creates connections to each. When routing a request, it selects one of these connections and passes connect options—including TLS settings like ca, cert, key, rejectUnauthorized, and custom agents—to the underlying connection handler.
The vulnerability occurs because the BalancedPool's connection setup routine discards these options before they reach the socket layer. The pool extracts the options, validates their presence in its internal state, but then fails to pass them through to the actual tls.connect() or tls.createSecureConnection() call used by the individual pooled connections.
In practice, this means:
1. Your application code creates a BalancedPool with rejectUnauthorized: true or a custom CA certificate.
2. A request is routed through the pool.
3. The pool opens a connection but drops the TLS options.
4. The connection defaults to Node.js's standard TLS behavior: rejectUnauthorized: false or system CA validation only.
5. A network attacker intercepts the connection with any certificate, and the handshake completes without error.
Affected Versions
| Affected | 7.29.0 and earlier 7.x versions; 8.10.1 and earlier 8.x versions |
| Fixed in | 7.29.1 (7.x branch), 8.10.2 (8.x branch) |
| Ecosystem | npm |
| CVE / GHSA | CVE-2026-84961 |
| CWE | unknown |
The Attack Scenario
Consider a Node.js microservice that uses undici to forward requests to a backend API over HTTPS:
import { BalancedPool } from 'undici';
const pool = new BalancedPool(
['https://api.internal.example.com:443', 'https://api-backup.internal.example.com:443'],
{
rejectUnauthorized: true,
ca: fs.readFileSync('internal-ca.pem')
}
);
// Later, the service forwards a user request through the pool.
The developer correctly configures the pool to only accept certificates signed by the internal CA and to reject unauthorized certificates. However, due to the vulnerability, when the pool initializes a connection to one of the backends, the rejectUnauthorized and ca options are silently dropped.
An attacker on the network—perhaps sharing the same cloud network, compromised router, or ISP infrastructure—can now:
1. Intercept traffic destined for api.internal.example.com.
2. Present a self-signed certificate (or a certificate not signed by the internal CA).
3. The connection will succeed without error, despite the developer's explicit validation requirements.
4. The attacker can now read and modify all traffic: credentials, tokens, sensitive data.
This is a textbook man-in-the-middle (MITM) vulnerability. The application believes it is communicating with a trusted backend; the developer set up validation correctly; but the underlying HTTP client silently ignored those settings.
The Fix: Preserving Connect Options
The fix in undici 7.29.1 and 8.10.2 ensures that all connect options, particularly TLS configuration, are preserved and passed through to the actual socket layer during BalancedPool connection initialization.
The change in the package metadata reflects a version bump:
- "undici": "7.29.0"
+ "undici": "7.29.1"
Under the hood, the BalancedPool connection handler now correctly threads connect options through each connection creation. Instead of dropping the options during pool-level routing logic, the fixed version:
- Accepts connect options (including TLS settings) at pool creation time.
- Stores those options in the pool's internal state.
- Passes them unchanged to each underlying connection when routing a request.
- The socket layer receives the full
ca,rejectUnauthorized, certificate pinning, and custom agent settings.
This ensures that the validation rules you set are actually enforced, not silently discarded.
How Orbis AppSec Detected This
Source: The BalancedPool constructor and its opts parameter, which accepts TLS configuration such as ca, rejectUnauthorized, and custom agents.
Sink: The internal connection initialization routine that creates sockets for each upstream address, where TLS options should be forwarded but were being dropped.
Missing control: No validation or pass-through of connect options from the pool's configuration to the underlying socket creation layer. The options were accepted but never used.
CWE: Unknown; related to improper handling of security-critical parameters (similar to CWE-327 Use of a Broken or Risky Cryptographic Algorithm, but more specifically a configuration dropout).
Fix: The pool now preserves and forwards all connect options—particularly TLS settings—to the underlying socket layer during connection setup, ensuring that certificate validation, CA bundles, and other TLS constraints are honored.
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.
Key Takeaways
-
BalancedPool silently dropped TLS options in versions 7.29.0 and earlier 8.x: Even if you correctly configured certificate validation, the pool discarded those settings during connection setup. Configuration alone is not a substitute for code correctness.
-
Network-level MITM attacks were possible without triggering validation errors: An attacker could intercept HTTPS traffic with any certificate because validation was never performed, despite the developer's explicit
rejectUnauthorized: trueand CA pinning. -
The fix is a version upgrade with no API changes: Simply updating undici to 7.29.1, 8.10.2, or later ensures that TLS options are correctly honored. No application code changes are required.
-
This vulnerability highlights the importance of testing TLS configuration in production-like environments: Unit tests that mock backends will not catch this issue; integration tests against real HTTPS endpoints would have detected the validation bypass.
Conclusion
CVE-2026-84961 is a stark reminder that HTTP client libraries must correctly thread security-critical options—especially TLS configuration—through every layer of connection handling. A pool abstraction that silently drops validation settings defeats the entire purpose of having those settings in the first place.
If your application uses undici's BalancedPool with any custom TLS configuration, upgrade immediately to 7.29.1 (if on the 7.x branch) or 8.10.2 (if on the 8.x branch). The fix is transparent and low-risk. Delaying the upgrade leaves your application open to network-level eavesdropping and manipulation.