diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 71a5ae41bf..4325f7710d 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1197,7 +1197,10 @@ impl Config { || config_profile.sandbox_mode.is_some() || cfg.sandbox_mode.is_some(); - let network_proxy = resolve_network_proxy_config(&cfg, &codex_home); + let mut network_proxy = resolve_network_proxy_config(&cfg, &codex_home); + if !features.enabled(Feature::NetworkProxy) { + network_proxy.enabled = false; + } let mut model_providers = built_in_model_providers(); // Merge user-defined providers into the built-in list. @@ -1556,6 +1559,51 @@ persistence = "none" ); } + #[test] + fn network_proxy_is_rollout_gated_by_feature_flag() -> std::io::Result<()> { + let cfg_without_feature_flag = r#" +[network_proxy] +enabled = true +"#; + let parsed_without_feature_flag = + toml::from_str::(cfg_without_feature_flag).expect("TOML parse should work"); + let cwd_temp_dir = TempDir::new().unwrap(); + std::fs::write(cwd_temp_dir.path().join(".git"), "gitdir: nowhere")?; + let codex_home_temp_dir = TempDir::new().unwrap(); + let config_without_feature_flag = Config::load_from_base_config_with_overrides( + parsed_without_feature_flag, + ConfigOverrides { + cwd: Some(cwd_temp_dir.path().to_path_buf()), + ..Default::default() + }, + codex_home_temp_dir.path().to_path_buf(), + )?; + assert!(!config_without_feature_flag.network_proxy.enabled); + + let cfg_with_feature_flag = r#" +[features] +network_proxy = true + +[network_proxy] +enabled = true +"#; + let parsed_with_feature_flag = + toml::from_str::(cfg_with_feature_flag).expect("TOML parse should work"); + let cwd_temp_dir = TempDir::new().unwrap(); + std::fs::write(cwd_temp_dir.path().join(".git"), "gitdir: nowhere")?; + let codex_home_temp_dir = TempDir::new().unwrap(); + let config_with_feature_flag = Config::load_from_base_config_with_overrides( + parsed_with_feature_flag, + ConfigOverrides { + cwd: Some(cwd_temp_dir.path().to_path_buf()), + ..Default::default() + }, + codex_home_temp_dir.path().to_path_buf(), + )?; + assert!(config_with_feature_flag.network_proxy.enabled); + Ok(()) + } + #[test] fn tui_config_missing_notifications_field_defaults_to_enabled() { let cfg = r#" diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 3239adc109..7fc52f3244 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -85,6 +85,8 @@ pub enum Feature { ShellSnapshot, /// Experimental TUI v2 (viewport) implementation. Tui2, + /// Route subprocess network access through the Codex network proxy and surface approvals. + NetworkProxy, } impl Feature { @@ -405,4 +407,10 @@ pub const FEATURES: &[FeatureSpec] = &[ stage: Stage::Experimental, default_enabled: false, }, + FeatureSpec { + id: Feature::NetworkProxy, + key: "network_proxy", + stage: Stage::Experimental, + default_enabled: false, + }, ]; diff --git a/docs/config.md b/docs/config.md index 659b5bb4b8..b2fbdc7e00 100644 --- a/docs/config.md +++ b/docs/config.md @@ -354,6 +354,9 @@ Though using this option may also be necessary if you try to use Codex in enviro Codex can route subprocess network traffic through an external proxy (for example, the `network_proxy` sandbox proxy) and surface approval prompts when requests are blocked by policy. ```toml +[features] +network_proxy = true + [network_proxy] enabled = true proxy_url = "http://127.0.0.1:3128" @@ -376,6 +379,7 @@ poll_interval_ms = 1000 Notes: - Proxy settings are injected only when sandbox network access is enabled (or full access mode). If the sandbox blocks network access, requests are blocked at the OS layer. +- Network proxy integration is rollout-gated behind `[features].network_proxy = true`. - `proxy_url` is used for HTTP proxy envs (`HTTP_PROXY`, `HTTPS_PROXY`, `http_proxy`, `https_proxy`, plus npm/yarn variants). Docker and Cloud SDK proxy envs are derived from the HTTP proxy when present. - When `proxy_url` points at localhost, Codex also assumes a SOCKS5 proxy on `localhost:8081` for `ALL_PROXY`, `GRPC_PROXY`, `FTP_PROXY`, `RSYNC_PROXY`, and (macOS only) `GIT_SSH_COMMAND`. - `no_proxy` entries bypass the proxy; defaults include localhost + private network ranges. Use sparingly because bypassed traffic is not filtered by the proxy policy. diff --git a/docs/example-config.md b/docs/example-config.md index ecfe1f3ed4..a64637bfce 100644 --- a/docs/example-config.md +++ b/docs/example-config.md @@ -105,6 +105,7 @@ exclude_slash_tmp = false [network_proxy] # Enable proxy env injection + approval prompts for blocked domains. Default: false +# NOTE: This is rollout-gated behind `[features].network_proxy = true`. enabled = false # HTTP/HTTPS proxy URL. Default: "http://127.0.0.1:3128" proxy_url = "http://127.0.0.1:3128" @@ -259,6 +260,7 @@ rmcp_client = false apply_patch_freeform = false view_image_tool = true web_search_request = false +network_proxy = false ghost_commit = false enable_experimental_windows_sandbox = false skills = false diff --git a/docs/network-proxy-design.md b/docs/network-proxy-design.md index 7cc7489b6a..5b30f050ea 100644 --- a/docs/network-proxy-design.md +++ b/docs/network-proxy-design.md @@ -47,6 +47,7 @@ The proxy reads `~/.codex/config.toml`: - `[network_proxy]` for endpoints, mode, and toggles. - `[network_proxy.policy]` for `allowed_domains` / `denied_domains` (and, on macOS, optional local IPC allowances). - `[network_proxy.mitm]` for MITM CA paths and inspection settings. +- Codex integration is rollout-gated behind `[features].network_proxy = true`. Codex is the source of truth. Approval actions update the config and trigger a proxy reload. diff --git a/docs/network-proxy-quickstart.md b/docs/network-proxy-quickstart.md index 9949f64b6e..3d144797ca 100644 --- a/docs/network-proxy-quickstart.md +++ b/docs/network-proxy-quickstart.md @@ -22,6 +22,9 @@ cargo build -p codex-network-proxy --features mitm Add this to `~/.codex/config.toml`: ```toml +[features] +network_proxy = true + [network_proxy] enabled = true proxy_url = "http://127.0.0.1:3128" @@ -95,6 +98,6 @@ curl -fsS -X POST http://127.0.0.1:8080/mode -d '{"mode":"full"}' ## Codex integration sanity check 1) Start the proxy. -2) Launch Codex with the proxy enabled in config. +2) Launch Codex with `[features].network_proxy = true` and `network_proxy.enabled = true` set in config. 3) Run a network command (e.g., `curl https://example.com`). 4) Confirm you see the allow/deny prompt and that the proxy logs reflect the decision. diff --git a/docs/sandbox.md b/docs/sandbox.md index 2ef53f3109..2b328303ff 100644 --- a/docs/sandbox.md +++ b/docs/sandbox.md @@ -61,7 +61,7 @@ Codex can optionally route outbound network traffic through a proxy and prompt y Key behaviors: - The OS sandbox is still the first line of defense. If network access is disabled, outbound requests are blocked at the OS level. -- When network is enabled and `network_proxy.enabled = true`, Codex polls the proxy admin API (`/blocked`) and immediately notifies you about blocked domains. +- When network is enabled and both `[features].network_proxy = true` and `network_proxy.enabled = true`, Codex polls the proxy admin API (`/blocked`) and immediately notifies you about blocked domains. - For exec commands that include HTTP/HTTPS URLs, Codex preflights the host against the proxy config and prompts before running the command. - Approvals update `~/.codex/config.toml` under `[network_proxy.policy]` and trigger a proxy reload. - You can choose to: