Skip to content

gh-51540: Let proxy auth handlers answer a 407 to CONNECT for https URLs - #161596

Merged
orsenthil merged 1 commit into
python:mainfrom
orsenthil:issues/51540
Nov 11, 2026
Merged

orsenthil merged 1 commit into
python:mainfrom
orsenthil:issues/51540

Conversation

@orsenthil

@orsenthil orsenthil commented Nov 8, 2026 •

Copy link
Copy Markdown
Member

Python's Proxy Handler when it sent an authentication request simply failed as discussed in gh-51540.

ProxyBasicAuthHandler and ProxyDigestAuthHandler could not authenticate with a proxy for an https URL. When the proxy answered the CONNECT request with 407 Proxy Authentication Required, http.client raised a plain OSError, do_open() wrapped it in URLError, and the http_error_407 handlers never ran. Only credentials embedded in the proxy URL worked.

With this change, a 407 sent by the origin through an established tunnel is still handled like one from the proxy; the proxy credentials are sent only on the CONNECT, never to the origin. This is documented on both http_error_407 methods and covered by a test.

Changes

  • http.client: raises a ProxyTunnelError, an OSError subclass when the proxy rejects CONNECT. It carries code, reason and headers; the message is unchanged, so except OSError keeps working. It holds no socket (the connection is closed before it is raised), which addresses the concern raised on the issue about exceptions that keep a socket open, and it can be pickled.

  • urllib.request.AbstractHTTPHandler.do_open(): a 407 to CONNECT is returned as an empty-bodied response, so the error handlers run and the request is retried with Proxy-Authorization on the CONNECT. Without credentials the caller now gets HTTPError 407 instead of URLError. Other CONNECT failures still raise URLError.

  • Request.set_proxy(): re-opening a tunnelled request (as any retry does) switched it to type='http' with the absolute URL as selector, so the retried request inside the tunnel was sent as GET https://host/path. It now keeps the tunnel.

  • AbstractDigestAuthHandler.get_authorization(): when tunnelling, the digest is computed over the CONNECT request (CONNECT host:port, RFC 7616 section 3.4) rather than the request for the origin, so strict proxies accept it.

🤖 Generated with Claude Code

Closes #161596
Closes #51540

@read-the-docs-community

read-the-docs-community Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #35821756 | 📁 Comparing 92755b1 against main (1955eb3)

  🔍 Preview build  

53 files changed · ± 53 modified

± Modified

…ttps URLs

urllib.request could not authenticate with a proxy for an https URL
using ProxyBasicAuthHandler or ProxyDigestAuthHandler: http.client
raised a plain OSError when the proxy answered the CONNECT request
with 407, so the http_error_407 handlers never ran.

* http.client: add ProxyTunnelError, an OSError subclass carrying the
  code, reason and headers of the proxy's response.  It holds no
  socket and can be pickled.
* urllib.request: do_open() passes a 407 to CONNECT on to the error
  handlers, so the request is retried with Proxy-Authorization on the
  CONNECT.  Other CONNECT failures still raise URLError.
* Request.set_proxy() keeps the tunnel when a request is re-opened, so
  the retried request still sends the path, not the absolute URL.
* ProxyDigestAuthHandler computes the digest over the CONNECT request
  (CONNECT host:port) when tunnelling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@orsenthil
orsenthil enabled auto-merge (squash) November 11, 2026 22:03
@orsenthil
orsenthil merged commit 9f03afd into python:main Nov 11, 2026
102 of 104 checks passed
orsenthil added a commit to orsenthil/cpython that referenced this pull request Nov 12, 2026
…ttps URLs (python#161596)

urllib.request could not authenticate with a proxy for an https URL
using ProxyBasicAuthHandler or ProxyDigestAuthHandler: http.client
raised a plain OSError when the proxy answered the CONNECT request
with 407, so the http_error_407 handlers never ran.

* http.client: add ProxyTunnelError, an OSError subclass carrying the
  code, reason and headers of the proxy's response.  It holds no
  socket and can be pickled.
* urllib.request: do_open() passes a 407 to CONNECT on to the error
  handlers, so the request is retried with Proxy-Authorization on the
  CONNECT.  Other CONNECT failures still raise URLError.
* Request.set_proxy() keeps the tunnel when a request is re-opened, so
  the retried request still sends the path, not the absolute URL.
* ProxyDigestAuthHandler computes the digest over the CONNECT request
  (CONNECT host:port) when tunnelling.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

urllib2 cannot handle https with proxy requiring auth

1 participant