Chat & WebSocket runtime have no TLS-bypass escape hatch — PR #20676 only fixed login (enterprise MITM still blocked)

Open 💬 1 comment Opened May 23, 2026 by liyoungc

Summary

PR #20676 (commit 9e90552, 2026-05-02) added SSL_CERT_FILE / CODEX_CA_CERTIFICATE detection to the login flow's reqwest client (codex-client/src/custom_ca.rs::build_reqwest_client_with_env). This partially addressed #6849.

The chat runtime and WebSocket transport are still broken behind any TLS-inspecting proxy whose MITM CA is rejected by rustls (modern signature/key policy):

  • WebSocket (tokio-tungstenite with rustls-tls-native-roots feature) is hardcoded to rustls — it has no env-var / config / flag escape hatch.
  • HTTPS fallback (chat path) also appears to use rustls without honoring the login-flow CA logic.

Net effect: codex login succeeds; codex chat dies on every prompt with invalid peer certificate: BadSignature and stream disconnected before completion.

Environment

  • codex-cli 0.132.0 (also reproduced on 0.130, 0.131, 0.133)
  • Windows 11 (x86_64)
  • Hospital LAN with two-tier SSL inspection:
  • "AI hosts" (chatgpt.com, *.openai.azure.com, api.anthropic.com, Bedrock, Vertex etc.) → MITM with CN=GlobalTrust Root CA (2002 self-signed, SHA1WithRSAEncryption, RSA-1024)
  • "Legacy" hosts (api.openai.com, github.com) → MITM with CN=FG200ETK18915851, O=Fortinet (SHA256, RSA-2048)
  • Result: api.openai.com works in codex (FortiGate SHA256 chain passes rustls). chatgpt.com (ChatGPT auth path) and Azure / Anthropic / etc. all fail.

Reproduction

codex doctor
...
⚠ websocket    Responses WebSocket failed; HTTPS fallback may still work
    endpoint                 wss://chatgpt.com/backend-api/<redacted>
    handshake transport error network error: invalid peer certificate: BadSignature

In chat:
⚠ Falling back from WebSockets to HTTPS transport.
stream disconnected before completion: invalid peer certificate: BadSignature
■ stream disconnected before completion: error sending request for url (https://chatgpt.com/backend-api/codex/responses)

Evidence — every modern TLS library rejects this same chain

Same cert chain, three different rejection paths (proves no single workaround works):

| TLS stack | Verdict | Reason |
|---|---|---|
| rustls (codex) | ❌ BadSignature | RFC 9155 — SHA1 signatures hard-deprecated |
| schannel (curl.exe on Windows) | ❌ CRYPT_E_NO_REVOCATION_CHECK (0x80092012) | Hospital MITM CA has no reachable CRL/OCSP endpoint |
| OpenSSL 3.x | ❌ verify error num=66 EE certificate key too weak + num=67 CA certificate key too weak | Security level 2 rejects RSA-1024 |
| Node 22 OpenSSL with NODE_TLS_REJECT_UNAUTHORIZED=0 | ✅ Works | Validation bypassed — only path that succeeds |

The Node escape hatch (NODE_TLS_REJECT_UNAUTHORIZED=0) is the only reason claude-code and other Node-based clients work in this environment. As #6849 comment put it bluntly: "I can easily use claude simply with NODE_TLS_REJECT_UNAUTHORIZED=0, whereas with codex I'm simply blocked".

What PR #20676 doesn't cover

The login flow's build_reqwest_client_with_env only wires the custom-CA path for codex-client (auth/login). But:

  1. The runtime chat client (whatever builds reqwest::Client for chatgpt.com/backend-api/codex/responses calls) does not read SSL_CERT_FILE / CODEX_CA_CERTIFICATE the same way. With those env vars set per #20676, login succeeds but chat still rejects.
  2. The WebSocket transport uses tokio-tungstenite with the rustls-tls-native-roots feature — Cargo.toml declares this with no opt-out and the wss connect path has no config hook for an accept-invalid-certs flag or alternate TLS backend.

Even if a user sets SSL_CERT_FILE to a bundle containing the corporate MITM root, rustls still rejects because the chain itself uses SHA1 / RSA-1024 / unreachable revocation — adding the CA to the bundle doesn't override those algorithm policies. This is a class of corporate MITM (FortiGate, certain Trend Micro / Palo Alto deployments etc.) where the root cert was generated decades ago and can't be cycled by IT in any short timeframe.

Requested change — provide an explicit escape hatch

At least one of the following so enterprise users aren't locked out:

  1. CODEX_INSECURE_TLS=1 env var that maps to reqwest::ClientBuilder::danger_accept_invalid_certs(true) for every codex reqwest client (auth, chat, telemetry) and to tokio_tungstenite::Connector::NativeTls or equivalent for the WebSocket transport. Loud warning on startup when enabled, like Node logs on NODE_TLS_REJECT_UNAUTHORIZED=0.
  2. A --tls-backend=native flag / config that switches reqwest + tungstenite to native-tls (schannel / Security.framework / OpenSSL) instead of rustls. schannel and OpenSSL legacy mode accept SHA1; native-tls path was sufficient for codex ≤ 0.118 (when the dep tree had this fallback) and there's no policy reason it should be impossible now.
  3. Extend PR #20676's build_reqwest_client_with_env so the same SSL_CERT_FILE / CODEX_CA_CERTIFICATE detection is honored by the runtime chat reqwest client and is allowed to also disable rustls's signature-algorithm policy when the user explicitly opts in (e.g. CODEX_CA_CERTIFICATE_ALLOW_LEGACY=1).

A trust-store escape hatch behind an explicit env var matches what every other major HTTP client offers (NODE_TLS_REJECT_UNAUTHORIZED, curl -k, git -c http.sslVerify=false, pip --trusted-host). The current "no escape hatch" stance effectively locks out healthcare / financial / government users who cannot change their corporate MITM.

Workarounds users currently rely on

  • Run codex from outside the corporate network (home WiFi / cellular tether) — useless for daily work
  • Tailscale / VPN exit nodes — adds latency + corporate policy concerns
  • Use a different agent (Claude Code via Anthropic SDK in Node, OpenCode, aider) — works fine
  • Switch to OpenAI Platform API key + api.openai.com instead of ChatGPT auth + chatgpt.com — works if the corporate MITM happens to use a stronger cert chain for that host (FortiGate SHA256 in our case), but unreliable in general

Related

  • #6849 — login-only version of this issue, fixed by PR #20676 for the auth path
  • PR #20676 / commit 9e90552 — partial fix (login only)
  • codex-rs/codex-client/src/custom_ca.rs::build_reqwest_client_with_env — the existing pattern that should be extended

Happy to provide additional probe data (cert dumps, doctor output, hospital network probe matrix across 18 LLM hosts) if it helps.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗