From 758c4f834249563d27615737eee6a6d0d5e1d850 Mon Sep 17 00:00:00 2001 From: Mudwood Labs Date: Thu, 3 Sep 2026 00:05:26 -0700 Subject: [PATCH] chore: floor urllib3 in the [eth] extra MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pyrxd does not import urllib3. It arrives two levels down — web3 requires requests>=2.23.0, which requires urllib3<3,>=1.26 — so `pip install pyrxd[eth]` pulls it whether we mention it or not. urllib3 below 2.7.0 carries two advisories: PYSEC-2026-141, where a proxied cross-origin redirect forwards sensitive headers, and PYSEC-2026-142, a decompression bomb via Brotli streaming or drain_conn. HYGIENE, NOT A FIX, and the comment in pyproject.toml says so. Measured on 2026-09-03: brotli is not installed, `drain_conn` appears in 0 files across web3/requests, and web3 uses `stream=True` at 0 sites — so the decompression issue is unreachable. The header leak's vulnerable call DOES exist (requests/adapters.py:651 passes assert_same_host=False through a ProxyManager), but reaching it needs a configured proxy AND a hostile redirect from the RPC endpoint, and no exploit was demonstrated. By this repo's review rule that makes it a path, not a finding. The floor is here so a consumer's own pip-audit is not handed a finding by our extra. Owning a constraint on a package we do not import has a real cost — it can conflict with a consumer's tree — so it is the only such floor, and the comment says to remove it once requests floors urllib3 itself. Compatible with requests' own range: 2.7.x satisfies both <3,>=1.26 and >=2.7.0. Found by pip-audit during the security review. Worth noting the triage nearly went wrong: the first dependency-closure walk parsed "urllib3>=1.21.1,<3" with `.split()[0]`, kept the version specifier as part of the name, matched nothing, and concluded the package was unreachable. The chain is real. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 13 +++++++++++++ pyproject.toml | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b350a295..833aab10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Security + +- **`pyrxd[eth]` now floors `urllib3>=2.7.0`.** pyrxd does not import urllib3; it arrives two levels + down (`web3` -> `requests` -> `urllib3<3,>=1.26`), so installing the extra pulls it either way. + Versions below 2.7.0 carry PYSEC-2026-141 (a proxied cross-origin redirect forwards sensitive + headers) and PYSEC-2026-142 (a decompression bomb via Brotli streaming or `drain_conn`). + + **Hygiene, not a fix — neither is reachable through pyrxd's usage**, measured: brotli is not + installed, `drain_conn` appears in 0 files of web3/requests, and web3 uses `stream=True` at 0 + sites. The header leak needs both a configured proxy and a hostile redirect from the RPC endpoint. + The floor exists so a consumer's own `pip-audit` is not handed a finding by our extra, and should + be removed once `requests` floors urllib3 itself. + ### Fixed - **Radiant-chain reserves were converted with the BITCOIN interval at seven sites (#579).** diff --git a/pyproject.toml b/pyproject.toml index 686e0501..89cd57b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,10 +74,28 @@ dependencies = [ # DIRECTLY, so leaving it implicit rests on two levels of transitive luck. # # aiohttp is deliberately absent: it is already a runtime dependency, not an eth-only one. +# +# urllib3 is different from the three above and the difference matters: pyrxd does NOT import it. +# It arrives two levels down — `web3` requires `requests>=2.23.0`, which requires +# `urllib3<3,>=1.26` — so installing this extra pulls it whether we mention it or not. +# +# HYGIENE, NOT A FIX. urllib3 < 2.7.0 carries PYSEC-2026-141 (a proxied cross-origin redirect +# forwards sensitive headers) and PYSEC-2026-142 (a decompression bomb via Brotli streaming or +# `drain_conn`). MEASURED as unreachable through pyrxd's usage on 2026-09-03: brotli is not +# installed, `drain_conn` appears in 0 files of web3/requests, and web3 uses `stream=True` at 0 +# sites. The header leak needs a proxy configured AND a hostile redirect from the RPC endpoint — +# the vulnerable call does exist (`requests/adapters.py:651` passes `assert_same_host=False` +# through a ProxyManager), so the path is real but the exploit was not demonstrated. +# +# The floor is here so `pip install pyrxd[eth]` does not hand a consumer a package their own +# `pip-audit` will flag. Owning a constraint on a package we do not import has a cost — it can +# conflict with a consumer's tree — so this is the only such floor, and it should be REMOVED once +# `requests` itself floors urllib3 above 2.7.0. eth = [ "web3>=7.16.0,<8.0.0", "eth-account>=0.14.0,<1.0.0", "eth-keys>=0.8.0,<1.0.0", + "urllib3>=2.7.0", ] [project.urls]