Skip to content

pip 26.0.1.post3 (Py3.9): vendored urllib3 1.26.20.post4; requests CVE-2026-25645 - #15

Merged
icanhasmath merged 2 commits into
26.0.1.xfrom
26.0.1.post3-urllib3-requests
Oct 28, 2026
Merged

icanhasmath merged 2 commits into
26.0.1.xfrom
26.0.1.post3-urllib3-requests

Conversation

@icanhasmath

@icanhasmath icanhasmath commented Oct 28, 2026 •

Copy link
Copy Markdown

Security release pip 26.0.1.post3 for the Python 3.9 line, also bundled by 3.10.22.1. It follows up the review of ActiveState/cpython#110: moving 3.10 from pip 26.2.2 to 26.0.1.post2 also moved its vendored urllib3 back to an unpatched 1.26.20.

Changes

Vendored urllib3 1.26.20 → ActiveState urllib3 1.26.20.post4. That release carries 1.26-line ports of these fixes:

CVE Advisory Issue
CVE-2025-66418 GHSA-gm62-xv2j-4w53 unbounded Content-Encoding decoder chain
CVE-2025-66471 GHSA-2xpw-w6gg-jr37 streaming API fully decompresses highly compressed data
CVE-2026-21441 GHSA-38jv-5279-wg99 redirect bodies decompressed while draining
CVE-2026-44431 GHSA-qccp-gfcp-xxvc sensitive headers forwarded on cross-origin redirects
CVE-2026-97687 GHSA-8988-9cw3-xx77 HTTPS proxy TLS used the target's TLS settings
CVE-2026-97689 GHSA-vxq7-64xx-v4gw unbounded chunk-size and trailer lines
CVE-2025-50181 GHSA-pq67-6m6q-mj2v PoolManager retries did not limit redirects
  • How it was vendored: I took the fork's src/urllib3 from the 1.26.20.post4 sdist and replayed pip's existing vendoring diff on top: the pip._vendor import paths, Brotli disabled, and LICENSE.txt. Every hunk applied cleanly. The fork's new Brotli code is all behind if brotli is not None.
  • Version check: urllib3.__version__ stays 1.26.20, so requests' version check doesn't warn.

Vendored requests 2.32.5: CVE-2026-25645 (GHSA-gc5v-m9x4-r6x2)

  • This backports upstream's fix (psf/requests 66d21cb07b, released in 2.33.0). extract_zipped_paths() extracted to a predictable name in the shared temporary directory and reused any file already there. It now extracts to a new mkstemp file.
  • It also ports upstream's companion commit e331a288f3 (9dac45c). HTTPAdapter.cert_verify() now uses the default CA bundle path directly instead of passing it through extract_zipped_paths().
  • pip didn't reach the vulnerable path. pip's vendored certifi extracts cacert.pem to a real file itself, so extract_zipped_paths() returned early even when pip runs from a zip. Both changes are defense in depth.

Validation

urllib3's own test suite, in python:3.9-slim and python:3.10-slim, comparing the fork's sdist with upstream urllib3 1.26.20 and without Brotli, as in pip:

  • Fork: 1456 pass, 5 fail.
  • Upstream: 1385 pass, 4 fail.
  • Shared failures: four fail on both and come from the container setup: test_recent_date, two test_tunneling_proxy_request_timeout cases and test_ssl_read_timeout.
  • Fork-only failure: test_proxy_https_target_tls_error[https-True]. That test expects an SSLError for forwarding through an HTTPS proxy, which only happened because of CVE-2026-97687: the target's TLS context was applied to the proxy. Upstream 2.7.0 skips that case as a known bug (urllib3#2577), and 2.8.0, which has the fix, drops it. So it's a stale test in the fork, not a regression. I'll fix that test in ActiveState/urllib3 separately.

pip wheel, built with python -m build, in python:3.9-slim and python:3.10-slim:

  • Versions: pip reports 26.0.1.post3, with vendored urllib3 1.26.20 (the fork), requests 2.32.5 and certifi 2026.10.23. requests emits no RequestsDependencyWarning, even under -W error.
  • Running from a zip: python pip-26.0.1.post3-py3-none-any.whl/pip download six==1.16.0 works with the companion change.
  • CVE-2026-25645 check: with a planted cacert.pem in the temp directory, extract_zipped_paths() returns a new file with the zip's content, not the planted file, and a different path on every call.
  • Network: pip download six==1.16.0 and a pip install --dry-run requests==2.32.3 resolution against PyPI both work.
  • Diff from the 26.0.1.post2 release asset: the wheel differs only in pip/__init__.py, vendor.txt, requests/utils.py and five urllib3 files, plus RECORD and the metadata version. The urllib3 files in the wheel are identical to the vendored tree in this PR.

After merge: tag 26.0.1.post3 and publish the release. Then bundle it in CPython 3.9.25.1 and 3.10.22.1 (ActiveState/cpython#110).

…E-2026-25645

Review follow-up from ActiveState/cpython#110.  pip 26.0.1 vendors
urllib3 1.26.20, which is open to CVE-2025-66418, CVE-2025-66471,
CVE-2026-21441, CVE-2026-44431, CVE-2026-97687, CVE-2026-97689 and
CVE-2025-50181.  Replace it with ActiveState's urllib3 1.26.20.post4,
which fixes all seven (ports of the upstream 2.x fixes to the 1.26
line).  The vendored copy is the fork's src/urllib3 from the
1.26.20.post4 sdist with pip's existing vendoring diff replayed on top
(pip._vendor import paths, Brotli disabled, LICENSE.txt); every hunk
applied cleanly, and the fork's new Brotli code is all behind
"if brotli is not None".  urllib3.__version__ stays "1.26.20", so
requests' version check is unaffected.

Also backport the requests 2.33.0 fix for CVE-2026-25645
(GHSA-gc5v-m9x4-r6x2, psf/requests 66d21cb07b) to the vendored requests
2.32.5: extract_zipped_paths() extracted to a predictable name in the
shared temp directory and reused an existing file.  pip's vendored
requests calls it from HTTPAdapter.cert_verify() for the default CA
bundle, which matters when pip runs from a zip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@ezequielp-activestate ezequielp-activestate left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers.

  • Vendored urllib3: I replayed pip's own vendoring diff (upstream urllib3 1.26.20 against the vendored copy on 26.0.1.x) onto src/urllib3 from the ActiveState urllib3 1.26.20.post4 release sdist. All 8 hunks apply cleanly, and the result is byte-identical to src/pip/_vendor/urllib3 in this PR. LICENSE.txt is unchanged. The five files that differ from the base are exactly the five post4 changes relative to upstream 1.26.20.
  • requests CVE-2026-25645: the extract_zipped_paths() change is identical to upstream psf/requests 66d21cb07b, and upstream hasn't changed that function since.
  • Through pip on Python 3.9: pip reports 26.0.1.post3, and importing it under -W error raises no RequestsDependencyWarning. The vendored urllib3 refuses a 6-link Content-Encoding chain ('Too many content encodings in the chain: 6 > 5'), and a 64 MiB deflate bomb through stream(65536) yields pieces of at most 65,536 bytes. pip download six==1.16.0 works.

⚪ Two small notes:

  • The description overstates the zip case. It says the requests fix matters when pip runs from a zip, but on 3.9 that path isn't reached. Run from a zip, pip's vendored certifi extracts cacert.pem to a real file itself, so extract_zipped_paths() returns early. Three HTTPAdapter.cert_verify() calls created no temp files. The fix is still correct to carry.
  • Optional companion commit. Upstream's companion commit e331a288f3 (also in 2.33.0) drops that extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH) call from HTTPAdapter.cert_verify (adapters.py:300 here). Porting it would remove the path entirely, but nothing reaches it today.

Review follow-up (#15).  Port upstream's companion commit to the
CVE-2026-25645 fix, psf/requests e331a288f3 (pypa#7292, also in 2.33.0):
HTTPAdapter.cert_verify() uses DEFAULT_CA_BUNDLE_PATH directly instead
of passing it through extract_zipped_paths(), since certifi already
extracts its bundle to a real file.

The NEWS entry said the fix mattered when pip runs from a zip.  On this
line it does not: pip's vendored certifi extracts cacert.pem itself, so
extract_zipped_paths() returned early and pip never reached the
vulnerable path.  The entry now says so; the fix is defense in depth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@icanhasmath

Copy link
Copy Markdown
Author

Both notes are addressed in 9dac45c.

  • Zip case: you're right, pip never reached the path. The NEWS entry and the PR description now say the vendored certifi already extracts its bundle, so the fix is defense in depth.
  • Companion commit: I ported e331a288f3. cert_verify() now uses DEFAULT_CA_BUNDLE_PATH directly, with upstream's comment in utils.py. Nothing else in the vendored code calls extract_zipped_paths().
  • Re-checked in python:3.9-slim and python:3.10-slim: versions, no RequestsDependencyWarning, the extract_zipped_paths() planted-file check, pip download, and a --dry-run resolution all pass. python pip-26.0.1.post3-py3-none-any.whl/pip download six==1.16.0 (pip running from the zip) also works.

@ezequielp-activestate ezequielp-activestate left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of 9dac45c: still approved. Both notes are addressed:

  • e331a288f3: the adapters.py and utils.py changes are identical to upstream's. cert_verify() now uses DEFAULT_CA_BUNDLE_PATH directly. extract_zipped_paths() is still defined, but nothing in pip calls it any more.
  • NEWS: the entry now describes the requests fix as defense in depth, which matches what pip actually reaches.

Running pip from a zip of this tree, on Python 3.9 and 3.10, under -W error: pip 26.0.1.post3 imports with vendored urllib3 1.26.20 (the fork) and requests 2.32.5. DEFAULT_CA_BUNDLE_PATH is the real temp file certifi extracts, and pip download six==1.16.0 works.

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.

2 participants