client: apply custom CA handling to websocket TLS#14240
Merged
joshka-oai merged 1 commit intojoshka/login-custom-ca-shared-clientsfrom Mar 12, 2026
Merged
Conversation
This was referenced Mar 10, 2026
5e3eb1c to
6c2de29
Compare
2fbc0a5 to
7703ac6
Compare
6c2de29 to
18917a9
Compare
7703ac6 to
002245e
Compare
18917a9 to
9490701
Compare
xl-openai
approved these changes
Mar 12, 2026
Use the shared custom CA loader to build explicit rustls configs for the responses and realtime websocket clients, so secure websocket connections honor CODEX_CA_CERTIFICATE / SSL_CERT_FILE alongside the existing reqwest paths. Co-authored-by: Codex <noreply@openai.com>
9490701 to
e6446a9
Compare
002245e to
017a0e7
Compare
b5f3e63
into
joshka/login-custom-ca-shared-clients
16 of 20 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked PRs
This work is split across three stacked PRs:
reqwestclients across CodexReview order: #14178, then #14239, then #14240.
Builds on top of #14239, which itself builds on #14178.
Problem
The shared custom-CA path covered
reqwestclients after #14239, but secure websocket connections still used tungstenite's default TLS connector. In enterprise MITM setups, that meant HTTPS requests could succeed while websocket connections still failed because they were not loading the same custom root CA bundle.What This Delivers
Secure websocket connections now honor the same custom CA configuration as the HTTPS clients introduced in the earlier stacked PRs. After this lands, setups that already work for HTTPS behind an intercepting proxy can also work for websocket-backed features instead of failing later during websocket TLS setup.
For users and operators, the configuration does not change:
CODEX_CA_CERTIFICATEwins, thenSSL_CERT_FILE, then system roots. The difference is that websocket TLS now participates in that same policy.Mental model
There is now one shared custom-CA policy for both HTTPS and secure websocket connections.
reqwestcallers continue to usebuild_reqwest_client_with_custom_ca(...).Websocket callers now ask
codex-clientfor a rustls client config when a custom CA bundle is configured, then pass that config into tungstenite explicitly.The env precedence remains the same:
CODEX_CA_CERTIFICATEwinsSSL_CERT_FILENon-goals
This does not add a live end-to-end TLS interception test. It does not change the fallback behavior when no custom CA env var is set. It also does not try to generalize all websocket transport concerns into
codex-client; it only extends the shared CA-loading policy to the websocket TLS boundary.Tradeoffs
The main tradeoff is that websocket callers now have an explicit shared rustls-config path only when a custom CA bundle is configured. That keeps the normal no-override path simple, but it means the websocket implementation still has two TLS setup paths: default connector when no override is set, explicit connector when it is.
This PR also renames the shared CA error type to match reality. The error is no longer reqwest-specific, because the same CA-loading failures now surface from websocket TLS configuration too.
Architecture
codex-client::custom_canow exposes an optional rustls client-config builder alongside the existing reqwest client builder.The websocket clients in
codex-apinow consume that shared config:When no custom CA env var is set, websocket callers still use their ordinary default connector path. When a custom CA bundle is configured, they build an explicit rustls connector with the same roots that the shared HTTPS path uses.
Observability
The websocket path now inherits the same CA-loading logs and user-facing errors as the shared HTTPS path. Failures to read, parse, or register custom CA certificates are surfaced before websocket TLS is attempted, instead of failing later as an opaque certificate-validation problem.
Tests
This PR relies on the existing
codex-clientCA tests plus the websocket-focused suites incodex-apiandcodex-core.Automated coverage run for this stack:
cargo test -p codex-client -p codex-apicargo test -p codex-core websocket_fallback -- --nocaptureManual validation:
CODEX_CA_CERTIFICATE=~/.mitmproxy/mitmproxy-ca-cert.pem HTTPS_PROXY=http://127.0.0.1:8080 just codexmitmproxyinstalled viabrew install mitmproxyThat manual check was specifically useful because it exercises the websocket path behind a real intercepting proxy instead of only the reqwest HTTPS path.