CVE-2026-84381: httpcore2 Cleartext WebSocket Transmission via SOCKS5 Proxy
When a Python app using httpcore2 routes a secure WebSocket connection through a SOCKS5 proxy, TLS is never started, so credentials and messages are sent in plaintext to anyone watching or…

The problem
The SOCKS5 connection handlers in httpcore2 check whether to start TLS using a condition that only matches the https scheme. The wss scheme is not included, so after the SOCKS5 tunnel is established, the raw TCP stream is handed directly to the HTTP/1.1 upgrade machinery with no TLS handshake.
This means the WebSocket opening request, including URL query parameters, Authorization headers, cookies, and all subsequent frames, crosses the proxy path in plaintext. Because TLS never runs, certificate verification is also skipped entirely, so a malicious proxy can impersonate the destination server and read or modify traffic in both directions.
Proof of concept
A working proof-of-concept for CVE-2026-84381 in httpcore2, with the exact payload below.
# Affected code path in httpcore2 < 2.10.0 (_sync/socks_proxy.py and _async/socks_proxy.py):
# The TLS upgrade guard reads:
#
# if origin.scheme == "https": # <-- wss is never matched
# stream = await self._connect_tls(...)
#
# A caller triggering the vulnerable path:
import httpx2
with httpx2.Client(proxy="socks5://proxy.example:1080") as client:
with client.websocket(
"wss://service.example/private?token=query-secret",
headers={"Authorization": "Bearer header-secret"},
cookies={"session": "cookie-secret"},
) as ws:
ws.send_text("private message")
# The proxy tunnel sees this in plaintext (no TLS ClientHello ever sent):
# GET /private?token=query-secret HTTP/1.1
# Host: service.example
# Authorization: Bearer header-secret
# Cookie: session=cookie-secret
# Connection: Upgrade
# Upgrade: websocket
# ...The root cause (CWE-319) is a missing branch in the post-SOCKS5 TLS decision. Both the sync path (_sync/socks_proxy.py) and async path (_async/socks_proxy.py) use a guard of if origin.scheme == "https" to decide whether to wrap the tunnel in TLS. Because wss is a distinct scheme string, the condition is never true for WebSocket connections, and the raw stream is passed directly to the HTTP/1.1 layer.
The patch in PR #1104 (commit fb008dd) changes the guard to if origin.scheme in ("https", "wss") in both files, ensuring TLS and certificate verification happen for secure WebSocket origins just as they do for HTTPS.
The fix
Upgrade both httpcore2 and httpx2 to version 2.10.0 or later. The patched release adds wss to the TLS-upgrade check in both sync and async SOCKS5 connection paths. If an immediate upgrade is not possible, do not route wss:// connections through a SOCKS proxy; use a direct secure WebSocket connection instead.
Reported by Kludex.
Related research
- high · 7.4CVE-2026-84366CVE-2026-84366: Scrapy S3DownloadHandler Cleartext Transmission of AWS Credentials
- highatomic-agents-stack: HTTP MCP Catalog MITM to RCE via Cleartext Command Injection
- highCVE-2026-80206CVE-2026-80206: NLTK tgrep Regular Expression Denial of Service
- highCVE-2026-84452CVE-2026-84452: winml-cli CORS Wildcard Enables Localhost RCE via trust_remote_code