From d112833142e31c1d1ca5db12af0e5ed5a9370d8e Mon Sep 17 00:00:00 2001 From: wzr-bot Date: Mon, 23 Mar 2026 03:14:30 +0800 Subject: [PATCH] Fix SSL certificate verification failure for hostnames with trailing dots Python's ssl module does not handle trailing dots in server_hostname, causing CERTIFICATE_VERIFY_FAILED errors for fully qualified domain names (FQDNs) like 'example.com.'. This fix strips the trailing dot from server_hostname at the connection level before passing it to SSL backends, following the same approach used by urllib3. The underlying DNS resolution still uses the original hostname with the trailing dot. Fixes #1063 --- httpcore/_async/connection.py | 5 +++-- httpcore/_async/http_proxy.py | 2 +- httpcore/_async/socks_proxy.py | 5 +++-- httpcore/_sync/connection.py | 5 +++-- httpcore/_sync/http_proxy.py | 2 +- httpcore/_sync/socks_proxy.py | 5 +++-- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/httpcore/_async/connection.py b/httpcore/_async/connection.py index b42581df..878d7852 100644 --- a/httpcore/_async/connection.py +++ b/httpcore/_async/connection.py @@ -148,8 +148,9 @@ async def _connect(self, request: Request) -> AsyncNetworkStream: kwargs = { "ssl_context": ssl_context, - "server_hostname": sni_hostname - or self._origin.host.decode("ascii"), + "server_hostname": ( + sni_hostname or self._origin.host.decode("ascii") + ).rstrip("."), "timeout": timeout, } async with Trace("start_tls", logger, request, kwargs) as trace: diff --git a/httpcore/_async/http_proxy.py b/httpcore/_async/http_proxy.py index cc9d9206..6e2c2e9e 100644 --- a/httpcore/_async/http_proxy.py +++ b/httpcore/_async/http_proxy.py @@ -309,7 +309,7 @@ async def handle_async_request(self, request: Request) -> Response: kwargs = { "ssl_context": ssl_context, - "server_hostname": self._remote_origin.host.decode("ascii"), + "server_hostname": self._remote_origin.host.decode("ascii").rstrip("."), "timeout": timeout, } async with Trace("start_tls", logger, request, kwargs) as trace: diff --git a/httpcore/_async/socks_proxy.py b/httpcore/_async/socks_proxy.py index b363f55a..489dae1f 100644 --- a/httpcore/_async/socks_proxy.py +++ b/httpcore/_async/socks_proxy.py @@ -258,8 +258,9 @@ async def handle_async_request(self, request: Request) -> Response: kwargs = { "ssl_context": ssl_context, - "server_hostname": sni_hostname - or self._remote_origin.host.decode("ascii"), + "server_hostname": ( + sni_hostname or self._remote_origin.host.decode("ascii") + ).rstrip("."), "timeout": timeout, } async with Trace("start_tls", logger, request, kwargs) as trace: diff --git a/httpcore/_sync/connection.py b/httpcore/_sync/connection.py index 363f8be8..a5ac7a83 100644 --- a/httpcore/_sync/connection.py +++ b/httpcore/_sync/connection.py @@ -148,8 +148,9 @@ def _connect(self, request: Request) -> NetworkStream: kwargs = { "ssl_context": ssl_context, - "server_hostname": sni_hostname - or self._origin.host.decode("ascii"), + "server_hostname": ( + sni_hostname or self._origin.host.decode("ascii") + ).rstrip("."), "timeout": timeout, } with Trace("start_tls", logger, request, kwargs) as trace: diff --git a/httpcore/_sync/http_proxy.py b/httpcore/_sync/http_proxy.py index ecca88f7..924f4c58 100644 --- a/httpcore/_sync/http_proxy.py +++ b/httpcore/_sync/http_proxy.py @@ -309,7 +309,7 @@ def handle_request(self, request: Request) -> Response: kwargs = { "ssl_context": ssl_context, - "server_hostname": self._remote_origin.host.decode("ascii"), + "server_hostname": self._remote_origin.host.decode("ascii").rstrip("."), "timeout": timeout, } with Trace("start_tls", logger, request, kwargs) as trace: diff --git a/httpcore/_sync/socks_proxy.py b/httpcore/_sync/socks_proxy.py index 0ca96ddf..cfd52a0f 100644 --- a/httpcore/_sync/socks_proxy.py +++ b/httpcore/_sync/socks_proxy.py @@ -258,8 +258,9 @@ def handle_request(self, request: Request) -> Response: kwargs = { "ssl_context": ssl_context, - "server_hostname": sni_hostname - or self._remote_origin.host.decode("ascii"), + "server_hostname": ( + sni_hostname or self._remote_origin.host.decode("ascii") + ).rstrip("."), "timeout": timeout, } with Trace("start_tls", logger, request, kwargs) as trace: