Bounded offline regression · 05 October 2026
CVE-2026-97689
Before the body,
the parser reads a line.
A small body read starts with a chunk-size line. In urllib3 2.7.0, that earlier read had no length limit. The 2.8.0 fix puts a cap on it.
Published upstream issue · 32 local cases passed · Application impact untested
01 / The boundary
The chunk-size line arrives before the chunk body.
The line read changed.
The maintainer advisory describes excessive memory use while buffering chunk-size lines. The patch bounds both size and trailer lines.
In the local fixtures, 2.7.0 calls readline() without a limit. Version 2.8.0 requests at most 65,537 bytes and rejects a line longer than 65,536 bytes. The extra byte detects a line over the limit; the count includes its CRLF.
Counted file reads verify rejection before body delivery. No memory-exhaustion run.
02 / Versions
The September fix comes after the earlier redirect fix.
Which versions changed?
| Affected range | ≥ 1.10.3 and < 2.8.0 |
|---|---|
| First fixed release | 2.8.0 |
| Advisory published | 15 September 2026 |
| Upstream weakness | CWE-770 / resource allocation without limits |
The earlier redirect study compared 2.6.3 and 2.7.0 for CVE-2026-44431. Version 2.7.0 fixed that header issue and remains affected by this streaming issue.
Upgrade to a supported release containing the 2.8.0 fix. Check dependency constraints and the release notes for other fixes and compatibility changes.
03 / Local evidence
Eight fixtures through each streaming API, on each release.
The boundary holds in the fixed release.
32 cases exercise the unmodified wheels’ stream() and read_chunked() methods over counted in-memory files. PyPI hashes identify each wheel; its response.py bytes match the pinned upstream release commit.
| Fixture | 2.7.0 | 2.8.0 |
|---|---|---|
| Ordinary three-byte body | Accepted | Accepted |
| 65,535 / 65,536-byte size line | Accepted | Accepted |
| 65,537 / 65,538-byte size line | Accepted | Rejected before any body read |
| 65,536-byte trailer line | Accepted | Accepted |
| 65,538-byte trailer line | Accepted | Rejected after the body was yielded |
| 65,537-byte chunk body | Accepted | Accepted in 1,024-byte reads |
The two-byte-over size fixture shows an earlier stop: 2.7.0 returns all 65,538 line bytes, while 2.8.0 returns 65,537 and closes the response. The maximum fixture input is 65,551 bytes.
What this establishes
The fixed framing cap and rejection order work in these controlled cases. Body size has a separate budget. Trailer validation happens later, so an exception can follow body bytes already delivered to a caller.
How the checks stay local
Each wheel runs in a separate Python process with site packages disabled. A Python audit guard blocks socket creation and DNS. Case-time network attempts are zero. urllib3’s import-time IPv6 socket probe is also blocked and recorded separately.
What is still untested?
Memory exhaustion, real network buffering, TLS, the requests wrapper, decompression, and application exploitability. This is a small parser regression; it does not measure process RSS or reproduce a remote attack.
Read all observations ↓ · Run the checks ↗ · Afterimage source ↗
Upstream credit: pquentin, coordinator; illia-v, remediation reviewer. Independent regression and presentation by Sergio Rodriguez, developed with AI assistance.
04 / Primary sources
Checked 05 October 2026.
Advisory, history, and release identity.
- Maintainer advisory / CVE-2026-97689 ↗Scope, impact and original attribution
- urllib3 2.7.0 / pinned response.py ↗Affected implementation
- urllib3 2.8.0 / pinned response.py ↗Fixed implementation and length checks
- Upstream fix commit ↗Implementation and upstream test changes
- urllib3 2.8.0 release notes ↗Release context and compatibility notes
- Exact release identities ↓PyPI URLs, hashes, tag objects and commits
- Review record ↓Source facts, regression scope and limits