Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-urllib3_1 for 
openSUSE:Factory checked in at 2026-10-13 17:43:38
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-urllib3_1 (Old)
 and      /work/SRC/openSUSE:Factory/.python-urllib3_1.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-urllib3_1"

Tue Oct 13 17:43:38 2026 rev:16 rq:1384605 version:1.26.20

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-urllib3_1/python-urllib3_1.changes        
2026-06-16 18:29:21.825430968 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-urllib3_1.new.3698736/python-urllib3_1.changes
   2026-10-13 17:44:20.827805365 +0200
@@ -1,0 +2,36 @@
+Tue Oct 13 12:31:43 UTC 2026 - Daniel Garcia <[email protected]>
+
+- Fix backported patch CVE-2026-97687.2.patch, bsc#1283908
+
+-------------------------------------------------------------------
+Thu Oct  8 10:22:20 UTC 2026 - Daniel Garcia <[email protected]>
+
+- Support Python >= 3.14.8
+  Add upstream patches:
+  * avoid-flushing-swallowed-gzip-trailing-garbage.patch, 
gh#urllib3/urllib3#5271
+  * pass-server_hostname-in-test_start_closed_socket.patch, 
gh#urllib3/urllib3#5289
+
+-------------------------------------------------------------------
+Wed Oct  7 09:08:31 UTC 2026 - Daniel Garcia <[email protected]>
+
+- CVE-2026-97689: denial of service due to unbounded memory allocation
+  in chunk parser (bsc#1283910)
+  Add patch CVE-2026-97689.patch
+
+-------------------------------------------------------------------
+Wed Oct  7 09:02:24 UTC 2026 - Daniel Garcia <[email protected]>
+
+- CVE-2026-97688: denial of service due to infinite loop during
+  chunked Deflate decoding (bsc#1283909)
+  Add patch CVE-2026-97688.patch
+
+-------------------------------------------------------------------
+Tue Oct  6 09:55:35 UTC 2026 - Daniel Garcia <[email protected]>
+
+- CVE-2026-97687: traffic interception due to possible HTTPS proxy TLS
+  configuration override (bsc#1283908)
+  - Add upstream patches:
+  * CVE-2026-97687.1.patch, gh#urllib3/urllib3@b6447295fff7
+  * CVE-2026-97687.2.patch, gh#urllib3/urllib3@07408cec79d1
+
+-------------------------------------------------------------------

New:
----
  CVE-2026-97687.1.patch
  CVE-2026-97687.2.patch
  CVE-2026-97688.patch
  CVE-2026-97689.patch
  avoid-flushing-swallowed-gzip-trailing-garbage.patch
  pass-server_hostname-in-test_start_closed_socket.patch

----------(New B)----------
  New:  - Add upstream patches:
  * CVE-2026-97687.1.patch, gh#urllib3/urllib3@b6447295fff7
  * CVE-2026-97687.2.patch, gh#urllib3/urllib3@07408cec79d1
  New:
- Fix backported patch CVE-2026-97687.2.patch, bsc#1283908
  New:  chunked Deflate decoding (bsc#1283909)
  Add patch CVE-2026-97688.patch
  New:  in chunk parser (bsc#1283910)
  Add patch CVE-2026-97689.patch
  New:  Add upstream patches:
  * avoid-flushing-swallowed-gzip-trailing-garbage.patch, 
gh#urllib3/urllib3#5271
  * pass-server_hostname-in-test_start_closed_socket.patch, 
gh#urllib3/urllib3#5289
  New:  * avoid-flushing-swallowed-gzip-trailing-garbage.patch, 
gh#urllib3/urllib3#5271
  * pass-server_hostname-in-test_start_closed_socket.patch, 
gh#urllib3/urllib3#5289
----------(New E)----------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-urllib3_1.spec ++++++
--- /var/tmp/diff_new_pack.S1IFwK/_old  2026-10-13 17:44:22.142860478 +0200
+++ /var/tmp/diff_new_pack.S1IFwK/_new  2026-10-13 17:44:22.144860562 +0200
@@ -48,6 +48,18 @@
 Patch5:         CVE-2026-21441.patch
 # PATCH-FIX-UPSTREAM CVE-2026-44431.patch bsc#1265267
 Patch6:         CVE-2026-44431.patch
+# PATCH-FIX-UPSTREAM CVE-2026-97687.1.patch bsc#1283908 
gh#urllib3/urllib3@b6447295fff7
+Patch7:         CVE-2026-97687.1.patch
+# PATCH-FIX-UPSTREAM CVE-2026-97687.2.patch bsc#1283908 
gh#urllib3/urllib3@07408cec79d1
+Patch8:         CVE-2026-97687.2.patch
+# PATCH-FIX-UPSTREAM CVE-2026-97688.patch bsc#1283909 
gh#urllib3/urllib3@ea2ad7b21a80
+Patch9:         CVE-2026-97688.patch
+# PATCH-FIX-UPSTREAM CVE-2026-97689.patch bsc#1283910 
gh#urllib3/urllib3@cd770b059b54
+Patch10:        CVE-2026-97689.patch
+# PATCH-FIX-UPSTREAM avoid-flushing-swallowed-gzip-trailing-garbage.patch 
gh#urllib3/urllib3#5271
+Patch11:        avoid-flushing-swallowed-gzip-trailing-garbage.patch
+# PATCH-FIX-UPSTREAM pass-server_hostname-in-test_start_closed_socket.patch 
gh#urllib3/urllib3#5289
+Patch12:        pass-server_hostname-in-test_start_closed_socket.patch
 BuildRequires:  %{python_module base >= 3.7}
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module setuptools}

++++++ CVE-2026-97687.1.patch ++++++
Index: urllib3-1.26.20/changelog/2577.bugfix.rst
===================================================================
--- /dev/null
+++ urllib3-1.26.20/changelog/2577.bugfix.rst
@@ -0,0 +1,2 @@
+Fixed usage of ``proxy_ssl_context`` with ``ProxyManager`` when 
``use_forwarding_for_https=True``.
+Passing ``ssl_context`` instead of ``proxy_ssl_context`` for HTTPS proxies in 
this configuration now emits a ``FutureWarning`` and will raise an error in 
v3.0.
Index: urllib3-1.26.20/src/urllib3/connection.py
===================================================================
--- urllib3-1.26.20.orig/src/urllib3/connection.py
+++ urllib3-1.26.20/src/urllib3/connection.py
@@ -158,6 +158,13 @@ class HTTPConnection(_HTTPConnection, ob
         """
         self._dns_host = value
 
+    @property
+    def proxy_is_forwarding(self):
+        """
+        Return True if a forwarding proxy is configured, else return False
+        """
+        return bool(self.proxy) and self._tunnel_host is None
+
     def _new_conn(self):
         """Establish a socket connection and set nodelay settings on it.
 
@@ -392,17 +399,31 @@ class HTTPSConnection(HTTPConnection):
                 SystemTimeWarning,
             )
 
+        # Forwarding proxies should use proxy SSL context for
+        # wrapping since that's the connection being established,
+        # whereas tunneling proxies should use the connection's SSL
+        # context.
+        # However, for backwards compatibility reasons, if the proxy
+        # is forwarding but no proxy SSL context is provided, we
+        # fall back to using the connection's SSL context until
+        # urllib3 v3.0. Appropriate warning is emitted in
+        # ``ProxyManager.__init__``.
+        if self.proxy_is_forwarding and self.proxy_config is not None:
+            ssl_context = self.proxy_config.ssl_context
+        else:
+            ssl_context = self.ssl_context
+
         # Wrap socket using verification with the root certs in
         # trusted_root_certs
         default_ssl_context = False
-        if self.ssl_context is None:
+        if ssl_context is None:
             default_ssl_context = True
-            self.ssl_context = create_urllib3_context(
+            ssl_context = create_urllib3_context(
                 ssl_version=resolve_ssl_version(self.ssl_version),
                 cert_reqs=resolve_cert_reqs(self.cert_reqs),
             )
 
-        context = self.ssl_context
+        context = ssl_context
         context.verify_mode = resolve_cert_reqs(self.cert_reqs)
 
         # Try to load OS default certs if none are given.
Index: urllib3-1.26.20/src/urllib3/poolmanager.py
===================================================================
--- urllib3-1.26.20.orig/src/urllib3/poolmanager.py
+++ urllib3-1.26.20/src/urllib3/poolmanager.py
@@ -1,5 +1,7 @@
 from __future__ import absolute_import
 
+import warnings
+
 import collections
 import functools
 import logging
@@ -499,6 +501,21 @@ class ProxyManager(PoolManager):
         if proxy.scheme not in ("http", "https"):
             raise ProxySchemeUnknown(proxy.scheme)
 
+        if (
+            use_forwarding_for_https
+            and proxy.scheme == "https"
+            and connection_pool_kw.get("ssl_context") is not None
+        ):
+            warnings.warn(
+                "Passing ssl_context when use_forwarding_for_https=True is 
deprecated "
+                "and will raise an error in urllib3 v3.0. "
+                "Use proxy_ssl_context to configure the TLS connection to the 
proxy.",
+                FutureWarning,
+                stacklevel=2,
+            )
+            if proxy_ssl_context is None:
+                proxy_ssl_context = connection_pool_kw.get("ssl_context")
+
         if not proxy.port:
             port = port_by_scheme.get(proxy.scheme, 80)
             proxy = proxy._replace(port=port)
Index: urllib3-1.26.20/test/with_dummyserver/test_proxy_poolmanager.py
===================================================================
--- urllib3-1.26.20.orig/test/with_dummyserver/test_proxy_poolmanager.py
+++ urllib3-1.26.20/test/with_dummyserver/test_proxy_poolmanager.py
@@ -1,3 +1,5 @@
+import typing
+import contextlib
 import json
 import os.path
 import shutil
@@ -23,6 +25,7 @@ from dummyserver.testcase import HTTPDum
 from urllib3._collections import HTTPHeaderDict
 from urllib3.util.retry import RequestHistory
 from urllib3.connectionpool import VerifiedHTTPSConnection, connection_from_url
+from urllib3.connection import HTTPSConnection
 from urllib3.exceptions import (
     ConnectTimeoutError,
     InsecureRequestWarning,
@@ -149,6 +152,41 @@ class TestHTTPProxyManager(HTTPDummyProx
             r = https.request("GET", "%s/" % self.https_url)
             assert r.status == 200
 
+    @requires_network
+    @pytest.mark.parametrize(
+        "proxy_ssl_context_kw",
+        [
+            ("ssl_context",),
+            ("proxy_ssl_context",),
+            ("ssl_context", "proxy_ssl_context"),
+        ],
+    )
+    def test_https_proxy_forwarding_for_https_with_custom_context(self, 
proxy_ssl_context_kw):
+        """
+        Test that an HTTP request succeeds when using a forwarding HTTPS
+        proxy with an SSL context provided via either
+        ``proxy_ssl_context``, ``ssl_context`` (fallback), or both.
+        """
+
+        proxy_ctx = ssl.create_default_context()
+        proxy_ctx.load_verify_locations(DEFAULT_CA)
+
+        warning_checker: contextlib.AbstractContextManager[typing.Any]
+        if "ssl_context" in proxy_ssl_context_kw:
+            warning_checker = pytest.warns(Warning, match="ssl_context")
+        else:
+            warning_checker = contextlib.nullcontext()
+        with warning_checker:
+            proxy = proxy_from_url(
+                self.https_proxy_url,
+                **{kw: proxy_ctx for kw in proxy_ssl_context_kw},
+                use_forwarding_for_https=True,
+            )
+
+        with proxy:
+            resp = proxy.request("GET", self.https_url)
+            assert resp.status == 200
+
     def test_nagle_proxy(self):
         """Test that proxy connections do not have TCP_NODELAY turned on"""
         with ProxyManager(self.proxy_url) as http:
@@ -701,15 +739,8 @@ class TestHTTPProxyManager(HTTPDummyProx
             assert type(e.value.reason) == SSLError
 
     @requires_network
-    @pytest.mark.parametrize(
-        ["proxy_scheme", "use_forwarding_for_https"],
-        [
-            ("http", False),
-            ("https", False),
-            ("https", True),
-        ],
-    )
-    def test_proxy_https_target_tls_error(self, proxy_scheme, 
use_forwarding_for_https):
+    @pytest.mark.parametrize("proxy_scheme", ["http", "https"])
+    def test_proxy_https_target_tls_error(self, proxy_scheme):
         _should_skip_https_in_https(proxy_scheme, "https")
 
         proxy_url = self.https_proxy_url if proxy_scheme == "https" else 
self.proxy_url
@@ -721,12 +752,102 @@ class TestHTTPProxyManager(HTTPDummyProx
             proxy_url,
             proxy_ssl_context=proxy_ctx,
             ssl_context=ctx,
-            use_forwarding_for_https=use_forwarding_for_https,
         ) as proxy:
             with pytest.raises(MaxRetryError) as e:
                 proxy.request("GET", self.https_url)
             assert type(e.value.reason) == SSLError
 
+    @requires_network
+    def test_forwarding_proxy_tls_error_with_fallback(self) -> None:
+        """
+        Test that when only ``ssl_context`` is passed to a forwarding
+        HTTPS proxy, a ``FutureWarning`` is emitted and if the TLS
+        handshake with the proxy fails using that context, the error is
+        a ``ProxyError`` wrapping the original TLS error.
+        """
+        ctx = ssl.create_default_context()
+
+        with pytest.warns(Warning, match="ssl_context"):
+            proxy = proxy_from_url(
+                self.https_proxy_url,
+                ssl_context=ctx,
+                use_forwarding_for_https=True,
+            )
+
+        with proxy:
+            with pytest.raises(MaxRetryError) as e:
+                proxy.request("GET", self.https_url)
+            assert isinstance(e.value.reason, SSLError)
+
+    @requires_network
+    def test_forwarding_proxy_ssl_context_precedence(self) -> None:
+        """
+        Test that when both ``ssl_context`` and ``proxy_ssl_context``
+        are passed to a forwarding HTTPS proxy, ``proxy_ssl_context`` is
+        used for the proxy connection and ``ssl_context`` is ignored
+        (with a warning).
+        """
+        ssl_context = ssl.create_default_context(cafile=DEFAULT_CA)
+        proxy_ssl_context = ssl.create_default_context(cafile=DEFAULT_CA)
+
+        with pytest.warns(Warning, match="ssl_context"):
+            proxy = proxy_from_url(
+                self.https_proxy_url,
+                proxy_ssl_context=proxy_ssl_context,
+                ssl_context=ssl_context,
+                use_forwarding_for_https=True,
+            )
+
+        with proxy:
+            pool = proxy.connection_from_url(self.https_url)
+            with contextlib.closing(pool._new_conn()) as conn:
+                conn = typing.cast(HTTPSConnection, conn)
+                conn.connect()
+                assert isinstance(conn.sock, ssl.SSLSocket)
+                assert conn.sock.context is proxy_ssl_context
+
+    @requires_network
+    def test_forwarding_proxy_ssl_context_fallback(self) -> None:
+        """
+        Test that when only ``ssl_context`` is passed to a forwarding
+        HTTPS proxy, a ``FutureWarning`` is emitted and ``ssl_context``
+        is used as the proxy TLS context (fallback).
+        """
+        ssl_context = ssl.create_default_context(cafile=DEFAULT_CA)
+
+        with pytest.warns(Warning, match="ssl_context"):
+            proxy = proxy_from_url(
+                self.https_proxy_url,
+                ssl_context=ssl_context,
+                use_forwarding_for_https=True,
+            )
+
+        with proxy:
+            pool = proxy.connection_from_url(self.https_url)
+            with contextlib.closing(pool._new_conn()) as conn:
+                conn = typing.cast(HTTPSConnection, conn)
+                conn.connect()
+                assert isinstance(conn.sock, ssl.SSLSocket)
+                assert conn.sock.context is ssl_context
+
+    @requires_network
+    def test_forwarding_non_https_proxy_ssl_context_fallback(self) -> None:
+        """
+        Test that when only ``ssl_context`` is passed to a forwarding
+        non-HTTPS proxy, no warning or error is emitted.
+        """
+        ssl_context = ssl.create_default_context(cafile=DEFAULT_CA)
+        # No warning/error should be emitted even when ``ssl_context``
+        # is provided to a non-HTTPS proxy for some reason.
+        proxy = proxy_from_url(
+            self.proxy_url,
+            ssl_context=ssl_context,
+            use_forwarding_for_https=True,
+        )
+        with proxy:
+            resp = proxy.request("GET", self.https_url)
+            assert resp.status == 200
+
     def test_scheme_host_case_insensitive(self):
         """Assert that upper-case schemes and hosts are normalized."""
         with proxy_from_url(self.proxy_url.upper(), ca_certs=DEFAULT_CA) as 
http:

++++++ CVE-2026-97687.2.patch ++++++
Index: urllib3-1.26.20/changelog/5093.bugfix.rst
===================================================================
--- /dev/null
+++ urllib3-1.26.20/changelog/5093.bugfix.rst
@@ -0,0 +1,7 @@
+Fixed ``HTTPSConnection.connect()`` overriding
+``ProxyConfig.ssl_context``'s certificate policy and proxy identity checks with
+the target connection's TLS settings when forwarding through an HTTPS proxy.
+
+``HTTPSConnection`` no longer applies target SNI, assertions, or client
+credentials to forwarding proxy handshakes and continues to use its
+``ssl_context`` as a fallback when an HTTPS proxy forwards an HTTP target.
Index: urllib3-1.26.20/src/urllib3/connection.py
===================================================================
--- urllib3-1.26.20.orig/src/urllib3/connection.py
+++ urllib3-1.26.20/src/urllib3/connection.py
@@ -408,8 +408,11 @@ class HTTPSConnection(HTTPConnection):
         # fall back to using the connection's SSL context until
         # urllib3 v3.0. Appropriate warning is emitted in
         # ``ProxyManager.__init__``.
+        cert_reqs = self.cert_reqs
         if self.proxy_is_forwarding and self.proxy_config is not None:
             ssl_context = self.proxy_config.ssl_context
+            if ssl_context is not None:
+                cert_reqs = ssl_context.verify_mode
         else:
             ssl_context = self.ssl_context
 
@@ -424,7 +427,7 @@ class HTTPSConnection(HTTPConnection):
             )
 
         context = ssl_context
-        context.verify_mode = resolve_cert_reqs(self.cert_reqs)
+        context.verify_mode = resolve_cert_reqs(cert_reqs)
 
         # Try to load OS default certs if none are given.
         # Works well on Windows (requires Python3.4+)
@@ -437,18 +440,27 @@ class HTTPSConnection(HTTPConnection):
         ):
             context.load_default_certs()
 
-        self.sock = ssl_wrap_socket(
-            sock=conn,
-            keyfile=self.key_file,
-            certfile=self.cert_file,
-            key_password=self.key_password,
-            ca_certs=self.ca_certs,
-            ca_cert_dir=self.ca_cert_dir,
-            ca_cert_data=self.ca_cert_data,
-            server_hostname=server_hostname,
-            ssl_context=context,
-            tls_in_tls=tls_in_tls,
-        )
+        if self.proxy_is_forwarding and self.proxy_config is not None:
+            self.sock = self._connect_tls_proxy(self.host, self.sock)
+            self.is_verified = self.proxy_is_verified is True
+        else:
+            self.sock = ssl_wrap_socket(
+                sock=conn,
+                keyfile=self.key_file,
+                certfile=self.cert_file,
+                key_password=self.key_password,
+                ca_certs=self.ca_certs,
+                ca_cert_dir=self.ca_cert_dir,
+                ca_cert_data=self.ca_cert_data,
+                server_hostname=server_hostname,
+                ssl_context=context,
+                tls_in_tls=tls_in_tls,
+            )
+
+            self.is_verified = (
+                context.verify_mode == ssl.CERT_REQUIRED
+                or self.assert_fingerprint is not None
+            )
 
         # If we're using all defaults and the connection
         # is TLSv1 or TLSv1.1 we throw a DeprecationWarning
@@ -492,17 +504,28 @@ class HTTPSConnection(HTTPConnection):
                 )
             _match_hostname(cert, self.assert_hostname or server_hostname)
 
-        self.is_verified = (
-            context.verify_mode == ssl.CERT_REQUIRED
-            or self.assert_fingerprint is not None
-        )
-
     def _connect_tls_proxy(self, hostname, conn):
         """
         Establish a TLS connection to the proxy using the provided SSL context.
         """
-        proxy_config = self.proxy_config
-        ssl_context = proxy_config.ssl_context
+        proxy_ssl_context = self.proxy_config.ssl_context
+        if proxy_ssl_context is not None:
+            # Prefer the proxy's cert policy for the proxy connection
+            ssl_context = proxy_ssl_context
+            cert_reqs = proxy_ssl_context.verify_mode
+            ca_certs = None
+            ca_cert_dir = None
+            ca_cert_data = None
+            ssl_version = None
+        else:
+            # Otherwise we inherit the pool's cert policies
+            ssl_context = self.ssl_context if self.proxy_is_forwarding else 
None
+            cert_reqs = self.cert_reqs
+            ca_certs = self.ca_certs
+            ca_cert_dir = self.ca_cert_dir
+            ca_cert_data = self.ca_cert_data
+            ssl_version = self.ssl_version
+
         if ssl_context:
             # If the user provided a proxy context, we assume CA and client
             # certificates have already been set
@@ -513,20 +536,20 @@ class HTTPSConnection(HTTPConnection):
             )
 
         ssl_context = create_proxy_ssl_context(
-            self.ssl_version,
-            self.cert_reqs,
-            self.ca_certs,
-            self.ca_cert_dir,
-            self.ca_cert_data,
+            ssl_version,
+            cert_reqs,
+            ca_certs,
+            ca_cert_dir,
+            ca_cert_data,
         )
 
         # If no cert was provided, use only the default options for server
         # certificate validation
         socket = ssl_wrap_socket(
             sock=conn,
-            ca_certs=self.ca_certs,
-            ca_cert_dir=self.ca_cert_dir,
-            ca_cert_data=self.ca_cert_data,
+            ca_certs=ca_certs,
+            ca_cert_dir=ca_cert_dir,
+            ca_cert_data=ca_cert_data,
             server_hostname=hostname,
             ssl_context=ssl_context,
         )
Index: urllib3-1.26.20/src/urllib3/poolmanager.py
===================================================================
--- urllib3-1.26.20.orig/src/urllib3/poolmanager.py
+++ urllib3-1.26.20/src/urllib3/poolmanager.py
@@ -501,6 +501,9 @@ class ProxyManager(PoolManager):
         if proxy.scheme not in ("http", "https"):
             raise ProxySchemeUnknown(proxy.scheme)
 
+        # Keep the deprecated ssl_context fallback on the manager for
+        # compatibility, while passing only explicit proxy policy to 
connections.
+        self.proxy_ssl_context = proxy_ssl_context
         if (
             use_forwarding_for_https
             and proxy.scheme == "https"
@@ -513,8 +516,8 @@ class ProxyManager(PoolManager):
                 FutureWarning,
                 stacklevel=2,
             )
-            if proxy_ssl_context is None:
-                proxy_ssl_context = connection_pool_kw.get("ssl_context")
+            if self.proxy_ssl_context is None:
+                self.proxy_ssl_context = connection_pool_kw.get("ssl_context")
 
         if not proxy.port:
             port = port_by_scheme.get(proxy.scheme, 80)
@@ -522,7 +525,6 @@ class ProxyManager(PoolManager):
 
         self.proxy = proxy
         self.proxy_headers = proxy_headers or {}
-        self.proxy_ssl_context = proxy_ssl_context
         self.proxy_config = ProxyConfig(proxy_ssl_context, 
use_forwarding_for_https)
 
         connection_pool_kw["_proxy"] = self.proxy
Index: urllib3-1.26.20/test/with_dummyserver/test_proxy_poolmanager.py
===================================================================
--- urllib3-1.26.20.orig/test/with_dummyserver/test_proxy_poolmanager.py
+++ urllib3-1.26.20/test/with_dummyserver/test_proxy_poolmanager.py
@@ -117,6 +117,18 @@ class TestHTTPProxyManager(HTTPDummyProx
             assert "is not supported in Python 2" in str(excinfo.value)
 
     @withPyOpenSSL
+    def test_https_proxy_with_proxy_ssl_context_pyopenssl(self) -> None:
+        proxy_ssl_context = create_urllib3_context()
+        proxy_ssl_context.load_verify_locations(DEFAULT_CA)
+
+        with proxy_from_url(
+            self.https_proxy_url,
+            proxy_ssl_context=proxy_ssl_context,
+        ) as https:
+            response = https.request("GET", f"{self.http_url}/")
+            assert response.status == 200
+
+    @withPyOpenSSL
     @onlyPy3
     def test_https_proxy_pyopenssl_not_supported(self):
         with proxy_from_url(self.https_proxy_url, ca_certs=DEFAULT_CA) as 
https:
@@ -831,6 +843,22 @@ class TestHTTPProxyManager(HTTPDummyProx
                 assert conn.sock.context is ssl_context
 
     @requires_network
+    def test_https_proxy_to_http_target_ssl_context_fallback(self) -> None:
+        ssl_context = create_urllib3_context()
+        ssl_context.load_verify_locations(DEFAULT_CA)
+
+        with proxy_from_url(
+            self.https_proxy_url,
+            ssl_context=ssl_context,
+        ) as proxy:
+            pool = proxy.connection_from_url(self.http_url)
+            with contextlib.closing(pool._new_conn()) as conn:
+                conn = typing.cast(HTTPSConnection, conn)
+                conn.connect()
+                assert isinstance(conn.sock, ssl.SSLSocket)
+                assert conn.sock.context is ssl_context
+
+    @requires_network
     def test_forwarding_non_https_proxy_ssl_context_fallback(self) -> None:
         """
         Test that when only ``ssl_context`` is passed to a forwarding
@@ -878,6 +906,12 @@ class TestHTTPProxyManager(HTTPDummyProx
         with pytest.raises(ProxySchemeUnknown, match=error_msg):
             proxy_from_url(url)
 
+    def test_is_verified_https_proxy_to_http_target_without_proxy_config(self) 
-> None:
+        with proxy_from_url(self.https_proxy_url, ca_certs=DEFAULT_CA) as 
https:
+            https.connection_pool_kw["_proxy_config"] = None
+            r = https.request("GET", f"{self.http_url}/")
+            assert r.status == 200
+
 
 @pytest.mark.skipif(not HAS_IPV6, reason="Only runs on IPv6 systems")
 class TestIPv6HTTPProxyManager(IPv6HTTPDummyProxyTestCase):

++++++ CVE-2026-97688.patch ++++++
Index: urllib3-1.26.20/changelog/GHSA-gh4c-6fx4-qh6g.bugfix.rst
===================================================================
--- /dev/null
+++ urllib3-1.26.20/changelog/GHSA-gh4c-6fx4-qh6g.bugfix.rst
@@ -0,0 +1 @@
+Fixed an infinite loop in ``HTTPResponse.stream()`` and 
``HTTPResponse.read_chunked()`` when decoding chunked responses with 
``Content-Encoding: deflate`` and trailing data after the compressed stream.
Index: urllib3-1.26.20/src/urllib3/response.py
===================================================================
--- urllib3-1.26.20.orig/src/urllib3/response.py
+++ urllib3-1.26.20/src/urllib3/response.py
@@ -53,6 +53,10 @@ class DeflateDecoder(object):
     def decompress(self, data, max_length = -1):
         data = self._unfed_data + data
         self._unfed_data = b""
+        # Data received after EOF cannot produce more output from this
+        # Deflate stream.
+        if self._obj.eof:
+            return b""
         if not data and not self._obj.unconsumed_tail:
             return data
         original_max_length = max_length
@@ -95,7 +99,9 @@ class DeflateDecoder(object):
     @property
     def has_unconsumed_tail(self):
         return bool(self._unfed_data) or (
-            bool(self._obj.unconsumed_tail) and not self._first_try
+            bool(self._obj.unconsumed_tail)
+            and not self._first_try
+            and not self._obj.eof
         )
 
 
Index: urllib3-1.26.20/test/test_response.py
===================================================================
--- urllib3-1.26.20.orig/test/test_response.py
+++ urllib3-1.26.20/test/test_response.py
@@ -8,7 +8,7 @@ import ssl
 import zlib
 from base64 import b64decode
 from io import BufferedReader, BytesIO, TextIOWrapper
-from test import onlyBrotlipy
+from test import LONG_TIMEOUT, onlyBrotlipy
 
 try:
     import unittest.mock as mock
@@ -1104,6 +1104,38 @@ class TestResponse(object):
         resp = HTTPResponse(fp, retries=retry)
         assert resp.geturl() == "https://www.example.com";
 
+    @pytest.mark.timeout(LONG_TIMEOUT)
+    @pytest.mark.parametrize(
+        "data",
+        [d[1] for d in _test_compressor_params],
+        ids=[d[0] for d in _test_compressor_params],
+    )
+    def test_read_chunked_with_trailing_data_does_not_hang(self, request, 
data):
+        if data is None:
+            pytest.skip(f"Proper {request.node.callspec.id} decoder is not 
available")
+        # The decoded body must fill multiple bounded reads so EOF is reached
+        # while trailing data remains in the decoder's input buffer.
+        original_data = b"A" * 100
+        content_encoding, compress_func = data
+        compressed_data = compress_func(original_data) + b"tail"
+        httplib_r = httplib.HTTPResponse(MockSock)  # type: ignore[arg-type]
+        httplib_r.fp = MockChunkedEncodingResponse([compressed_data])  # type: 
ignore[assignment]
+        r = HTTPResponse(
+            httplib_r,
+            preload_content=False,
+            headers={
+                "transfer-encoding": "chunked",
+                "content-encoding": content_encoding,
+            },
+        )
+
+        stream = r.stream(len(original_data) // 2, decode_content=True)
+        if content_encoding in ("br", "zstd"):
+            with pytest.raises(DecodeError):
+                list(stream)
+        else:
+            assert b"".join(stream) == original_data
+
     @pytest.mark.parametrize(
         ["payload", "expected_stream"],
         [

++++++ CVE-2026-97689.patch ++++++
Index: urllib3-1.26.20/changelog/GHSA-vxq7-64xx-v4gw.bugfix.rst
===================================================================
--- /dev/null
+++ urllib3-1.26.20/changelog/GHSA-vxq7-64xx-v4gw.bugfix.rst
@@ -0,0 +1 @@
+Fixed a denial-of-service issue where a malicious server could cause unbounded 
memory usage in the streaming chunked-transfer-encoding read path 
(``HTTPResponse.stream()`` / ``HTTPResponse.read_chunked()``) by sending an 
unterminated chunk-size line. The chunk-size line and trailing CRLF are now 
read with a bounded length matching :data:`http.client._MAXLINE`, raising 
:class:`~urllib3.exceptions.ProtocolError` if exceeded, consistent with the 
non-streaming ``HTTPResponse.read()`` path.
Index: urllib3-1.26.20/src/urllib3/response.py
===================================================================
--- urllib3-1.26.20.orig/src/urllib3/response.py
+++ urllib3-1.26.20/src/urllib3/response.py
@@ -39,6 +39,13 @@ from .util.response import is_fp_closed,
 
 log = logging.getLogger(__name__)
 
+# Maximum length of a chunk-size line (and the trailing CRLF line) when 
reading a
+# chunked-transfer-encoded response. Matches ``http.client._MAXLINE`` so 
urllib3's
+# streaming path bounds these reads exactly like the stdlib ``.read()`` path 
does,
+# preventing a malicious server from forcing unbounded buffering (memory 
exhaustion)
+# via an unterminated chunk-size line.
+_MAX_CHUNK_LINE_LENGTH = 2**16
+
 
 class DeflateDecoder(object):
     def __init__(self):
@@ -1018,7 +1025,12 @@ class HTTPResponse(io.IOBase):
         # we'll try to read it from socket.
         if self.chunk_left is not None:
             return
-        line = self._fp.fp.readline()
+        line = self._fp.fp.readline(_MAX_CHUNK_LINE_LENGTH + 1)  # type: 
ignore[union-attr]
+        if len(line) > _MAX_CHUNK_LINE_LENGTH:
+            self.close()
+            raise ProtocolError(
+                "Response chunk size line exceeded maximum allowed length"
+            )
         line = line.split(b";", 1)[0]
         try:
             self.chunk_left = int(line, 16)
@@ -1111,7 +1123,11 @@ class HTTPResponse(io.IOBase):
 
             # Chunk content ends with \r\n: discard it.
             while True:
-                line = self._fp.fp.readline()
+                line = self._fp.fp.readline(_MAX_CHUNK_LINE_LENGTH + 1)
+                if len(line) > _MAX_CHUNK_LINE_LENGTH:
+                    raise ProtocolError(
+                        "Response chunk trailer line exceeded maximum allowed 
length"
+                    )
                 if not line:
                     # Some sites may not end with '\r\n'.
                     break
Index: urllib3-1.26.20/test/test_response.py
===================================================================
--- urllib3-1.26.20.orig/test/test_response.py
+++ urllib3-1.26.20/test/test_response.py
@@ -30,6 +30,7 @@ from urllib3.exceptions import (
 )
 from urllib3.packages.six.moves import http_client as httplib
 from urllib3.response import HTTPResponse, BytesQueueBuffer, brotli
+from urllib3.response import _MAX_CHUNK_LINE_LENGTH
 from urllib3.util.response import is_fp_closed
 from urllib3.util.retry import RequestHistory, Retry
 
@@ -1019,6 +1020,63 @@ class TestResponse(object):
         assert isinstance(orig_ex, InvalidChunkLength)
         assert orig_ex.length == six.b(fp.BAD_LENGTH_LINE)
 
+    def test_chunk_size_line_too_long(self) -> None:
+        # A malicious server can send a chunk-size line with no newline. 
urllib3's
+        # streaming path must reject it with a bounded read rather than 
buffering
+        # the whole line (memory exhaustion). See GHSA / CVE for chunked DoS.
+        stream = [b"foooo"]
+        fp = MockChunkedEncodingLongChunkSizeLine(stream)
+        r = httplib.HTTPResponse(MockSock)  # type: ignore[arg-type]
+        r.fp = fp  # type: ignore[assignment]
+        r.chunked = True
+        r.chunk_left = None
+        resp = HTTPResponse(
+            r, preload_content=False, headers={"transfer-encoding": "chunked"}
+        )
+        with pytest.raises(ProtocolError) as ctx:
+            next(resp.read_chunked())
+
+        assert "chunk size line exceeded maximum allowed length" in 
str(ctx.value)
+
+    @pytest.mark.parametrize(
+        "trailer_length",
+        [
+            _MAX_CHUNK_LINE_LENGTH - 1,
+            _MAX_CHUNK_LINE_LENGTH,
+            _MAX_CHUNK_LINE_LENGTH + 1,
+        ],
+    )
+    @pytest.mark.parametrize("line_ending", [b"\r\n", b""])
+    def test_chunk_trailer_line_length(
+        self, trailer_length: int, line_ending: bytes
+    ) -> None:
+        # The line limit includes its CRLF, but not the final blank line.
+        trailer = b"X:" + b"x" * (trailer_length - 2 - len(line_ending)) + 
line_ending
+        fp = BytesIO(b"3\r\nfoo\r\n0\r\n" + trailer + (b"\r\n" if line_ending 
else b""))
+        r = httplib.HTTPResponse(MockSock, method="GET")  # type: 
ignore[arg-type]
+        r.fp = fp  # type: ignore[assignment]
+        resp = HTTPResponse(
+            r,
+            preload_content=False,
+            headers={"transfer-encoding": "chunked"},
+            original_response=r,
+        )
+        chunks = resp.read_chunked()
+        assert next(chunks) == b"foo"
+
+        if trailer_length > _MAX_CHUNK_LINE_LENGTH:
+            with pytest.raises(
+                ProtocolError,
+                match="Response chunk trailer line exceeded maximum allowed 
length",
+            ):
+                next(chunks)
+        else:
+            assert list(chunks) == []
+
+        assert fp.closed
+        assert r.isclosed()
+        assert resp.closed
+
     def test_chunked_response_without_crlf_on_end(self):
         stream = [b"foo", b"bar", b"baz"]
         fp = MockChunkedEncodingWithoutCRLFOnEnd(stream)
@@ -1261,8 +1319,20 @@ class MockChunkedEncodingResponse(object
                 self.cur_chunk = self.cur_chunk[amt:]
             return chunk_part
 
-    def readline(self):
-        return self.pop_current_chunk(till_crlf=True)
+    def readline(self, amt=-1):
+        # Emulate a real file object's readline(size): return bytes up to and
+        # including the first newline, or at most ``amt`` bytes when ``amt >= 
0``,
+        # whichever comes first.
+        if len(self.cur_chunk) <= 0:
+            self.cur_chunk = self._pop_new_chunk()
+        line = self.cur_chunk
+        newline_index = line.find(b"\n")
+        if newline_index != -1:
+            line = line[: newline_index + 1]
+        if 0 <= amt < len(line):
+            line = line[:amt]
+        self.cur_chunk = self.cur_chunk[len(line) :]
+        return line
 
     def read(self, amt=-1):
         return self.pop_current_chunk(amt)
@@ -1287,6 +1357,14 @@ class MockChunkedInvalidChunkLength(Mock
         return "%s%s\r\n" % (self.BAD_LENGTH_LINE, chunk.decode())
 
 
+class MockChunkedEncodingLongChunkSizeLine(MockChunkedEncodingResponse):
+    def _encode_chunk(self, chunk: bytes) -> bytes:
+        # A chunk-size line far longer than _MAX_CHUNK_LINE_LENGTH with no
+        # newline, simulating a malicious server that never terminates the
+        # size line (memory-exhaustion attack).
+        return b"f" * (2**16 + 1024)
+
+
 class MockChunkedEncodingWithoutCRLFOnEnd(MockChunkedEncodingResponse):
     def _encode_chunk(self, chunk):
         return "%X\r\n%s%s" % (
Index: urllib3-1.26.20/test/with_dummyserver/test_socketlevel.py
===================================================================
--- urllib3-1.26.20.orig/test/with_dummyserver/test_socketlevel.py
+++ urllib3-1.26.20/test/with_dummyserver/test_socketlevel.py
@@ -33,6 +33,7 @@ except ImportError:
         pass
 
 
+import contextlib
 import os
 import os.path
 import platform
@@ -1872,6 +1873,40 @@ class TestStream(SocketDummyServerTestCa
 
             done_event.set()
 
+    def test_chunked_size_line_too_long_does_not_buffer(self) -> None:
+        # A malicious server can advertise chunked encoding and then send an
+        # unterminated chunk-size line (no newline). The streaming path must
+        # reject it with a bounded read instead of buffering the whole run,
+        # which would exhaust memory.
+        done_event = Event()
+        # Well above the 65536-byte cap, but small enough for a fast test.
+        run_length = 512 * 1024
+
+        def socket_handler(listener: socket.socket) -> None:
+            sock = listener.accept()[0]
+
+            buf = b""
+            while not buf.endswith(b"\r\n\r\n"):
+                buf += sock.recv(65536)
+
+            sock.sendall(b"HTTP/1.1 200 OK\r\nTransfer-Encoding: 
chunked\r\n\r\n")
+            # A chunk-size line that never terminates with a newline.
+            with contextlib.suppress(OSError):
+                sock.sendall(b"f" * run_length)
+
+            done_event.wait(5)
+            sock.close()
+
+        self._start_server(socket_handler)
+
+        with HTTPConnectionPool(self.host, self.port, retries=False) as pool:
+            r = pool.request("GET", "/", timeout=LONG_TIMEOUT, 
preload_content=False)
+            with pytest.raises(ProtocolError) as ctx:
+                next(r.stream(65536))
+            assert "chunk size line exceeded maximum allowed length" in 
str(ctx.value)
+
+            done_event.set()
+
 
 class TestBadContentLength(SocketDummyServerTestCase):
     def test_enforce_content_length_get(self):

++++++ avoid-flushing-swallowed-gzip-trailing-garbage.patch ++++++
Index: urllib3-1.26.20/changelog/5268.bugfix.rst
===================================================================
--- /dev/null
+++ urllib3-1.26.20/changelog/5268.bugfix.rst
@@ -0,0 +1,2 @@
+Prevented a suppressed :class:`GzipDecoder` decompression error caused by 
invalid
+trailing data from resurfacing during ``flush()`` on newer Python versions.
Index: urllib3-1.26.20/src/urllib3/response.py
===================================================================
--- urllib3-1.26.20.orig/src/urllib3/response.py
+++ urllib3-1.26.20/src/urllib3/response.py
@@ -183,6 +183,11 @@ class GzipDecoder(object):
     def has_unconsumed_tail(self):
         return bool(self._unconsumed_tail)
 
+    def flush(self) -> bytes:
+        if self._state == GzipDecoderState.SWALLOW_DATA:
+            return b""
+        return self._obj.flush()
+
 
 if brotli is not None:
 
Index: urllib3-1.26.20/test/test_response.py
===================================================================
--- urllib3-1.26.20.orig/test/test_response.py
+++ urllib3-1.26.20/test/test_response.py
@@ -31,6 +31,7 @@ from urllib3.exceptions import (
 from urllib3.packages.six.moves import http_client as httplib
 from urllib3.response import HTTPResponse, BytesQueueBuffer, brotli
 from urllib3.response import _MAX_CHUNK_LINE_LENGTH
+from urllib3.response import GzipDecoder
 from urllib3.util.response import is_fp_closed
 from urllib3.util.retry import RequestHistory, Retry
 
@@ -244,6 +245,16 @@ class TestResponse(object):
 
         assert ret == b"foofoofoo"
 
+    def test_decode_gzip_swallow_garbage_does_not_flush(self) -> None:
+        decoder = GzipDecoder()
+        data = gzip.compress(b"foo") + b"garbage"
+
+        assert decoder.decompress(data) == b"foo"
+        decoder._obj = mock.Mock(flush=mock.Mock(side_effect=zlib.error))
+
+        assert decoder.flush() == b""
+        decoder._obj.flush.assert_not_called()
+
     def test_chunked_decoding_gzip_swallow_garbage(self):
         compress = zlib.compressobj(6, zlib.DEFLATED, 16 + zlib.MAX_WBITS)
         data = compress.compress(b"foo")

++++++ pass-server_hostname-in-test_start_closed_socket.patch ++++++
Index: urllib3-1.26.20/changelog/5289.misc.rst
===================================================================
--- /dev/null
+++ urllib3-1.26.20/changelog/5289.misc.rst
@@ -0,0 +1 @@
+Fixed SSL transport test failures on Python 3.10.22+, 3.11.17+, 3.12.15+, 
3.13.16+, and 3.14.8+.
Index: urllib3-1.26.20/test/test_ssltransport.py
===================================================================
--- urllib3-1.26.20.orig/test/test_ssltransport.py
+++ urllib3-1.26.20/test/test_ssltransport.py
@@ -109,7 +109,7 @@ class SingleTLSLayerTestCase(SocketDummy
         context = ssl.create_default_context()
         sock.close()
         with pytest.raises(OSError):
-            SSLTransport(sock, context)
+            SSLTransport(sock, context, server_hostname="localhost")
 
     @pytest.mark.timeout(PER_TEST_TIMEOUT)
     def test_close_after_handshake(self):

Reply via email to