← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2026-1502 · CWE-93 · Disclosed 2026-04-10

HTTP client proxy tunnel headers not validated for CR/LF

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
01 · The Real Story

Like a bad label maker on an internal mail chute, this only matters if you already let strangers write the label

CVE-2026-1502 is a CR/LF injection flaw in CPython's http.client CONNECT-tunneling path. Before the April 2026 fixes, HTTPConnection.set_tunnel() could accept tunnel host or tunnel header values containing carriage-return/line-feed characters, letting malformed data alter the CONNECT request sent to an upstream HTTP proxy. Upstream references show fixes landing in current branches including Python 3.13.13 and 3.14.4, with security backports also queued/applied for 3.10/3.11/3.12 branches; the unfixed development line was effectively pre-3.15.0.

The vendor's MEDIUM 5.7 is technically defensible in a lab, but it is too hot for enterprise patch triage. Real exploitation requires an application to use Python's proxy tunneling feature, pass attacker-influenced values into set_tunnel(), and route through a proxy where modified CONNECT headers actually change behavior. That's a narrow, post-design-mistake chain with tiny exposed population, no KEV listing, no public in-the-wild evidence, and a near-zero EPSS.

"This is a niche proxy-header injection bug, not a fleet-wide fire drill."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Gain control of tunnel input

The attacker needs influence over data that an application feeds into http.client.HTTPConnection.set_tunnel() as the tunnel host or custom tunnel headers. In practice this usually means a higher-level app already accepts untrusted proxy configuration, destination metadata, or header content and forwards it into Python's stdlib client. The "weapon" here is just the application's own use of http.client.
Conditions required:
  • Application uses CPython's http.client directly or through a wrapper
  • Application calls set_tunnel() for CONNECT proxying
  • Attacker can control or significantly influence tunnel host or tunnel header values
Where this breaks in practice:
  • Most enterprise Python apps never call set_tunnel() directly
  • Many apps hardcode proxy settings rather than taking them from user input
  • This prerequisite already implies a design bug or unsafe trust boundary in the calling application
Detection/coverage: Generic vuln scanners can find Python versions, but they usually cannot determine whether the vulnerable set_tunnel() path is reachable with attacker-controlled input.
STEP 02

Trigger malformed CONNECT request

On an affected runtime, CR/LF bytes are not rejected before building the CONNECT request. A tester can reproduce this with ordinary tooling like python plus a local intercepting proxy, or by adapting the upstream issue/patch test cases from GitHub. The effective technique is header injection / request splitting into the proxy-facing CONNECT transaction.
Conditions required:
  • Affected CPython build is running
  • Attacker-supplied value reaches set_tunnel() unsanitized
  • Application attempts a proxied outbound connection
Where this breaks in practice:
  • Many frameworks do not expose this exact stdlib path
  • Some wrappers or app-side validation may strip control characters first
  • Not every malformed CONNECT line meaningfully changes proxy behavior
Detection/coverage: Code review, SAST, or custom grep on set_tunnel( usage is more useful than perimeter scanning. Network IDS visibility is poor unless you inspect outbound proxy traffic.
STEP 03

Abuse proxy-side interpretation

If the upstream proxy accepts the injected bytes as new headers or split lines, the attacker may alter what the proxy sees in the CONNECT negotiation. The most plausible impact is integrity-only manipulation of the proxy request context, not code execution on the host. Think policy bypass, malformed proxy auth context, or altered downstream request framing depending on proxy behavior.
Conditions required:
  • Outbound traffic traverses an HTTP proxy that processes CONNECT headers
  • That proxy interprets the injected CR/LF in a useful way
  • The application or environment relies on proxy-side policy decisions
Where this breaks in practice:
  • Modern proxies often normalize or reject malformed CONNECT traffic
  • Many enterprise egress stacks log or block oddball proxy requests
  • Impact varies heavily by proxy implementation and local policy
Detection/coverage: Proxy logs are the best telemetry source: look for CONNECT requests with malformed header structure, unusual extra headers, or repeated 4xx failures from specific clients.
STEP 04

Land limited integrity impact

Successful exploitation can tamper with the semantics of a proxied outbound request, but the blast radius usually stays tied to the calling application and its outbound session. There is no evidence in the public record of broad internet-scale weaponization, wormability, or turnkey RCE chaining from this bug alone.
Conditions required:
  • Prior steps succeeded
  • The target environment depends on the proxied transaction for a sensitive workflow
Where this breaks in practice:
  • Impact is bounded to a narrow feature path
  • No confidentiality or availability impact is claimed by the CNA vector
  • No public exploitation campaigns or KEV inclusion found
Detection/coverage: Expect little to no commercial scanner certainty on actual exploitability. Incident review would hinge on app logs plus outbound proxy telemetry.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo public in-the-wild exploitation found in vendor advisory, NVD, or CISA KEV checks. Inference: this currently looks like a low-traction bug, not an active campaign item.
Public exploit / PoCNo standalone weaponized PoC repo surfaced in public search. However, the upstream issue, PR, and tests make reproduction straightforward for anyone comfortable with Python and a proxy.
EPSS0.00024 from the user-supplied intel, which is effectively noise-floor risk. That lines up with the weak real-world exposure story.
KEV statusNot listed in the CISA KEV catalog when checked.
CVSS / vendor baselinePython CNA scored it 5.7 MEDIUM with CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N. The important part is the built-in friction: high privileges + user interaction + integrity-only impact.
Weakness classNVD now maps this to CWE-93, CRLF injection.
Affected versionsAffected code sits in CPython's stdlib http.client tunnel handling. Public records indicate exposure on the active line before 3.15.0 and released branches before the April 2026 fixes, including pre-3.13.13 and pre-3.14.4.
Fixed versions / backportsConfirmed public fixed releases include Python 3.13.13 and 3.14.4 dated 2026-04-07. Upstream PR metadata also shows backport work for 3.12, 3.11, and 3.10 branches; FreeBSD and distro trackers subsequently carried the issue.
Exposure / scanning realityThere is no clean internet-wide fingerprint for "apps that call set_tunnel() with attacker-controlled data." Shodan/Censys/FOFA-style counts are mostly useless here because this is a library usage bug, not a directly bannered service vulnerability.
Disclosure / creditsPublished 2026-04-10 via Python security announce and MITRE CNA record. Public credit in aggregators and references points to senseicat and Seth Larson.
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to LOW (2.9/10)

The decisive downgrade factor is reachability: exploitation requires a very specific application behavior chain around HTTPConnection.set_tunnel() and attacker-controlled proxy-tunnel metadata. This is not an unauthenticated internet-edge bug in Python itself; it is a narrow misuse-sensitive feature flaw with limited blast radius and no active exploitation signal.

HIGH Vendor/product identification and root-cause mechanics
MEDIUM Patched branch mapping across legacy 3.10/3.11/3.12 releases
HIGH Real-world downgrade based on attack-path friction

Why this verdict

  • Attacker position is already constrained: this is not unauthenticated remote-to-RCE; the CNA vector itself says PR:H and UI:P, and real abuse requires control over values fed into set_tunnel().
  • Exposure population collapses fast: only apps using Python's CONNECT proxy tunneling are in scope, and only a subset of those let untrusted input reach tunnel host or headers.
  • Modern controls should catch adjacent mistakes: secure coding review, SAST, outbound proxy validation, and egress monitoring all reduce exploitability long before this becomes an estate-wide incident.
  • Blast radius is narrow: impact is integrity manipulation of a proxied request path, not host takeover, credential dumping, or remote wormability.
  • Threat intel is cold: no KEV, no public campaign reporting, no mass-scanning signal, and an EPSS of 0.00024 all push this down.

Why not higher?

To justify MEDIUM or HIGH in a 10,000-host environment, you would want broader reachability, simpler attacker position, or active exploitation evidence. This flaw instead stacks multiple prerequisites: specific stdlib feature use, attacker-controlled input, CONNECT proxying, and favorable proxy-side parsing. That is too much compounding friction for a higher queue position.

Why not lower?

It is not pure non-issue territory because the bug is real, fixed upstream, and can alter request integrity in environments that depend on CONNECT proxies. If you run Python services that broker outbound traffic or let tenants influence proxy destinations or headers, there is a legitimate though narrow abuse case. That keeps it above IGNORE.

05 · Compensating Control

What to do — in priority order.

  1. Audit set_tunnel() usage — Search application code and dependencies for HTTPConnection.set_tunnel( and wrappers around http.client; this identifies the tiny subset of hosts where the CVE is even reachable. For a LOW verdict there is no fixed mitigation deadline, so do this as backlog hygiene during normal engineering cycles.
  2. Block control characters at app boundaries — Reject \r and \n in proxy hostnames, CONNECT metadata, and any custom header values before they reach stdlib networking calls. This is the right temporary guard if you cannot patch immediately; for LOW, deploy during normal code-hardening work rather than emergency change windows.
  3. Harden outbound proxy policy — Configure egress proxies to reject malformed CONNECT requests and unexpected custom CONNECT headers where possible. That cuts off the downstream interpreter-dependent impact and is useful regardless of runtime version.
  4. Log CONNECT anomalies — Turn on or retain outbound proxy logging for malformed CONNECT attempts, repeated 4xx responses, or unexpected header patterns from Python application tiers. This gives you the only realistic detection path if someone probes the edge cases.
What doesn't work
  • A WAF does little here because the dangerous traffic is usually server-side outbound CONNECT traffic, not inbound web requests at your perimeter.
  • Host EDR will not reliably flag this; the exploit primitive is malformed proxy request construction inside a legitimate Python process, not malware execution.
  • Perimeter asset scanners cannot tell you whether an app actually passes attacker-controlled values into set_tunnel(), so version-only scanning overstates practical risk.
06 · Verification

Crowdsourced verification payload.

Run this on the target host using the same Python interpreter your application uses, or from your software inventory pipeline against each discovered python binary. Invoke it as python3 check_cve_2026_1502.py or C:\Python313\python.exe check_cve_2026_1502.py; no admin rights are required, but you must run the specific interpreter instance you want to assess.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
# CVE-2026-1502 verifier for CPython http.client CONNECT tunnel CR/LF validation
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

import inspect
import re
import sys

STATUS_PATCHED = 0
STATUS_VULNERABLE = 1
STATUS_UNKNOWN = 2


def out(msg):
    sys.stdout.write(msg + "\n")


def main():
    try:
        import http.client as hc
    except Exception as e:
        out(f"UNKNOWN: failed to import http.client: {e}")
        return STATUS_UNKNOWN

    ver = sys.version.split()[0]

    try:
        src = inspect.getsource(hc.HTTPConnection._tunnel)
    except Exception as e:
        out(f"UNKNOWN: could not inspect http.client.HTTPConnection._tunnel on Python {ver}: {e}")
        return STATUS_UNKNOWN

    # Heuristic 1: patched code added explicit tunnel-host validation and header name/value validation.
    checks = [
        "_contains_disallowed_url_pchar_re.search(self._tunnel_host)",
        "_is_legal_header_name",
        "_is_illegal_header_value",
        "Invalid header name",
        "Invalid header value",
        "Tunnel host can't contain control characters",
    ]

    found = [c for c in checks if c in src]

    # Heuristic 2: behavior test. Vulnerable versions typically accept CR/LF into tunnel headers until send time.
    behavior_result = None
    try:
        conn = hc.HTTPConnection("example.com")
        conn.set_tunnel("example.org", headers={"X-Test": "ok\r\nInjected: yes"})
        # If we got here without ValueError, that's a strong vulnerable signal.
        behavior_result = "accepted_bad_header"
    except ValueError:
        behavior_result = "rejected_bad_header"
    except TypeError:
        behavior_result = "rejected_bad_header"
    except Exception:
        # Unexpected exception; source-based result will decide.
        behavior_result = "indeterminate"

    if len(found) >= 4 and behavior_result == "rejected_bad_header":
        out(f"PATCHED: Python {ver} contains tunnel host/header validation consistent with CVE-2026-1502 fix")
        return STATUS_PATCHED

    if len(found) <= 1 and behavior_result == "accepted_bad_header":
        out(f"VULNERABLE: Python {ver} accepted CR/LF in set_tunnel() header data and lacks fix markers")
        return STATUS_VULNERABLE

    # Mixed case: report what we saw so operators can decide.
    out(
        "UNKNOWN: inconclusive result for Python {} (fix markers found: {}; behavior: {})".format(
            ver, len(found), behavior_result
        )
    )
    return STATUS_UNKNOWN


if __name__ == "__main__":
    sys.exit(main())
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning, do not burn an emergency patch window on this unless you already know you have Python apps doing attacker-influenced CONNECT proxying. First, inventory Python runtimes and grep code for set_tunnel( in services that broker outbound traffic; if you find real reachability, add CR/LF input rejection and patch those runtimes in the next normal maintenance cycle. For a LOW noisgate rating there is no noisgate mitigation SLA and no noisgate remediation SLA beyond backlog hygiene—treat it as routine runtime cleanup, not a stop-the-line event.

Sources

  1. Python security announcement
  2. Upstream issue gh-146211
  3. Upstream fix commit 05ed7ce
  4. NVD record for CVE-2026-1502
  5. Python 3.13.13 release page
  6. Python 3.14.4 release page
  7. CISA Known Exploited Vulnerabilities catalog
  8. FreeBSD VuXML entry
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.