This is a secret falling out of a side door, not the front gate being blown open
CVE-2026-44431 is a header leakage bug in urllib3 affecting versions 1.23 through before 2.7.0. In the normal high-level request APIs, urllib3 strips sensitive headers like Authorization, Cookie, and Proxy-Authorization on cross-origin redirects. The flaw lives in a much narrower path: code that uses ProxyManager.connection_from_url().urlopen(..., assert_same_host=False) can still forward those headers to a different host after a redirect.
The raw technical issue is legitimate, but the real-world severity is below the headline score. This is not generic 'any urllib3 request leaks creds' behavior; it requires a specific low-level API, proxy-mediated request flow, and a cross-origin redirect chain. Vendor/NVD scoring treats the package as broadly reachable over the network, but defender reality is that only a small slice of Python apps will ever exercise this path.
4 steps from start to impact.
Land on a request path the app will fetch
30x to an attacker-controlled origin.- The target app performs outbound HTTP requests based on attacker-controlled or attacker-influenced input
- The request path is made through
urllib3rather than another HTTP client
- Many enterprise apps do not let untrusted users choose arbitrary outbound URLs
- Even where SSRF-like fetch features exist, egress filtering or allowlists often constrain destinations
Hit the vulnerable low-level proxy code path
urllib3.request() or PoolManager.request(); those already strip redirect-sensitive headers correctly. The attacker only wins if the application uses ProxyManager.connection_from_url().urlopen(..., assert_same_host=False), which is a comparatively uncommon low-level pattern.- The application uses
ProxyManager.connection_from_url() - The code follows redirects via
urlopen() assert_same_host=Falseis set
- This is a niche developer-facing API, not the common request path used by most Python applications
- Many codebases never disable same-host assertions for redirects
connection_from_url, urlopen, and assert_same_host=False tells you whether the dangerous path exists.Force a cross-origin redirect and collect headers
Authorization, Cookie, or Proxy-Authorization can be forwarded to the attacker endpoint.- Redirects are allowed
- The original request includes sensitive headers worth stealing
- The new redirect target is reachable from the application host
- Many requests won't carry reusable secrets at all
- Modern service-to-service designs often use scoped or short-lived tokens, reducing resale value
- Proxy auth headers may only matter in very specific network topologies
Replay the stolen credential
- The leaked header is valid beyond the original request
- The credential grants meaningful access if replayed
- Some cookies are origin-bound or short-lived
- Many bearer tokens are audience-scoped, tenant-scoped, or quickly rotated
- Leaking a header does not automatically produce host compromise
ProxyManager.connection_from_url, .urlopen(, and assert_same_host=False first. That tells you whether you have actual exploitability versus a merely affected package version; for a LOW verdict, do this as backlog hygiene with no SLA, before deciding how broad patching needs to be.ProxyManager.request() or other high-level helpers that already strip redirect-sensitive headers correctly. This is the cleanest compensating control when you cannot upgrade immediately; for LOW, treat it as engineering backlog work with no formal mitigation deadline.- A WAF does not solve this; the leak occurs in the application's outbound HTTP client behavior, not at the inbound web edge.
- Generic internet exposure scanning does not help much; this is a library-level flaw with no banner or port to fingerprint.
- Blindly upgrading every
urllib3instance without code-path triage may waste effort, because many vulnerable versions will never exercise the affected low-level proxied redirect flow.
The supporting signals.
| In-the-wild status | No public exploitation evidence found in the sources reviewed, and not listed in CISA KEV as of the public records tied to this CVE. |
|---|---|
| PoC availability | No standalone public exploit repo located during review. Reproduction is straightforward from the advisory because the vulnerable call chain is explicitly named. |
| EPSS | 0.00013 from the prompt — effectively noise-floor probability, which fits the narrow preconditions. |
| KEV status | Not KEV-listed. Disclosure/public record dates in the sources land on 2026-05-07 for GHSA publication and 2026-05-13 for CVE/NVD publication. |
| CVSS reality check | There is a scoring split: NVD shows CVSS v3.1 5.3 / MEDIUM (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N), while the GitHub CNA rates it CVSS v4 8.2 / HIGH because it models higher confidentiality impact once the niche path is reached. |
| Affected versions | urllib3 >=1.23, <2.7.0. |
| Fixed versions | Upstream fix is 2.7.0. Distros may backport separately; Debian's tracker still showed multiple suites as vulnerable/unfixed at review time, while Ubuntu OSV already tracks package-level updates for some releases. |
| Exposure and scanning | This is a library bug, not an internet-facing service fingerprint. Shodan/Censys/FOFA-style exposure counts are mostly irrelevant; the right lens is SBOM/SCA coverage plus code-path hunting. |
| Disclosure timeline | GitHub Advisory publication: 2026-05-07. NVD publication: 2026-05-13. PyPI shows urllib3 2.7.0 uploaded on 2026-05-07. |
| Reporter / originating org | The public advisory is published by urllib3 maintainer illia-v via GitHub Security Advisory. I found no separate external researcher attribution in the reviewed sources. |
Sources.
Why this verdict
- Baseline starts at 5.3 MEDIUM from NVD, but that assumes package-level reachability rather than how rarely enterprises hit this exact redirect path in production.
- Requires attacker influence over an outbound request. That implies the attacker must already control a URL fetch path, a redirecting upstream, or a similar SSRF-adjacent feature — not just send traffic to an exposed service.
- Requires a niche API chain. The common high-level APIs already strip the sensitive headers correctly; only
ProxyManager.connection_from_url().urlopen(..., assert_same_host=False)stays exposed, which sharply reduces the affected population. - Requires a proxy-mediated, cross-origin redirect with useful secrets present. Even after hitting the bug, the attacker still needs the request to carry reusable
Authorization,Cookie, orProxy-Authorizationmaterial. - Impact is confidentiality-only and usually scoped. This leaks headers; it does not directly execute code, bypass auth globally, or crash systems at scale.
Why not higher?
This is not a general remote exploit against every Python service using urllib3. The chain depends on a rare low-level usage pattern, explicit redirect behavior, and the presence of valuable headers on the request. Those are compounding friction points, not edge-case details. If this affected the default high-level request path, the verdict would jump materially.
Why not lower?
It still deserves tracking because the leaked artifacts can be high-value secrets: bearer tokens, session cookies, or proxy credentials. In shops that run heavy internal proxies or custom Python integrations, that can become a clean credential disclosure path. So this is not IGNORE; it is just well below vendor-style urgency.
Crowdsourced verification payload.
Run this on the target host or build image that has the Python application environment installed. Invoke it with python3 check_urllib3_cve_2026_44431.py from the relevant venv or container shell; no admin rights are required unless you need to inspect a system Python that only root can access. This script checks the installed urllib3 version only — it does not prove the app uses the vulnerable low-level API path.
#!/usr/bin/env python3
# CVE-2026-44431 urllib3 version check
# Exit codes:
# 0 = PATCHED / NOT AFFECTED
# 1 = VULNERABLE
# 2 = UNKNOWN
import re
import sys
try:
import urllib3 # type: ignore
except Exception as e:
print(f"UNKNOWN: urllib3 import failed: {e}")
sys.exit(2)
version = getattr(urllib3, "__version__", None)
path = getattr(urllib3, "__file__", "unknown")
if not version:
print("UNKNOWN: urllib3 imported but version could not be determined")
sys.exit(2)
def parse_semver(v: str):
# Extract leading numeric components like 2.6.3 from strings such as
# 2.6.3, 2.6.3-1ubuntu1, 1.26.12+deb12u3
m = re.match(r"^(\d+)\.(\d+)(?:\.(\d+))?", v)
if not m:
return None
major = int(m.group(1))
minor = int(m.group(2))
patch = int(m.group(3) or 0)
return (major, minor, patch)
cur = parse_semver(version)
if cur is None:
print(f"UNKNOWN: unrecognized urllib3 version format: {version} ({path})")
sys.exit(2)
lower = (1, 23, 0)
fixed = (2, 7, 0)
# Advisory says affected range is >=1.23, <2.7.0
if cur < lower:
print(f"PATCHED: urllib3 {version} is below the affected range (<1.23) at {path}")
sys.exit(0)
if cur >= fixed:
print(f"PATCHED: urllib3 {version} is at or above the upstream fixed version (>=2.7.0) at {path}")
sys.exit(0)
print(f"VULNERABLE: urllib3 {version} falls in the affected range (>=1.23,<2.7.0) at {path}")
print("NOTE: Exploitability still depends on application code using ProxyManager.connection_from_url().urlopen(..., assert_same_host=False) with cross-origin redirects.")
sys.exit(1)