Safeguard
Vulnerability Analysis•

urllib3 2.8.0: HTTPS Proxy TLS Mix-Ups and Two Streaming DoS Bugs

urllib3 2.8.0 fixed three advisories on 15 Sept. The proxy TLS bug needs specific HTTPS proxy options. The streaming bugs sit closer to everyday requests code.

Safeguard Security Team
5 min read

On 15 September 2026 urllib3 released 2.8.0 and published three security advisories: two rated High and one Moderate. The GitHub Advisory Database added them on 30 September as CVE-2026-97687, CVE-2026-97689 and CVE-2026-97688.

urllib3 is one of the most downloaded packages on PyPI and the transport under requests, botocore and a long list of SDKs. Almost every Python service has it. What varies is whether that service touches the code paths these advisories describe.

What's affected

CVE-2026-97687, High: HTTPS proxy TLS configuration ignored or overridden. urllib3 lets you configure TLS for an HTTPS proxy (proxy_ssl_context, proxy_assert_hostname, proxy_assert_fingerprint) separately from TLS for the target server. In 1.26.0 through 2.7.0 those settings were not kept apart. The advisory lists four cases:

  1. With use_forwarding_for_https=True, the target's ssl_context was used for the proxy handshake instead of proxy_ssl_context.
  2. The target's cert_reqs could be applied to the proxy context, so cert_reqs="CERT_NONE" for a target could also disable verification of the proxy.
  3. That change was made in place, so a reused proxy_ssl_context stayed unverified for later connections.
  4. Target-specific SNI, hostname and fingerprint checks, and even the TLS client certificate, could be applied to the proxy handshake.

An attacker who can intercept traffic to the proxy may impersonate it when verification ends up disabled. In forwarding mode that exposes requests and responses. The advisory notes that in CONNECT tunneling mode, impersonating the proxy does not by itself break the separate TLS session to the target. Plain HTTP proxies are not affected.

CVE-2026-97689, High: unbounded chunk-size line in streaming. When streaming a chunked response with HTTPResponse.stream() or read_chunked(), urllib3 buffered the chunk-size line until it saw a newline. A malicious server could send a huge run of bytes with no newline. 2.8.0 rejects chunk-size fields over 65,536 bytes, matching the non-streaming path. Affected from 1.10.3. The advisory says this "also affects requests streaming API."

CVE-2026-97688, Moderate: infinite loop in chunked deflate streaming. In 2.6.2 through 2.7.0, streaming a chunked response with Content-Encoding: deflate and trailing bytes after the compressed stream could loop forever on the same bytes. Network timeouts don't help, because no socket read happens. It needs streaming with a positive chunk size and content decoding enabled.

The advisories list 2.8.0 as the fix and give no patched release on the 1.26.x line.

How to check if you're exposed

Find the installed version and what pulls it in:

pip show urllib3 | grep -i '^version'
pip install pipdeptree && pipdeptree -r -p urllib3

For the proxy bug, search for the options it involves. If none appear in your code or config, the four cases don't apply:

grep -rnE "proxy_ssl_context|use_forwarding_for_https|proxy_assert_(hostname|fingerprint)" --include=*.py .
grep -rnE "ProxyManager|proxy_from_url" --include=*.py .

For the streaming bugs, the question is who controls the server on the other end. Search for streaming and for requests calls to URLs that come from outside:

grep -rnE "stream=True|iter_content|iter_lines|\.stream\(|read_chunked" --include=*.py .

Don't stop at explicit streaming. In requests, Response.content is assembled by iterating iter_content(), which reads through urllib3's stream(). So a plain requests.get(url).text against a server an attacker controls goes through the same code. Webhook delivery, URL previews, feed fetchers, OAuth discovery and scrapers are the services to look at first.

What to do now

  1. Upgrade to urllib3 2.8.0 or later everywhere you can. Check base images and Lambda layers too, since they often carry their own copy.
  2. Find what pins urllib3 below 2. pipdeptree -r -p urllib3 shows which package set the constraint. With no 1.26.x fix listed, that dependency is now the thing to upgrade.
  3. Set proxy_ssl_context explicitly for HTTPS forwarding proxies. In 2.8.0, using use_forwarding_for_https=True without it emits a FutureWarning, and the advisory says urllib3 3.0 will raise an error.
  4. Never reuse an SSL context object between connections that need different verification settings.
  5. Bound outbound fetches in time and size. Wrap requests to untrusted servers in an overall deadline, not just a socket timeout. The deflate loop shows why a read timeout alone isn't enough.

Why it matters

Transport libraries carry risk in two very different shapes here. The proxy bug is a configuration bug: it only bites teams that set up HTTPS proxies with separate TLS settings, but for them it can silently turn off certificate checks. The streaming bugs are input bugs: they need nothing special in your config, just a conversation with a server that doesn't play fair.

Reachability for the second kind isn't about which function you call. It is about which servers you talk to. A service that only calls your own internal APIs has little exposure. A service that fetches URLs on behalf of users has a lot.

Try it yourself

Take one Python service's requirements.txt and drop it into the free scan at gold.safeguard.sh/scan. It runs in the browser and the file stays on your machine. Then run the two grep commands above and list every outbound request whose destination a user or partner can influence.

Click through it at your own pace, or open it full screen.

Quick check

A service routes traffic through a plain http:// proxy. Is CVE-2026-97687 relevant?

No. The advisory says code connecting through a plain HTTP proxy does not establish a TLS connection to the proxy and is not affected.

What is the advisory's interim workaround for the chunk-size streaming bug?

Read the response at once with read() instead of streaming it, until you can upgrade to 2.8.0.

Why doesn't a read timeout stop the deflate infinite loop?

urllib3 keeps trying to decode the same buffered bytes without making another socket read, so the network timeout never fires.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.