CVE-2026-71290: Apache HttpComponents' Async Client Silently Skips Hostname Verification
Apache HttpComponents Client versions 5.4 through 5.6.3 ship an async transport that silently ignores hostname verification, even when it is explicitly configured. CVE-2026-71290, CVSS 9.1, means any Java service using HttpAsyncClients with default or BUILTIN hostname policy will accept a valid certificate for any domain as proof of identity for the domain it actually intended to connect to.
What changed
The GitHub Security Advisory is specific: HostnameVerificationPolicy#BUILTIN has no effect when used with the async version of HttpClient. The classic, blocking HttpClient (HttpClients.custom()...) is unaffected. The bug is isolated to the non-blocking transport built on top of Apache’s I/O reactor.
This is CWE-295, improper certificate validation, and it fails silently, with no exception or log line. The TLS handshake completes, the connection is treated as authenticated, and the application proceeds. The only way to notice is to deliberately test with a certificate that doesn’t match the hostname you’re connecting to and watch it get accepted anyway.
Fixed in 5.6.4. NVD lists the CVE as published August 11, 2026 and last modified August 17, 2026 as remediation guidance solidified.
Why it matters operationally
This hits any Java service that adopted the async client for performance reasons (non-blocking outbound calls to internal microservices, external APIs, webhook targets, or ACME/CA endpoints) and did so specifically because someone wanted to move off the blocking classic client. That migration path is common, so the affected population is anyone who cared enough about I/O throughput to switch transports.
The failure mode that should worry you most is server impersonation on internal traffic. If your Java services trust a private CA and route east-west traffic through the async client, an attacker who can intercept that traffic (a compromised sidecar, a misconfigured proxy, DNS poisoning inside a VPC) can present any certificate the CA has ever issued, for a completely unrelated service, and the async client will accept it as if it belonged to the intended host. mTLS setups that rely on hostname checking as part of peer identity are exposed the same way; client certificate authentication on the request itself doesn’t help if the server’s identity was never actually checked.
Check your dependency tree first:
mvn dependency:tree -Dincludes=org.apache.httpcomponents.client5:httpclient5# or./gradlew dependencies --configuration runtimeClasspath | grep httpclient5If you land in the 5.4-5.6.3 range and construct any client via HttpAsyncClients, you’re affected regardless of what you passed to setHostnameVerificationPolicy().
Reproducing the failure
Stand up a TLS listener with a certificate for a hostname that doesn’t match what you’ll connect to:
openssl req -x509 -newkey rsa:2048 -keyout wrong.key -out wrong.crt \ -days 1 -nodes -subj "/CN=wrong-host.example"openssl s_server -accept 8443 -cert wrong.crt -key wrong.key -wwwThen connect to localhost:8443 (not wrong-host.example) with both transports:
// Classic client: correctly throws SSLPeerUnverifiedExceptionCloseableHttpClient classic = HttpClients.custom() .setSSLHostnameVerifier(new DefaultHostnameVerifier()) .build();
// Async client on 5.4-5.6.3: completes the handshake, returns 200CloseableHttpAsyncClient async = HttpAsyncClients.custom() .setTlsStrategy(ClientTlsStrategyBuilder.create() .setHostnameVerificationPolicy(HostnameVerificationPolicy.BUILTIN) .build()) .build();The classic client rejects the connection because wrong-host.example doesn’t match localhost. The async client, on an affected version, completes the request. The configuration intent is identical in both cases, and nothing in the response tells you which behavior you got.
The fix is a version bump, org.apache.httpcomponents.client5:httpclient5 to 5.6.4 or later. No config change helps, since the vulnerable configuration was already the one the API told you to use.
Where this leaves KrakenKey
This doesn’t change anything in KrakenKey’s flow. We’re not a Java HTTP client, and issuance and renewal aren’t affected by how a downstream service happens to validate TLS on outbound requests. It is also a bug our own TLS scanner can’t catch: the scanner audits what a server presents (chain, expiry, hostname match from the outside), but it has no visibility into whether a client library deep in your service mesh is actually checking what it receives. That check only happens at the code and dependency level, so certificate automation and endpoint monitoring would not have surfaced it; it has to be patched in the dependency.