diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 8e2f788441..1ebe4938cd 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1266,13 +1266,13 @@ dependencies = [ "bytes", "codex-client", "codex-protocol", + "codex-utils-rustls-provider", "eventsource-stream", "futures", "http 1.4.0", "pretty_assertions", "regex-lite", "reqwest", - "rustls", "serde", "serde_json", "thiserror 2.0.18", @@ -1956,6 +1956,7 @@ dependencies = [ "async-trait", "clap", "codex-utils-absolute-path", + "codex-utils-rustls-provider", "globset", "pretty_assertions", "rama-core", @@ -2326,6 +2327,13 @@ dependencies = [ "tokio", ] +[[package]] +name = "codex-utils-rustls-provider" +version = "0.0.0" +dependencies = [ + "rustls", +] + [[package]] name = "codex-utils-string" version = "0.0.0" diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 7863446c43..4563aef9a3 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -46,6 +46,7 @@ members = [ "utils/home-dir", "utils/pty", "utils/readiness", + "utils/rustls-provider", "utils/string", "codex-client", "codex-api", @@ -111,6 +112,7 @@ codex-utils-json-to-toml = { path = "utils/json-to-toml" } codex-utils-home-dir = { path = "utils/home-dir" } codex-utils-pty = { path = "utils/pty" } codex-utils-readiness = { path = "utils/readiness" } +codex-utils-rustls-provider = { path = "utils/rustls-provider" } codex-utils-string = { path = "utils/string" } codex-windows-sandbox = { path = "windows-sandbox-rs" } core_test_support = { path = "core/tests/common" } diff --git a/codex-rs/codex-api/Cargo.toml b/codex-rs/codex-api/Cargo.toml index 42c2310b8c..a3a5fa4d92 100644 --- a/codex-rs/codex-api/Cargo.toml +++ b/codex-rs/codex-api/Cargo.toml @@ -9,6 +9,7 @@ async-trait = { workspace = true } bytes = { workspace = true } codex-client = { workspace = true } codex-protocol = { workspace = true } +codex-utils-rustls-provider = { workspace = true } futures = { workspace = true } http = { workspace = true } serde = { workspace = true, features = ["derive"] } @@ -20,7 +21,6 @@ tungstenite = { workspace = true } tracing = { workspace = true } eventsource-stream = { workspace = true } regex-lite = { workspace = true } -rustls = { workspace = true } tokio-util = { workspace = true, features = ["codec"] } url = { workspace = true } diff --git a/codex-rs/codex-api/src/endpoint/responses_websocket.rs b/codex-rs/codex-api/src/endpoint/responses_websocket.rs index 3fbdec6838..3e7f1e4e3f 100644 --- a/codex-rs/codex-api/src/endpoint/responses_websocket.rs +++ b/codex-rs/codex-api/src/endpoint/responses_websocket.rs @@ -10,6 +10,7 @@ use crate::sse::responses::ResponsesStreamEvent; use crate::sse::responses::process_responses_event; use crate::telemetry::WebsocketTelemetry; use codex_client::TransportError; +use codex_utils_rustls_provider::ensure_rustls_crypto_provider; use futures::SinkExt; use futures::StreamExt; use http::HeaderMap; @@ -44,7 +45,6 @@ type WsStream = WebSocketStream>; const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state"; const X_MODELS_ETAG_HEADER: &str = "x-models-etag"; const X_REASONING_INCLUDED_HEADER: &str = "x-reasoning-included"; -static RUSTLS_PROVIDER_INSTALLED: OnceLock<()> = OnceLock::new(); pub struct ResponsesWebsocketConnection { stream: Arc>>, @@ -218,12 +218,6 @@ async fn connect_websocket( Ok((stream, reasoning_included, models_etag)) } -fn ensure_rustls_crypto_provider() { - let _ = RUSTLS_PROVIDER_INSTALLED.get_or_init(|| { - let _ = rustls::crypto::ring::default_provider().install_default(); - }); -} - fn websocket_config() -> WebSocketConfig { let mut extensions = ExtensionsConfig::default(); extensions.permessage_deflate = Some(DeflateConfig::default()); diff --git a/codex-rs/network-proxy/Cargo.toml b/codex-rs/network-proxy/Cargo.toml index cf6df55036..48a75c288d 100644 --- a/codex-rs/network-proxy/Cargo.toml +++ b/codex-rs/network-proxy/Cargo.toml @@ -16,6 +16,7 @@ anyhow = { workspace = true } async-trait = { workspace = true } clap = { workspace = true, features = ["derive"] } codex-utils-absolute-path = { workspace = true } +codex-utils-rustls-provider = { workspace = true } globset = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/codex-rs/network-proxy/src/proxy.rs b/codex-rs/network-proxy/src/proxy.rs index 6388854b43..c4f3d97dc5 100644 --- a/codex-rs/network-proxy/src/proxy.rs +++ b/codex-rs/network-proxy/src/proxy.rs @@ -8,6 +8,7 @@ use crate::state::NetworkProxyState; use anyhow::Context; use anyhow::Result; use clap::Parser; +use codex_utils_rustls_provider::ensure_rustls_crypto_provider; use std::collections::HashMap; use std::net::SocketAddr; use std::net::TcpListener as StdTcpListener; @@ -381,6 +382,8 @@ impl NetworkProxy { return Ok(NetworkProxyHandle::noop()); } + ensure_rustls_crypto_provider(); + if !unix_socket_permissions_supported() { warn!("allowUnixSockets is macOS-only; requests will be rejected on this platform"); } diff --git a/codex-rs/utils/rustls-provider/Cargo.toml b/codex-rs/utils/rustls-provider/Cargo.toml new file mode 100644 index 0000000000..c9077df780 --- /dev/null +++ b/codex-rs/utils/rustls-provider/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "codex-utils-rustls-provider" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lints] +workspace = true + +[dependencies] +rustls = { workspace = true } diff --git a/codex-rs/utils/rustls-provider/src/lib.rs b/codex-rs/utils/rustls-provider/src/lib.rs new file mode 100644 index 0000000000..39d01d1829 --- /dev/null +++ b/codex-rs/utils/rustls-provider/src/lib.rs @@ -0,0 +1,12 @@ +use std::sync::Once; + +/// Ensures a process-wide rustls crypto provider is installed. +/// +/// rustls cannot auto-select a provider when both `ring` and `aws-lc-rs` +/// features are enabled in the dependency graph. +pub fn ensure_rustls_crypto_provider() { + static RUSTLS_PROVIDER_INIT: Once = Once::new(); + RUSTLS_PROVIDER_INIT.call_once(|| { + let _ = rustls::crypto::ring::default_provider().install_default(); + }); +}