diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 3d72909115..7db1da147f 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -16294,7 +16294,7 @@ "additionalProperties": { "$ref": "#/definitions/v2/NetworkDomainPermission" }, - "description": "Canonical network permission map for `experimental_network`.", + "description": "Canonical managed network permission map.", "type": [ "object", "null" @@ -16343,7 +16343,7 @@ "additionalProperties": { "$ref": "#/definitions/v2/NetworkUnixSocketPermission" }, - "description": "Canonical unix socket permission map for `experimental_network`.", + "description": "Canonical managed unix socket permission map.", "type": [ "object", "null" diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 5505864acc..e106849a05 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -12286,7 +12286,7 @@ "additionalProperties": { "$ref": "#/definitions/NetworkDomainPermission" }, - "description": "Canonical network permission map for `experimental_network`.", + "description": "Canonical managed network permission map.", "type": [ "object", "null" @@ -12335,7 +12335,7 @@ "additionalProperties": { "$ref": "#/definitions/NetworkUnixSocketPermission" }, - "description": "Canonical unix socket permission map for `experimental_network`.", + "description": "Canonical managed unix socket permission map.", "type": [ "object", "null" diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json index d549850ac1..79f84b629d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json @@ -1046,7 +1046,7 @@ "additionalProperties": { "$ref": "#/definitions/NetworkDomainPermission" }, - "description": "Canonical network permission map for `experimental_network`.", + "description": "Canonical managed network permission map.", "type": [ "object", "null" @@ -1095,7 +1095,7 @@ "additionalProperties": { "$ref": "#/definitions/NetworkUnixSocketPermission" }, - "description": "Canonical unix socket permission map for `experimental_network`.", + "description": "Canonical managed unix socket permission map.", "type": [ "object", "null" diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst index 483b244c01..458f58fc0f 100644 Binary files a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst and b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst differ diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst index 581d6d0af4..5ed9ebcd40 100644 Binary files a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst and b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst differ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/NetworkRequirements.ts b/codex-rs/app-server-protocol/schema/typescript/v2/NetworkRequirements.ts index 5a496b132a..953f69da7c 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/NetworkRequirements.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/NetworkRequirements.ts @@ -6,7 +6,7 @@ import type { NetworkUnixSocketPermission } from "./NetworkUnixSocketPermission" export type NetworkRequirements = { enabled: boolean | null, httpPort: number | null, socksPort: number | null, allowUpstreamProxy: boolean | null, dangerouslyAllowNonLoopbackProxy: boolean | null, dangerouslyAllowAllUnixSockets: boolean | null, /** - * Canonical network permission map for `experimental_network`. + * Canonical managed network permission map. */ domains: { [key in string]?: NetworkDomainPermission } | null, /** @@ -27,7 +27,7 @@ allowedDomains: Array | null, */ deniedDomains: Array | null, /** - * Canonical unix socket permission map for `experimental_network`. + * Canonical managed unix socket permission map. */ unixSockets: { [key in string]?: NetworkUnixSocketPermission } | null, /** diff --git a/codex-rs/app-server-protocol/src/protocol/v2/config.rs b/codex-rs/app-server-protocol/src/protocol/v2/config.rs index 552520aff3..f88df60376 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/config.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/config.rs @@ -723,7 +723,7 @@ pub struct NetworkRequirements { pub allow_upstream_proxy: Option, pub dangerously_allow_non_loopback_proxy: Option, pub dangerously_allow_all_unix_sockets: Option, - /// Canonical network permission map for `experimental_network`. + /// Canonical managed network permission map. pub domains: Option>, /// Host patterns routed to network approval before ordinary allow rules. pub review_domains: Option>, @@ -734,7 +734,7 @@ pub struct NetworkRequirements { pub allowed_domains: Option>, /// Legacy compatibility view derived from `domains`. pub denied_domains: Option>, - /// Canonical unix socket permission map for `experimental_network`. + /// Canonical managed unix socket permission map. pub unix_sockets: Option>, /// Legacy compatibility view derived from `unix_sockets`. pub allow_unix_sockets: Option>, diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 269e649e72..93533fce60 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -302,6 +302,23 @@ updates. Realtime connections use separate routing configuration and are not che Interrupt, realtime stop, and goal pause/clear remain available. User and project configuration changes alone do not invalidate existing threads. +## Managed network requirements + +Use `[network_proxy]` in `requirements.toml` for shared managed network constraints. +`[experimental_network]` remains a compatibility alias. A single requirements layer cannot +contain both names; across layers, they merge with normal precedence. The +`configRequirements/read.network` response exposes the resulting constraints. + +In `config.toml`, `[features.network_proxy]` configures the proxy, while +`[permissions..network]` configures a profile's network settings. Managed requirements +remain separate and override conflicting profile settings. They can start the proxy without +enabling the feature; feature settings apply only when the feature is enabled. + +Policy publishers must continue using `[experimental_network]` until every client and policy +validation layer supports `[network_proxy]`. Older clients or intermediaries may ignore or strip +the new spelling, or skip host-local validation. This reader change does not migrate or +version-gate published policy bundles. + # Amazon Bedrock authentication If `model_providers.amazon-bedrock.aws.credential_export` is configured, Bedrock setup and diff --git a/codex-rs/app-server/tests/suite/v2/config_requirements_application.rs b/codex-rs/app-server/tests/suite/v2/config_requirements_application.rs index 863a57dfb4..f79f99d5e5 100644 --- a/codex-rs/app-server/tests/suite/v2/config_requirements_application.rs +++ b/codex-rs/app-server/tests/suite/v2/config_requirements_application.rs @@ -67,32 +67,37 @@ async fn application_network_defaults_and_null_base_url_are_exposed() -> Result< #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn application_destinations_stay_separate_from_agent_network() -> Result<()> { - let home = TempDir::new()?; - std::fs::write( - home.path().join("requirements.toml"), - r#" + for table in ["network_proxy", "experimental_network"] { + let home = TempDir::new()?; + std::fs::write( + home.path().join("requirements.toml"), + format!( + r#" chatgpt_base_url = "https://managed.example.com/backend-api/" -[experimental_network.domains] +[{table}.domains] "github.com" = "allow" [application.network.domains] "GOV.example.com." = "allow" "github.com" = "deny" -"#, - )?; - let mut server = start_server(&home).await?; - let wire = read_requirements(&mut server).await?; - assert_eq!( - ( - &wire["requirements"]["application"], - &wire["requirements"]["network"]["domains"], - &wire["requirements"]["chatgptBaseUrl"] - ), - ( - &json!({"network": {"enabled": true, "domains": {"gov.example.com": "allow", "github.com": "deny"}}, "storage": null}), - &json!({"github.com": "allow"}), - &json!("https://managed.example.com/backend-api/") - ), - ); +"# + ), + )?; + let mut server = start_server(&home).await?; + let wire = read_requirements(&mut server).await?; + assert_eq!( + ( + &wire["requirements"]["application"], + &wire["requirements"]["network"]["domains"], + &wire["requirements"]["chatgptBaseUrl"] + ), + ( + &json!({"network": {"enabled": true, "domains": {"gov.example.com": "allow", "github.com": "deny"}}, "storage": null}), + &json!({"github.com": "allow"}), + &json!("https://managed.example.com/backend-api/") + ), + "{table}" + ); + } Ok(()) } diff --git a/codex-rs/config/src/config_requirements.rs b/codex-rs/config/src/config_requirements.rs index e83fba9de4..3cfc55cad3 100644 --- a/codex-rs/config/src/config_requirements.rs +++ b/codex-rs/config/src/config_requirements.rs @@ -517,13 +517,13 @@ impl<'de> Deserialize<'de> for NetworkRequirementsToml { if domains.is_some() && (allowed_domains.is_some() || denied_domains.is_some()) { return Err(D::Error::custom( - "`experimental_network.domains` cannot be combined with legacy `allowed_domains` or `denied_domains`", + "`domains` cannot be combined with legacy `allowed_domains` or `denied_domains` in managed network requirements", )); } if unix_sockets.is_some() && allow_unix_sockets.is_some() { return Err(D::Error::custom( - "`experimental_network.unix_sockets` cannot be combined with legacy `allow_unix_sockets`", + "`unix_sockets` cannot be combined with legacy `allow_unix_sockets` in managed network requirements", )); } @@ -1088,7 +1088,8 @@ pub struct ConfigRequirementsToml { pub apps: Option, pub rules: Option, pub enforce_residency: Option, - #[serde(rename = "experimental_network")] + /// Shared managed network constraints; `experimental_network` remains an alias. + #[serde(rename = "network_proxy", alias = "experimental_network")] pub network: Option, pub application: Option, pub permissions: Option, @@ -4495,6 +4496,38 @@ command = "python3 /enterprise/hooks/pre.py" Ok(()) } + #[test] + fn network_proxy_and_experimental_network_are_equivalent_aliases() -> Result<()> { + let parse = |table: &str| { + from_str::(&format!( + "[{table}]\nenabled = true\nmanaged_allowed_domains_only = true\n\ + [{table}.domains]\n\"api.example.com\" = \"allow\"" + )) + }; + assert_eq!(parse("network_proxy")?, parse("experimental_network")?); + + for values in ["", "enabled = true"] { + let both = format!("[network_proxy]\n{values}\n[experimental_network]\n{values}"); + assert!(from_str::(&both).is_err()); + } + Ok(()) + } + + #[test] + fn network_proxy_does_not_reserve_the_managed_network_profile() -> Result<()> { + let requirements: ConfigRequirementsToml = from_str( + "[network_proxy]\nenabled = true\n[permissions.network]\nextends = ':workspace'", + )?; + assert!( + requirements + .permissions + .expect("managed permissions") + .profiles + .contains_key("network") + ); + Ok(()) + } + #[test] fn network_requirements_are_preserved_as_constraints_with_source() -> Result<()> { let toml_str = r#" @@ -4687,8 +4720,7 @@ command = "python3 /enterprise/hooks/pre.py" .expect_err("mixed network domain shapes should fail"); assert!( - err.to_string() - .contains("`experimental_network.domains` cannot be combined"), + err.to_string().contains("`domains` cannot be combined"), "unexpected error: {err:#}" ); @@ -4705,7 +4737,7 @@ command = "python3 /enterprise/hooks/pre.py" assert!( err.to_string() - .contains("`experimental_network.unix_sockets` cannot be combined"), + .contains("`unix_sockets` cannot be combined"), "unexpected error: {err:#}" ); } diff --git a/codex-rs/config/src/loader/local.rs b/codex-rs/config/src/loader/local.rs index 2511510334..86eca4d277 100644 --- a/codex-rs/config/src/loader/local.rs +++ b/codex-rs/config/src/loader/local.rs @@ -161,6 +161,7 @@ pub(super) async fn load_local_config_layers_with_overrides( "credential_masking".to_string(), ], vec!["experimental_network".to_string(), "enabled".to_string()], + vec!["network_proxy".to_string(), "enabled".to_string()], ]); let effective_requirements = compose_requirements(broker_requirements.layers.into_iter().map(|layer| { diff --git a/codex-rs/config/src/loader/managed_project_discovery_tests.rs b/codex-rs/config/src/loader/managed_project_discovery_tests.rs index 79933760ce..b3aa423a3b 100644 --- a/codex-rs/config/src/loader/managed_project_discovery_tests.rs +++ b/codex-rs/config/src/loader/managed_project_discovery_tests.rs @@ -144,6 +144,54 @@ fn assert_discovery( Ok(()) } +#[tokio::test] +async fn managed_network_requirements_control_broker_project_discovery() -> anyhow::Result<()> { + let mut fixture = Fixture::new()?; + std::fs::write( + fixture.home.join("config.toml"), + format!( + "{}\n[features.network_proxy]\nenabled = true\ncredential_broker = true\n", + fixture.trust("trusted") + ), + )?; + std::fs::write( + fixture.cwd.join(".codex/config.toml"), + "[features]\nshell_snapshot = false\n", + )?; + let requirements_path = fixture._temp.path().join("requirements.toml"); + fixture.overrides.system_requirements_path = Some(requirements_path.clone()); + + for table in ["network_proxy", "experimental_network"] { + for enabled in [false, true] { + std::fs::write( + &requirements_path, + format!("[{table}]\nenabled = {enabled}\n"), + )?; + let (canonical, local) = fixture.load().await?; + let mut local_effective = TomlValue::Table(toml::map::Map::new()); + for layer in local.config.layers { + merge_toml_values(&mut local_effective, &layer.toml); + } + let shell_snapshot = |value: &TomlValue| { + value + .get("features") + .and_then(|features| features.get("shell_snapshot")) + .cloned() + }; + let expected = (!enabled).then_some(TomlValue::Boolean(false)); + assert_eq!( + ( + shell_snapshot(&canonical.effective_config()), + shell_snapshot(&local_effective) + ), + (expected.clone(), expected), + "{table}, enabled = {enabled}" + ); + } + } + Ok(()) +} + #[tokio::test] async fn managed_project_discovery_uses_file_markers_without_changing_precedence() -> anyhow::Result<()> { diff --git a/codex-rs/config/src/requirements_layers/layer.rs b/codex-rs/config/src/requirements_layers/layer.rs index 225b5cb0b3..f4a08d972c 100644 --- a/codex-rs/config/src/requirements_layers/layer.rs +++ b/codex-rs/config/src/requirements_layers/layer.rs @@ -153,6 +153,11 @@ impl ComposableRequirementsLayer { { table.insert("features".to_string(), features); } + if let Some(table) = regular_toml.as_table_mut() + && let Some(network) = table.remove("experimental_network") + { + table.insert("network_proxy".to_string(), network); + } // Hostname lookup is configuration-driven and may block on DNS, so only // resolve it when this layer contains hostname-based sandbox selectors. diff --git a/codex-rs/config/src/requirements_layers/stack.rs b/codex-rs/config/src/requirements_layers/stack.rs index 4dd6e255ba..d3510bddb1 100644 --- a/codex-rs/config/src/requirements_layers/stack.rs +++ b/codex-rs/config/src/requirements_layers/stack.rs @@ -314,7 +314,7 @@ fn populate_merged_regular_fields_with_sources( set_sourced!(marketplaces, &["marketplaces"]); set_sourced!(apps, &["apps"]); set_sourced!(enforce_residency, &["enforce_residency"]); - set_sourced!(network, &["experimental_network"]); + set_sourced!(network, &["network_proxy"]); set_sourced!(application, &["application"]); set_sourced!(permissions, &["permissions"]); set_sourced!(models, &["models"]); diff --git a/codex-rs/config/src/requirements_layers/stack_tests.rs b/codex-rs/config/src/requirements_layers/stack_tests.rs index e1c9d9fe22..9d4cb927db 100644 --- a/codex-rs/config/src/requirements_layers/stack_tests.rs +++ b/codex-rs/config/src/requirements_layers/stack_tests.rs @@ -725,6 +725,48 @@ fn network_maps_use_regular_toml_merge() { ); } +#[test] +fn network_aliases_merge_with_layer_precedence_and_sources() { + for (low_key, high_key) in [ + ("network_proxy", "experimental_network"), + ("experimental_network", "network_proxy"), + ] { + let low = layer( + "req_low", + "Low", + &format!( + "[{low_key}]\nenabled = true\nallow_local_binding = false\n\ + [{low_key}.domains]\n'low.example.com' = 'allow'\n'shared.example.com' = 'allow'" + ), + ); + let high = layer( + "req_high", + "High", + &format!( + "[{high_key}]\nenabled = false\nallow_upstream_proxy = true\n\ + [{high_key}.domains]\n'high.example.com' = 'allow'\n'shared.example.com' = 'deny'" + ), + ); + let expected_source = + RequirementSource::composite([high.source.clone(), low.source.clone()]); + let composed = compose_requirements_for_hostname(vec![low, high], /*hostname*/ None) + .expect("compose network aliases") + .expect("requirements present"); + assert_eq!( + composed.network, + Some(Sourced::new( + expected_requirements( + "[network_proxy]\nenabled = false\nallow_local_binding = false\nallow_upstream_proxy = true\n\ + [network_proxy.domains]\n'low.example.com' = 'allow'\n'high.example.com' = 'allow'\n'shared.example.com' = 'deny'" + ) + .network + .expect("network requirements"), + expected_source, + )) + ); + } +} + #[test] fn browser_and_computer_use_requirements_use_regular_toml_merge() { let composed = compose(vec![ diff --git a/codex-rs/config/src/strict_config.rs b/codex-rs/config/src/strict_config.rs index b8e8f8d88d..81e0d7e08c 100644 --- a/codex-rs/config/src/strict_config.rs +++ b/codex-rs/config/src/strict_config.rs @@ -305,7 +305,7 @@ pub(crate) fn ignored_config_warning( " Remove windows.sandbox_private_desktop; legacy Windows sandboxes always use a private desktop." } ["network_proxy"] => { - " Use [permissions..network] for network settings, or [experimental_network] in requirements.toml for enforced network policy." + " Use [permissions..network] for network settings, or [network_proxy] in requirements.toml for enforced network policy." } ["allowed_permissions"] => { " Use [allowed_permission_profiles] and default_permissions; the old allowlist is ignored even when both forms are present." diff --git a/codex-rs/core/src/config/config_loader_tests.rs b/codex-rs/core/src/config/config_loader_tests.rs index 5e0c5b4435..3552bf3981 100644 --- a/codex-rs/core/src/config/config_loader_tests.rs +++ b/codex-rs/core/src/config/config_loader_tests.rs @@ -1934,6 +1934,58 @@ enable_socks5 = false Ok(()) } +#[tokio::test] +async fn network_requirements_start_proxy_for_legacy_and_permission_configs() -> anyhow::Result<()> +{ + let mut policies = Vec::new(); + for config_toml in [ + "sandbox_mode = 'workspace-write'\n[sandbox_workspace_write]\nnetwork_access = true\n", + "default_permissions = 'dev'\n[permissions.dev]\nextends = ':workspace'\n[permissions.dev.network]\nenabled = true\n", + "default_permissions = 'network'\n[permissions.network]\nextends = ':workspace'\n[permissions.network.network]\nenabled = true\n", + ] { + for table in ["network_proxy", "experimental_network"] { + let requirements_toml = format!( + "[{table}]\nenabled = true\n[{table}.domains]\n'api.example.com' = 'allow'\n" + ); + let codex_home = tempdir()?; + tokio::fs::write(codex_home.path().join(CONFIG_TOML_FILE), config_toml).await?; + let config = ConfigBuilder::without_managed_config_for_tests() + .codex_home(codex_home.path().to_path_buf()) + .fallback_cwd(Some(codex_home.path().to_path_buf())) + .cloud_config_bundle( + CloudConfigBundleFixture::loader_with_enterprise_requirement( + &requirements_toml, + ), + ) + .build() + .await?; + + assert!(!config.features.enabled(Feature::NetworkProxy)); + assert!(config.managed_network_requirements_enabled()); + assert!( + config + .permissions + .network + .as_ref() + .expect("managed network requirements should configure the proxy") + .enabled() + ); + policies.push( + config + .config_layer_stack + .requirements() + .network + .as_ref() + .expect("managed network constraints") + .value + .clone(), + ); + } + } + assert!(policies.windows(2).all(|pair| pair[0] == pair[1])); + Ok(()) +} + #[tokio::test] async fn credential_masking_respects_config_precedence() -> std::io::Result<()> { for (features, requirements, expected_proxy, expected_broker) in [ diff --git a/codex-rs/core/tests/suite/network_approval.rs b/codex-rs/core/tests/suite/network_approval.rs index dc2328ce36..eb947c3688 100644 --- a/codex-rs/core/tests/suite/network_approval.rs +++ b/codex-rs/core/tests/suite/network_approval.rs @@ -1,5 +1,6 @@ use anyhow::Context; use anyhow::Result; +use codex_config::test_support::CloudConfigBundleFixture; use codex_config::types::ApprovalsReviewer; use codex_core::EnvironmentConfig; use codex_core::EnvironmentNetworkPolicy; @@ -115,19 +116,33 @@ use tokio::io::AsyncWriteExt; const NETWORK_TEST_HOST: &str = "codex-network-test.invalid"; const NETWORK_TEST_TARGET: &str = "http://codex-network-test.invalid:80"; +#[test_case("experimental_network"; "legacy_requirements")] +#[test_case("network_proxy"; "proxy_requirements")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[cfg_attr( not(target_os = "linux"), ignore = "requires the trusted Linux proxy bridge" )] -async fn guardian_network_approval_preserves_action_and_outcome_routing() -> Result<()> { +async fn guardian_network_approval_preserves_action_and_outcome_routing( + requirements_table: &str, +) -> Result<()> { skip_if_target_windows!(Ok(()), "uses the POSIX/Python network fixture"); skip_if_host_windows!(Ok(())); skip_if_no_network!(Ok(())); skip_if_sandbox!(Ok(())); let server = start_mock_server().await; - let test = managed_network_unified_exec_test(&server).await?; + let test = managed_network_unified_exec_builder(&[])? + .with_cloud_config_bundle( + CloudConfigBundleFixture::loader_with_enterprise_requirement(format!( + "[{requirements_table}]\nenabled = true\nallow_local_binding = true\n" + )), + ) + .build_with_remote_and_local_env(&server) + .await?; + assert!(test.config.managed_network_requirements_enabled()); + assert!(test.config.permissions.network.is_some()); + assert!(test.session_configured.network_proxy.is_some()); let first_call_id = "guardian-network-approved"; let second_call_id = "guardian-network-denied"; let first_command = network_fetch_args(LOCAL_ENVIRONMENT_ID)["cmd"] diff --git a/codex-rs/tui/src/debug_config.rs b/codex-rs/tui/src/debug_config.rs index 01842c2f1e..daacb4b24b 100644 --- a/codex-rs/tui/src/debug_config.rs +++ b/codex-rs/tui/src/debug_config.rs @@ -359,7 +359,7 @@ fn render_debug_config_lines( if let Some(network) = requirements.network.as_ref() { requirement_lines.push(requirement_line( - "experimental_network", + "network_proxy", format_network_constraints(&network.value), Some(&network.source), )); @@ -1071,7 +1071,7 @@ interrupt_message = false "enforce_residency: us (source: {requirements_source})" ))); assert!(rendered.contains(&format!( - "experimental_network: enabled=true, domains={{example.com=allow}}, review_domains={{api.example.com}}, header_injections=1 hosts={{api.example.com}} (source: {requirements_source})" + "network_proxy: enabled=true, domains={{example.com=allow}}, review_domains={{api.example.com}}, header_injections=1 hosts={{api.example.com}} (source: {requirements_source})" ))); assert!(!rendered.contains("secret-looking-value")); assert!( @@ -1227,7 +1227,7 @@ interrupt_message = false let rendered = render_stack_to_text(&stack); let requirements_source = (RequirementSource::LegacyManagedConfigTomlFromMdm).to_string(); assert!(rendered.contains(&format!( - "experimental_network: unix_sockets={{/tmp/blocked.sock=deny, /tmp/codex.sock=allow}} (source: {requirements_source})" + "network_proxy: unix_sockets={{/tmp/blocked.sock=deny, /tmp/codex.sock=allow}} (source: {requirements_source})" ))); } diff --git a/codex-rs/tui/src/snapshots/codex_tui__debug_config__tests__debug_config_requirement_sources.snap b/codex-rs/tui/src/snapshots/codex_tui__debug_config__tests__debug_config_requirement_sources.snap index 64120d4aed..0f9464caf5 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__debug_config__tests__debug_config_requirement_sources.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__debug_config__tests__debug_config_requirement_sources.snap @@ -25,5 +25,5 @@ Requirements: - features: guardian_approval=true (source: MDM managed_config.toml (legacy)) - mcp_servers: docs (source: MDM managed_config.toml (legacy)) - enforce_residency: us (source: MDM managed_config.toml (legacy)) - - experimental_network: enabled=true, domains={example.com=allow}, review_domains={api.example.com}, header_injections=1 hosts={api.example.com} (source: MDM managed_config.toml (legacy)) + - network_proxy: enabled=true, domains={example.com=allow}, review_domains={api.example.com}, header_injections=1 hosts={api.example.com} (source: MDM managed_config.toml (legacy)) - permissions.filesystem.deny_read: /home/alice/.gitconfig (source: /etc/codex/requirements.toml) diff --git a/codex-rs/windows-sandbox-service/src/machine_policy_tests.rs b/codex-rs/windows-sandbox-service/src/machine_policy_tests.rs index 92d77620a8..520aa5a6ee 100644 --- a/codex-rs/windows-sandbox-service/src/machine_policy_tests.rs +++ b/codex-rs/windows-sandbox-service/src/machine_policy_tests.rs @@ -91,38 +91,44 @@ fn forbidden_local_binding_is_rejected() { proxy_ports: Vec::new(), allow_local_binding: true, }; - assert!( - validate_requirements( - &settings, - WindowsSandboxProxyListeners::default(), - "[experimental_network]\nallow_local_binding = false" - ) - .is_err() - ); + for table in ["network_proxy", "experimental_network"] { + assert!( + validate_requirements( + &settings, + WindowsSandboxProxyListeners::default(), + &format!("[{table}]\nallow_local_binding = false") + ) + .is_err(), + "{table}" + ); + } } #[test] fn disabled_network_rejects_proxy_ports_and_local_binding() -> Result<()> { - let policy = "[experimental_network]\nenabled = false"; - validate_requirements( - &WindowsSandboxProvisioningSettings::default(), - WindowsSandboxProxyListeners::default(), - policy, - )?; - for settings in [ - WindowsSandboxProvisioningSettings { - proxy_ports: vec![3128], - allow_local_binding: false, - }, - WindowsSandboxProvisioningSettings { - proxy_ports: Vec::new(), - allow_local_binding: true, - }, - ] { - assert!( - validate_requirements(&settings, WindowsSandboxProxyListeners::default(), policy) - .is_err() - ); + for table in ["network_proxy", "experimental_network"] { + let policy = format!("[{table}]\nenabled = false"); + validate_requirements( + &WindowsSandboxProvisioningSettings::default(), + WindowsSandboxProxyListeners::default(), + &policy, + )?; + for settings in [ + WindowsSandboxProvisioningSettings { + proxy_ports: vec![3128], + allow_local_binding: false, + }, + WindowsSandboxProvisioningSettings { + proxy_ports: Vec::new(), + allow_local_binding: true, + }, + ] { + assert!( + validate_requirements(&settings, WindowsSandboxProxyListeners::default(), &policy) + .is_err(), + "{table}" + ); + } } Ok(()) } @@ -153,42 +159,45 @@ fn disabled_local_binding_is_allowed_when_policy_permits_binding() -> Result<()> #[test] fn managed_proxy_ports_allow_only_configured_ports() -> Result<()> { - let policy = "[experimental_network]\nhttp_port = 3128\nsocks_port = 1080"; let allowed = WindowsSandboxProvisioningSettings { proxy_ports: vec![1080, 3128], allow_local_binding: false, }; - for listeners in [ - WindowsSandboxProxyListeners { - http_ports: vec![3128], - socks_ports: vec![1080], - }, - WindowsSandboxProxyListeners::default(), - ] { - validate_requirements(&allowed, listeners, policy)?; - } let prohibited = WindowsSandboxProvisioningSettings { proxy_ports: vec![1080, 3128, 8080], allow_local_binding: false, }; - for listeners in [ - WindowsSandboxProxyListeners { - http_ports: vec![3128, 8080], - socks_ports: vec![1080], - }, - WindowsSandboxProxyListeners { - http_ports: vec![3128], - socks_ports: vec![1080], - }, - WindowsSandboxProxyListeners::default(), - ] { - assert!(validate_requirements(&prohibited, listeners, policy).is_err()); + for table in ["network_proxy", "experimental_network"] { + let policy = format!("[{table}]\nhttp_port = 3128\nsocks_port = 1080"); + for listeners in [ + WindowsSandboxProxyListeners { + http_ports: vec![3128], + socks_ports: vec![1080], + }, + WindowsSandboxProxyListeners::default(), + ] { + validate_requirements(&allowed, listeners, &policy)?; + } + for listeners in [ + WindowsSandboxProxyListeners { + http_ports: vec![3128, 8080], + socks_ports: vec![1080], + }, + WindowsSandboxProxyListeners { + http_ports: vec![3128], + socks_ports: vec![1080], + }, + WindowsSandboxProxyListeners::default(), + ] { + assert!(validate_requirements(&prohibited, listeners, &policy).is_err()); + } + validate_requirements( + &WindowsSandboxProvisioningSettings::default(), + WindowsSandboxProxyListeners::default(), + &policy, + )?; } - validate_requirements( - &WindowsSandboxProvisioningSettings::default(), - WindowsSandboxProxyListeners::default(), - policy, - ) + Ok(()) } #[test] diff --git a/sdk/python/src/openai_codex/generated/v2_all.py b/sdk/python/src/openai_codex/generated/v2_all.py index 0551659b29..9533c59582 100644 --- a/sdk/python/src/openai_codex/generated/v2_all.py +++ b/sdk/python/src/openai_codex/generated/v2_all.py @@ -2924,8 +2924,7 @@ class NetworkRequirements(BaseModel): ), ] = None domains: Annotated[ - dict[str, Any] | None, - Field(description="Canonical network permission map for `experimental_network`."), + dict[str, Any] | None, Field(description="Canonical managed network permission map.") ] = None enabled: bool | None = None http_port: Annotated[int | None, Field(alias="httpPort", ge=0)] = None @@ -2946,10 +2945,7 @@ class NetworkRequirements(BaseModel): socks_port: Annotated[int | None, Field(alias="socksPort", ge=0)] = None unix_sockets: Annotated[ dict[str, Any] | None, - Field( - alias="unixSockets", - description="Canonical unix socket permission map for `experimental_network`.", - ), + Field(alias="unixSockets", description="Canonical managed unix socket permission map."), ] = None