From 5aa81f1cced38195f6c25ca133cf153ad090a442 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 9 Jul 2026 11:29:30 +0100 Subject: [PATCH 1/2] Simplify network proxy configuration --- codex-rs/config/src/permissions_toml.rs | 34 +-- codex-rs/core/src/config/config_tests.rs | 18 +- codex-rs/core/src/config/mod.rs | 4 +- .../core/src/config/network_proxy_spec.rs | 37 +-- .../src/config/network_proxy_spec_tests.rs | 98 +++---- codex-rs/core/src/config/permissions.rs | 2 +- codex-rs/core/src/config/permissions_tests.rs | 12 +- codex-rs/core/src/guardian/tests.rs | 12 +- codex-rs/core/src/network_proxy_loader.rs | 18 +- .../core/src/network_proxy_loader_tests.rs | 46 ++- codex-rs/core/src/session/tests.rs | 24 +- codex-rs/core/src/tasks/mod.rs | 2 +- .../network-proxy/src/attribution_tests.rs | 6 +- codex-rs/network-proxy/src/config.rs | 119 ++++---- codex-rs/network-proxy/src/connect_policy.rs | 8 +- codex-rs/network-proxy/src/http_proxy.rs | 46 ++- codex-rs/network-proxy/src/lib.rs | 1 - codex-rs/network-proxy/src/mitm_hook.rs | 50 ++-- codex-rs/network-proxy/src/mitm_tests.rs | 20 +- codex-rs/network-proxy/src/network_policy.rs | 19 +- codex-rs/network-proxy/src/proxy.rs | 117 ++++---- codex-rs/network-proxy/src/runtime.rs | 273 ++++++------------ codex-rs/network-proxy/src/socks5.rs | 42 ++- codex-rs/network-proxy/src/state.rs | 36 +-- codex-rs/sandboxing/src/seatbelt_tests.rs | 8 +- 25 files changed, 422 insertions(+), 630 deletions(-) diff --git a/codex-rs/config/src/permissions_toml.rs b/codex-rs/config/src/permissions_toml.rs index 988e21e539d4..92bdcfcbb353 100644 --- a/codex-rs/config/src/permissions_toml.rs +++ b/codex-rs/config/src/permissions_toml.rs @@ -501,40 +501,39 @@ impl NetworkMitmActionToml { impl NetworkToml { pub fn apply_to_network_proxy_config(&self, config: &mut NetworkProxyConfig) { if let Some(enabled) = self.enabled { - config.network.enabled = enabled; + config.enabled = enabled; } if let Some(proxy_url) = self.proxy_url.as_ref() { - config.network.proxy_url = proxy_url.clone(); + config.proxy_url = proxy_url.clone(); } if let Some(enable_socks5) = self.enable_socks5 { - config.network.enable_socks5 = enable_socks5; + config.enable_socks5 = enable_socks5; } if let Some(socks_url) = self.socks_url.as_ref() { - config.network.socks_url = socks_url.clone(); + config.socks_url = socks_url.clone(); } if let Some(enable_socks5_udp) = self.enable_socks5_udp { - config.network.enable_socks5_udp = enable_socks5_udp; + config.enable_socks5_udp = enable_socks5_udp; } if let Some(allow_upstream_proxy) = self.allow_upstream_proxy { - config.network.allow_upstream_proxy = allow_upstream_proxy; + config.allow_upstream_proxy = allow_upstream_proxy; } if let Some(dangerously_allow_non_loopback_proxy) = self.dangerously_allow_non_loopback_proxy { - config.network.dangerously_allow_non_loopback_proxy = - dangerously_allow_non_loopback_proxy; + config.dangerously_allow_non_loopback_proxy = dangerously_allow_non_loopback_proxy; } if let Some(dangerously_allow_all_unix_sockets) = self.dangerously_allow_all_unix_sockets { - config.network.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets; + config.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets; } if let Some(mode) = self.mode { - config.network.mode = mode; + config.mode = mode; } if let Some(domains) = self.domains.as_ref() { overlay_network_domain_permissions(config, domains); } if let Some(unix_sockets) = self.unix_sockets.as_ref() { - let mut proxy_unix_sockets = config.network.unix_sockets.take().unwrap_or_default(); + let mut proxy_unix_sockets = config.unix_sockets.take().unwrap_or_default(); for (path, permission) in &unix_sockets.entries { let permission = match permission { NetworkUnixSocketPermissionToml::Allow => { @@ -544,17 +543,16 @@ impl NetworkToml { }; proxy_unix_sockets.entries.insert(path.clone(), permission); } - config.network.unix_sockets = + config.unix_sockets = (!proxy_unix_sockets.entries.is_empty()).then_some(proxy_unix_sockets); } if let Some(allow_local_binding) = self.allow_local_binding { - config.network.allow_local_binding = allow_local_binding; + config.allow_local_binding = allow_local_binding; } if let Some(mitm) = self.mitm.as_ref() { - config.network.mitm_hooks = mitm.to_runtime_hooks(mitm.actions.as_ref()); + config.mitm_hooks = mitm.to_runtime_hooks(mitm.actions.as_ref()); } - config.network.mitm = - config.network.mode == NetworkMode::Limited || !config.network.mitm_hooks.is_empty(); + config.mitm = config.mode == NetworkMode::Limited || !config.mitm_hooks.is_empty(); } pub fn to_network_proxy_config(&self) -> NetworkProxyConfig { @@ -628,8 +626,6 @@ pub fn overlay_network_domain_permissions( NetworkDomainPermissionToml::Allow => ProxyNetworkDomainPermission::Allow, NetworkDomainPermissionToml::Deny => ProxyNetworkDomainPermission::Deny, }; - config - .network - .upsert_domain_permission(pattern.clone(), permission, normalize_host); + config.upsert_domain_permission(pattern.clone(), permission, normalize_host); } } diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 0c8d0899a48f..1df2bb3e3fc0 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -1182,16 +1182,16 @@ fn permissions_profile_network_to_proxy_config_preserves_mitm_hooks() { let config = network.to_network_proxy_config(); - assert_eq!(config.network.mode, NetworkMode::Full); - assert!(config.network.mitm); - assert_eq!(config.network.mitm_hooks.len(), 1); - assert_eq!(config.network.mitm_hooks[0].host, "api.github.com"); + assert_eq!(config.mode, NetworkMode::Full); + assert!(config.mitm); + assert_eq!(config.mitm_hooks.len(), 1); + assert_eq!(config.mitm_hooks[0].host, "api.github.com"); assert_eq!( - config.network.mitm_hooks[0].matcher.methods, + config.mitm_hooks[0].matcher.methods, vec!["POST".to_string()] ); assert_eq!( - config.network.mitm_hooks[0].actions.strip_request_headers, + config.mitm_hooks[0].actions.strip_request_headers, vec!["authorization".to_string()] ); } @@ -1228,13 +1228,13 @@ action = ["noop"] let config = network.to_network_proxy_config(); - assert_eq!(config.network.mitm_hooks.len(), 2); + assert_eq!(config.mitm_hooks.len(), 2); assert_eq!( - config.network.mitm_hooks[0].matcher.path_prefixes, + config.mitm_hooks[0].matcher.path_prefixes, vec!["/repos/openai/".to_string()] ); assert_eq!( - config.network.mitm_hooks[1].matcher.path_prefixes, + config.mitm_hooks[1].matcher.path_prefixes, vec!["/repos/".to_string()] ); } diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 6348f14c0443..5cd6748c0580 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -3368,7 +3368,7 @@ impl Config { network_proxy, ); } - configured_network_proxy_config.network.enabled = true; + configured_network_proxy_config.enabled = true; } let approval_policy_was_explicit = approval_policy_override.is_some() || cfg.approval_policy.is_some(); @@ -4128,7 +4128,7 @@ impl Config { network_proxy, ); } - configured_network_proxy_config.network.enabled = true; + configured_network_proxy_config.enabled = true; } configured_network_proxy_config } else { diff --git a/codex-rs/core/src/config/network_proxy_spec.rs b/codex-rs/core/src/config/network_proxy_spec.rs index 653e6987f308..a5cef5ca72d8 100644 --- a/codex-rs/core/src/config/network_proxy_spec.rs +++ b/codex-rs/core/src/config/network_proxy_spec.rs @@ -74,15 +74,15 @@ impl ConfigReloader for StaticNetworkProxyReloader { impl NetworkProxySpec { pub(crate) fn enabled(&self) -> bool { - self.config.network.enabled + self.config.enabled } pub fn proxy_host_and_port(&self) -> String { - host_and_port_from_network_addr(&self.config.network.proxy_url, /*default_port*/ 3128) + host_and_port_from_network_addr(&self.config.proxy_url, /*default_port*/ 3128) } pub fn socks_enabled(&self) -> bool { - self.config.network.enable_socks5 + self.config.enable_socks5 } pub(crate) fn from_config_and_constraints( @@ -221,31 +221,30 @@ impl NetworkProxySpec { let denylist_expansion_enabled = Self::denylist_expansion_enabled(permission_profile); if let Some(enabled) = requirements.enabled { - config.network.enabled = enabled; + config.enabled = enabled; constraints.enabled = Some(enabled); } if let Some(http_port) = requirements.http_port { - config.network.proxy_url = format!("http://127.0.0.1:{http_port}"); + config.proxy_url = format!("http://127.0.0.1:{http_port}"); } if let Some(socks_port) = requirements.socks_port { - config.network.socks_url = format!("http://127.0.0.1:{socks_port}"); + config.socks_url = format!("http://127.0.0.1:{socks_port}"); } if let Some(allow_upstream_proxy) = requirements.allow_upstream_proxy { - config.network.allow_upstream_proxy = allow_upstream_proxy; + config.allow_upstream_proxy = allow_upstream_proxy; constraints.allow_upstream_proxy = Some(allow_upstream_proxy); } if let Some(dangerously_allow_non_loopback_proxy) = requirements.dangerously_allow_non_loopback_proxy { - config.network.dangerously_allow_non_loopback_proxy = - dangerously_allow_non_loopback_proxy; + config.dangerously_allow_non_loopback_proxy = dangerously_allow_non_loopback_proxy; constraints.dangerously_allow_non_loopback_proxy = Some(dangerously_allow_non_loopback_proxy); } if let Some(dangerously_allow_all_unix_sockets) = requirements.dangerously_allow_all_unix_sockets { - config.network.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets; + config.dangerously_allow_all_unix_sockets = dangerously_allow_all_unix_sockets; constraints.dangerously_allow_all_unix_sockets = Some(dangerously_allow_all_unix_sockets); } @@ -270,14 +269,12 @@ impl NetworkProxySpec { let effective_allowed_domains = if allowlist_expansion_enabled { Self::merge_domain_lists( managed_allowed_domains.clone(), - config.network.allowed_domains().as_deref().unwrap_or(&[]), + config.allowed_domains().as_deref().unwrap_or(&[]), ) } else { managed_allowed_domains.clone() }; - config - .network - .set_allowed_domains(effective_allowed_domains); + config.set_allowed_domains(effective_allowed_domains); constraints.allowed_domains = Some(managed_allowed_domains); constraints.allowlist_expansion_enabled = Some(allowlist_expansion_enabled); } @@ -289,12 +286,12 @@ impl NetworkProxySpec { let effective_denied_domains = if denylist_expansion_enabled { Self::merge_domain_lists( managed_denied_domains.clone(), - config.network.denied_domains().as_deref().unwrap_or(&[]), + config.denied_domains().as_deref().unwrap_or(&[]), ) } else { managed_denied_domains.clone() }; - config.network.set_denied_domains(effective_denied_domains); + config.set_denied_domains(effective_denied_domains); constraints.denied_domains = Some(managed_denied_domains); constraints.denylist_expansion_enabled = Some(denylist_expansion_enabled); } @@ -304,13 +301,11 @@ impl NetworkProxySpec { .as_ref() .map(codex_config::NetworkUnixSocketPermissionsToml::allow_unix_sockets) .unwrap_or_default(); - config - .network - .set_allow_unix_sockets(allow_unix_sockets.clone()); + config.set_allow_unix_sockets(allow_unix_sockets.clone()); constraints.allow_unix_sockets = Some(allow_unix_sockets); } if let Some(allow_local_binding) = requirements.allow_local_binding { - config.network.allow_local_binding = allow_local_binding; + config.allow_local_binding = allow_local_binding; constraints.allow_local_binding = Some(allow_local_binding); } @@ -359,7 +354,7 @@ fn upsert_network_domains(config: &mut NetworkProxyConfig, hosts: Vec, a let mut incoming = HashSet::new(); for host in hosts { if incoming.insert(host.clone()) { - config.network.upsert_domain_permission( + config.upsert_domain_permission( host, if allow { codex_network_proxy::NetworkDomainPermission::Allow diff --git a/codex-rs/core/src/config/network_proxy_spec_tests.rs b/codex-rs/core/src/config/network_proxy_spec_tests.rs index 6dfb1e3a25fa..ff90af0c7413 100644 --- a/codex-rs/core/src/config/network_proxy_spec_tests.rs +++ b/codex-rs/core/src/config/network_proxy_spec_tests.rs @@ -43,9 +43,7 @@ fn build_state_with_audit_metadata_threads_metadata_to_state() { #[test] fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_allowed_domains(vec!["api.example.com".to_string()]); + config.set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { domains: Some(domain_permissions([( "*.example.com", @@ -62,7 +60,7 @@ fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() { .expect("config should stay within the managed allowlist"); assert_eq!( - spec.config.network.allowed_domains(), + spec.config.allowed_domains(), Some(vec![ "*.example.com".to_string(), "api.example.com".to_string() @@ -78,9 +76,7 @@ fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() { #[test] fn requirements_allowed_domains_do_not_override_user_denies_for_same_pattern() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_denied_domains(vec!["api.example.com".to_string()]); + config.set_denied_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { domains: Some(domain_permissions([( "api.example.com", @@ -96,9 +92,9 @@ fn requirements_allowed_domains_do_not_override_user_denies_for_same_pattern() { ) .expect("managed allowlist should not erase a user deny"); - assert_eq!(spec.config.network.allowed_domains(), None); + assert_eq!(spec.config.allowed_domains(), None); assert_eq!( - spec.config.network.denied_domains(), + spec.config.denied_domains(), Some(vec!["api.example.com".to_string()]) ); assert_eq!( @@ -110,9 +106,7 @@ fn requirements_allowed_domains_do_not_override_user_denies_for_same_pattern() { #[test] fn requirements_allowlist_expansion_keeps_user_entries_mutable() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_allowed_domains(vec!["api.example.com".to_string()]); + config.set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { domains: Some(domain_permissions([( "*.example.com", @@ -129,18 +123,18 @@ fn requirements_allowlist_expansion_keeps_user_entries_mutable() { .expect("managed baseline should still allow user edits"); let mut candidate = spec.config.clone(); - candidate.network.upsert_domain_permission( + candidate.upsert_domain_permission( "api.example.com".to_string(), NetworkDomainPermission::Deny, normalize_host, ); assert_eq!( - candidate.network.allowed_domains(), + candidate.allowed_domains(), Some(vec!["*.example.com".to_string()]) ); assert_eq!( - candidate.network.denied_domains(), + candidate.denied_domains(), Some(vec!["api.example.com".to_string()]) ); validate_policy_against_constraints(&candidate, &spec.constraints) @@ -150,9 +144,7 @@ fn requirements_allowlist_expansion_keeps_user_entries_mutable() { #[test] fn managed_unrestricted_profile_allows_domain_expansion() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_allowed_domains(vec!["api.example.com".to_string()]); + config.set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { domains: Some(domain_permissions([( "*.example.com", @@ -173,7 +165,7 @@ fn managed_unrestricted_profile_allows_domain_expansion() { .expect("managed unrestricted filesystem should still use managed network constraints"); assert_eq!( - spec.config.network.allowed_domains(), + spec.config.allowed_domains(), Some(vec![ "*.example.com".to_string(), "api.example.com".to_string() @@ -185,12 +177,8 @@ fn managed_unrestricted_profile_allows_domain_expansion() { #[test] fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_allowed_domains(vec!["evil.com".to_string()]); - config - .network - .set_denied_domains(vec!["more-blocked.example.com".to_string()]); + config.set_allowed_domains(vec!["evil.com".to_string()]); + config.set_denied_domains(vec!["more-blocked.example.com".to_string()]); let requirements = NetworkConstraints { domains: Some(domain_permissions([ ("*.example.com", NetworkDomainPermissionToml::Allow), @@ -207,11 +195,11 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() { .expect("yolo mode should pin the effective policy to the managed baseline"); assert_eq!( - spec.config.network.allowed_domains(), + spec.config.allowed_domains(), Some(vec!["*.example.com".to_string()]) ); assert_eq!( - spec.config.network.denied_domains(), + spec.config.denied_domains(), Some(vec!["blocked.example.com".to_string()]) ); assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false)); @@ -221,9 +209,7 @@ fn danger_full_access_keeps_managed_allowlist_and_denylist_fixed() { #[test] fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_allowed_domains(vec!["api.example.com".to_string()]); + config.set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { domains: Some(domain_permissions([( "*.example.com", @@ -241,7 +227,7 @@ fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() { .expect("managed baseline should still load"); assert_eq!( - spec.config.network.allowed_domains(), + spec.config.allowed_domains(), Some(vec!["*.example.com".to_string()]) ); assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false)); @@ -250,9 +236,7 @@ fn managed_allowed_domains_only_disables_default_mode_allowlist_expansion() { #[test] fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_allowed_domains(vec!["api.example.com".to_string()]); + config.set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { domains: Some(domain_permissions([( "managed.example.com", @@ -270,7 +254,7 @@ fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses() .expect("managed-only allowlist should still load"); assert_eq!( - spec.config.network.allowed_domains(), + spec.config.allowed_domains(), Some(vec!["managed.example.com".to_string()]) ); assert_eq!( @@ -284,9 +268,7 @@ fn managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses() #[test] fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domains() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_allowed_domains(vec!["api.example.com".to_string()]); + config.set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { managed_allowed_domains_only: Some(true), ..Default::default() @@ -299,7 +281,7 @@ fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domain ) .expect("managed-only mode should treat missing managed allowlist as empty"); - assert_eq!(spec.config.network.allowed_domains(), None); + assert_eq!(spec.config.allowed_domains(), None); assert_eq!(spec.constraints.allowed_domains, Some(Vec::new())); assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false)); assert!(spec.hard_deny_allowlist_misses); @@ -308,9 +290,7 @@ fn managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domain #[test] fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_managed_list() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_allowed_domains(vec!["api.example.com".to_string()]); + config.set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { managed_allowed_domains_only: Some(true), ..Default::default() @@ -323,7 +303,7 @@ fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_m ) .expect("managed-only mode should treat missing managed allowlist as empty"); - assert_eq!(spec.config.network.allowed_domains(), None); + assert_eq!(spec.config.allowed_domains(), None); assert_eq!(spec.constraints.allowed_domains, Some(Vec::new())); assert_eq!(spec.constraints.allowlist_expansion_enabled, Some(false)); assert!(spec.hard_deny_allowlist_misses); @@ -332,9 +312,7 @@ fn managed_allowed_domains_only_blocks_all_user_domains_in_full_access_without_m #[test] fn deny_only_requirements_do_not_create_allow_constraints_in_full_access() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_allowed_domains(vec!["api.example.com".to_string()]); + config.set_allowed_domains(vec!["api.example.com".to_string()]); let requirements = NetworkConstraints { domains: Some(domain_permissions([( "managed-blocked.example.com", @@ -351,13 +329,13 @@ fn deny_only_requirements_do_not_create_allow_constraints_in_full_access() { .expect("deny-only requirements should not constrain the allowlist"); assert_eq!( - spec.config.network.allowed_domains(), + spec.config.allowed_domains(), Some(vec!["api.example.com".to_string()]) ); assert_eq!(spec.constraints.allowed_domains, None); assert_eq!(spec.constraints.allowlist_expansion_enabled, None); assert_eq!( - spec.config.network.denied_domains(), + spec.config.denied_domains(), Some(vec!["managed-blocked.example.com".to_string()]) ); } @@ -365,9 +343,7 @@ fn deny_only_requirements_do_not_create_allow_constraints_in_full_access() { #[test] fn allow_only_requirements_do_not_create_deny_constraints_in_full_access() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_denied_domains(vec!["blocked.example.com".to_string()]); + config.set_denied_domains(vec!["blocked.example.com".to_string()]); let requirements = NetworkConstraints { domains: Some(domain_permissions([( "managed.example.com", @@ -384,11 +360,11 @@ fn allow_only_requirements_do_not_create_deny_constraints_in_full_access() { .expect("allow-only requirements should not constrain the denylist"); assert_eq!( - spec.config.network.allowed_domains(), + spec.config.allowed_domains(), Some(vec!["managed.example.com".to_string()]) ); assert_eq!( - spec.config.network.denied_domains(), + spec.config.denied_domains(), Some(vec!["blocked.example.com".to_string()]) ); assert_eq!(spec.constraints.denied_domains, None); @@ -398,9 +374,7 @@ fn allow_only_requirements_do_not_create_deny_constraints_in_full_access() { #[test] fn requirements_denied_domains_are_a_baseline_for_default_mode() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_denied_domains(vec!["blocked.example.com".to_string()]); + config.set_denied_domains(vec!["blocked.example.com".to_string()]); let requirements = NetworkConstraints { domains: Some(domain_permissions([( "managed-blocked.example.com", @@ -417,7 +391,7 @@ fn requirements_denied_domains_are_a_baseline_for_default_mode() { .expect("default mode should merge managed and user deny entries"); assert_eq!( - spec.config.network.denied_domains(), + spec.config.denied_domains(), Some(vec![ "managed-blocked.example.com".to_string(), "blocked.example.com".to_string() @@ -433,9 +407,7 @@ fn requirements_denied_domains_are_a_baseline_for_default_mode() { #[test] fn requirements_denylist_expansion_keeps_user_entries_mutable() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_denied_domains(vec!["blocked.example.com".to_string()]); + config.set_denied_domains(vec!["blocked.example.com".to_string()]); let requirements = NetworkConstraints { domains: Some(domain_permissions([( "managed-blocked.example.com", @@ -452,18 +424,18 @@ fn requirements_denylist_expansion_keeps_user_entries_mutable() { .expect("managed baseline should still allow user edits"); let mut candidate = spec.config.clone(); - candidate.network.upsert_domain_permission( + candidate.upsert_domain_permission( "blocked.example.com".to_string(), NetworkDomainPermission::Allow, normalize_host, ); assert_eq!( - candidate.network.allowed_domains(), + candidate.allowed_domains(), Some(vec!["blocked.example.com".to_string()]) ); assert_eq!( - candidate.network.denied_domains(), + candidate.denied_domains(), Some(vec!["managed-blocked.example.com".to_string()]) ); validate_policy_against_constraints(&candidate, &spec.constraints) diff --git a/codex-rs/core/src/config/permissions.rs b/codex-rs/core/src/config/permissions.rs index 9c6d3ed72519..1f0eb1e12d93 100644 --- a/codex-rs/core/src/config/permissions.rs +++ b/codex-rs/core/src/config/permissions.rs @@ -127,7 +127,7 @@ pub(crate) fn network_proxy_config_from_profile_network( // Profile `network.enabled` controls sandbox network access. Profiles may // provide proxy settings for the feature gate to consume when that network // access is enabled, but they do not start the managed proxy on their own. - config.network.enabled = false; + config.enabled = false; config } diff --git a/codex-rs/core/src/config/permissions_tests.rs b/codex-rs/core/src/config/permissions_tests.rs index 2c1f649042c2..9c78a1d5e21e 100644 --- a/codex-rs/core/src/config/permissions_tests.rs +++ b/codex-rs/core/src/config/permissions_tests.rs @@ -220,7 +220,7 @@ fn network_toml_overlays_unix_socket_permissions_by_path() { .apply_to_network_proxy_config(&mut config); assert_eq!( - config.network.unix_sockets, + config.unix_sockets, Some(codex_network_proxy::NetworkUnixSocketPermissions { entries: BTreeMap::from([ ( @@ -399,7 +399,7 @@ fn profile_network_proxy_config_keeps_proxy_disabled_for_bare_network_access() { ..Default::default() })); - assert!(!config.network.enabled); + assert!(!config.enabled); } #[test] @@ -417,11 +417,11 @@ fn profile_network_proxy_config_keeps_proxy_disabled_for_proxy_policy() { ..Default::default() })); - assert!(!config.network.enabled); - assert_eq!(config.network.proxy_url, "http://127.0.0.1:43128"); - assert!(!config.network.enable_socks5); + assert!(!config.enabled); + assert_eq!(config.proxy_url, "http://127.0.0.1:43128"); + assert!(!config.enable_socks5); assert_eq!( - config.network.domains, + config.domains, Some(codex_network_proxy::NetworkDomainPermissions { entries: vec![codex_network_proxy::NetworkDomainPermissionEntry { pattern: "openai.com".to_string(), diff --git a/codex-rs/core/src/guardian/tests.rs b/codex-rs/core/src/guardian/tests.rs index e23475d30e74..011573f0ae78 100644 --- a/codex-rs/core/src/guardian/tests.rs +++ b/codex-rs/core/src/guardian/tests.rs @@ -2975,10 +2975,8 @@ async fn guardian_review_session_config_clears_legacy_notify() { async fn guardian_review_session_config_uses_live_network_proxy_state() { let mut parent_config = test_config().await; let mut parent_network = NetworkProxyConfig::default(); - parent_network.network.enabled = true; - parent_network - .network - .set_allowed_domains(vec!["parent.example".to_string()]); + parent_network.enabled = true; + parent_network.set_allowed_domains(vec!["parent.example".to_string()]); parent_config.permissions.network = Some( NetworkProxySpec::from_config_and_constraints( parent_network, @@ -2989,10 +2987,8 @@ async fn guardian_review_session_config_uses_live_network_proxy_state() { ); let mut live_network = NetworkProxyConfig::default(); - live_network.network.enabled = true; - live_network - .network - .set_allowed_domains(vec!["github.com".to_string()]); + live_network.enabled = true; + live_network.set_allowed_domains(vec!["github.com".to_string()]); let guardian_config = build_guardian_review_session_config_for_test( &parent_config, diff --git a/codex-rs/core/src/network_proxy_loader.rs b/codex-rs/core/src/network_proxy_loader.rs index 62ed44ad81ce..049f55c40da4 100644 --- a/codex-rs/core/src/network_proxy_loader.rs +++ b/codex-rs/core/src/network_proxy_loader.rs @@ -162,14 +162,14 @@ fn apply_network_constraints(network: NetworkToml, constraints: &mut NetworkProx if let Some(domains) = network.domains.as_ref() { let mut config = NetworkProxyConfig::default(); if let Some(allowed_domains) = constraints.allowed_domains.take() { - config.network.set_allowed_domains(allowed_domains); + config.set_allowed_domains(allowed_domains); } if let Some(denied_domains) = constraints.denied_domains.take() { - config.network.set_denied_domains(denied_domains); + config.set_denied_domains(denied_domains); } overlay_network_domain_permissions(&mut config, domains); - constraints.allowed_domains = config.network.allowed_domains(); - constraints.denied_domains = config.network.denied_domains(); + constraints.allowed_domains = config.allowed_domains(); + constraints.denied_domains = config.denied_domains(); } if let Some(unix_sockets) = network.unix_sockets.as_ref() { let allow_unix_sockets = unix_sockets.allow_unix_sockets(); @@ -256,11 +256,11 @@ impl NetworkConfigAccumulator { }; mitm.validate_action_references(&actions) .map_err(anyhow::Error::msg)?; - self.config.network.mitm_hooks = mitm.to_runtime_hooks(Some(&actions)); + self.config.mitm_hooks = mitm.to_runtime_hooks(Some(&actions)); } - self.config.network.mitm = self.config.network.mode == NetworkMode::Limited - || !self.config.network.mitm_hooks.is_empty(); + self.config.mitm = + self.config.mode == NetworkMode::Limited || !self.config.mitm_hooks.is_empty(); Ok(self.config) } } @@ -310,9 +310,7 @@ fn upsert_network_domain( host: String, permission: codex_network_proxy::NetworkDomainPermission, ) { - config - .network - .upsert_domain_permission(host, permission, normalize_host); + config.upsert_domain_permission(host, permission, normalize_host); } fn is_user_controlled_layer(layer: &ConfigLayerSource) -> bool { diff --git a/codex-rs/core/src/network_proxy_loader_tests.rs b/codex-rs/core/src/network_proxy_loader_tests.rs index f7c7c2e45217..f2758e894613 100644 --- a/codex-rs/core/src/network_proxy_loader_tests.rs +++ b/codex-rs/core/src/network_proxy_loader_tests.rs @@ -57,14 +57,14 @@ default_permissions = "dev" .expect("higher layer should apply"); assert_eq!( - config.network.allowed_domains(), + config.allowed_domains(), Some(vec![ "lower.example.com".to_string(), "higher.example.com".to_string() ]) ); assert_eq!( - config.network.denied_domains(), + config.denied_domains(), Some(vec!["blocked.example.com".to_string()]) ); } @@ -108,13 +108,13 @@ default_permissions = "dev" .expect("higher layer should apply"); assert_eq!( - config.network.allowed_domains(), + config.allowed_domains(), Some(vec![ "other.example.com".to_string(), "shared.example.com".to_string() ]) ); - assert_eq!(config.network.denied_domains(), None); + assert_eq!(config.denied_domains(), None); } #[test] @@ -169,20 +169,20 @@ strip_request_headers = ["x-api-key"] .expect("higher layer should apply"); let config = accumulator.finish().expect("merged config should build"); - assert_eq!(config.network.mode, codex_network_proxy::NetworkMode::Full); - assert!(config.network.mitm); + assert_eq!(config.mode, codex_network_proxy::NetworkMode::Full); + assert!(config.mitm); assert_eq!( - config.network.allowed_domains(), + config.allowed_domains(), Some(vec![ "lower.example.com".to_string(), "higher.example.com".to_string() ]) ); - assert_eq!(config.network.mitm_hooks.len(), 1); - assert_eq!(config.network.mitm_hooks[0].host, "api.github.com"); - assert_eq!(config.network.mitm_hooks[0].matcher.methods, vec!["POST"]); + assert_eq!(config.mitm_hooks.len(), 1); + assert_eq!(config.mitm_hooks[0].host, "api.github.com"); + assert_eq!(config.mitm_hooks[0].matcher.methods, vec!["POST"]); assert_eq!( - config.network.mitm_hooks[0].actions.strip_request_headers, + config.mitm_hooks[0].actions.strip_request_headers, vec!["x-api-key"] ); } @@ -190,12 +190,8 @@ strip_request_headers = ["x-api-key"] #[test] fn execpolicy_network_rules_overlay_network_lists() { let mut config = NetworkProxyConfig::default(); - config - .network - .set_allowed_domains(vec!["config.example.com".to_string()]); - config - .network - .set_denied_domains(vec!["blocked.example.com".to_string()]); + config.set_allowed_domains(vec!["config.example.com".to_string()]); + config.set_denied_domains(vec!["blocked.example.com".to_string()]); let mut exec_policy = Policy::empty(); exec_policy @@ -218,14 +214,14 @@ fn execpolicy_network_rules_overlay_network_lists() { apply_exec_policy_network_rules(&mut config, &exec_policy); assert_eq!( - config.network.allowed_domains(), + config.allowed_domains(), Some(vec![ "config.example.com".to_string(), "blocked.example.com".to_string() ]) ); assert_eq!( - config.network.denied_domains(), + config.denied_domains(), Some(vec!["api.example.com".to_string()]) ); } @@ -270,7 +266,7 @@ async fn malformed_custom_rules_preserve_managed_denied_domain() { .expect("proxy state should tolerate malformed custom rules"); assert_eq!( - state.config.network.denied_domains(), + state.config.denied_domains(), Some(vec!["blocked.example.com".to_string()]) ); } @@ -468,7 +464,7 @@ fn config_from_layers_resolves_inherited_profiles_across_layers() { config_from_layers(&layers, &Policy::empty()).expect("inherited profiles should load"); assert_eq!( - config.network.allowed_domains(), + config.allowed_domains(), Some(vec![ "base.example.com".to_string(), "child.example.com".to_string(), @@ -507,10 +503,10 @@ fn config_from_layers_normalizes_profile_network_domains_before_merging_layers() .expect("network domain layer precedence should load"); assert_eq!( - config.network.allowed_domains(), + config.allowed_domains(), Some(vec!["example.com".to_string()]) ); - assert_eq!(config.network.denied_domains(), None); + assert_eq!(config.denied_domains(), None); } #[test] @@ -542,8 +538,8 @@ fn config_from_layers_uses_only_the_final_selected_profile_network() { let config = config_from_layers(&layers, &Policy::empty()) .expect("final built-in profile selection should load"); - assert_eq!(config.network.allowed_domains(), None); - assert_eq!(config.network.denied_domains(), None); + assert_eq!(config.allowed_domains(), None); + assert_eq!(config.denied_domains(), None); } #[test] diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 5c6d3d7bb208..696a8adbd839 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -809,7 +809,7 @@ async fn start_managed_network_proxy_applies_execpolicy_network_rules() -> anyho let current_cfg = started_proxy.proxy().current_cfg().await?; assert_eq!( - current_cfg.network.allowed_domains(), + current_cfg.allowed_domains(), Some(vec!["example.com".to_string()]) ); Ok(()) @@ -854,7 +854,7 @@ async fn start_managed_network_proxy_ignores_invalid_execpolicy_network_rules() let current_cfg = started_proxy.proxy().current_cfg().await?; assert_eq!( - current_cfg.network.allowed_domains(), + current_cfg.allowed_domains(), Some(vec!["managed.example.com".to_string()]) ); Ok(()) @@ -895,7 +895,7 @@ async fn managed_network_proxy_decider_survives_full_access_start() -> anyhow::R let spec = spec.recompute_for_permission_profile(&PermissionProfile::workspace_write())?; spec.apply_to_started_proxy(&started_proxy).await?; let current_cfg = started_proxy.proxy().current_cfg().await?; - assert_eq!(current_cfg.network.allowed_domains(), None); + assert_eq!(current_cfg.allowed_domains(), None); use tokio::io::AsyncReadExt as _; use tokio::io::AsyncWriteExt as _; @@ -934,9 +934,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow let initial_permission_profile = PermissionProfile::workspace_write(); let mut network_config = NetworkProxyConfig::default(); - network_config - .network - .set_allowed_domains(vec!["evil.com".to_string()]); + network_config.set_allowed_domains(vec!["evil.com".to_string()]); let requirements = NetworkConstraints { domains: Some(NetworkDomainPermissionsToml { entries: std::collections::BTreeMap::from([( @@ -962,12 +960,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow ) .await?; assert_eq!( - started_proxy - .proxy() - .current_cfg() - .await? - .network - .allowed_domains(), + started_proxy.proxy().current_cfg().await?.allowed_domains(), Some(vec!["*.example.com".to_string(), "evil.com".to_string()]) ); @@ -1006,12 +999,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow .load_full() .expect("managed network proxy should be present"); assert_eq!( - started_proxy - .proxy() - .current_cfg() - .await? - .network - .allowed_domains(), + started_proxy.proxy().current_cfg().await?.allowed_domains(), Some(vec!["*.example.com".to_string()]) ); diff --git a/codex-rs/core/src/tasks/mod.rs b/codex-rs/core/src/tasks/mod.rs index c6d9d2990c2a..7a930bb5789c 100644 --- a/codex-rs/core/src/tasks/mod.rs +++ b/codex-rs/core/src/tasks/mod.rs @@ -637,7 +637,7 @@ impl Session { let network_proxy_active = match network_proxy.as_ref() { Some(started_network_proxy) => { match started_network_proxy.proxy().current_cfg().await { - Ok(config) => config.network.enabled, + Ok(config) => config.enabled, Err(err) => { warn!( "failed to read managed network proxy state for turn metrics: {err:#}" diff --git a/codex-rs/network-proxy/src/attribution_tests.rs b/codex-rs/network-proxy/src/attribution_tests.rs index e28110263c0b..fcb3c3771e15 100644 --- a/codex-rs/network-proxy/src/attribution_tests.rs +++ b/codex-rs/network-proxy/src/attribution_tests.rs @@ -1,6 +1,6 @@ use super::BindConnectionAttribution; use super::write_attribution_frame; -use crate::config::NetworkProxySettings; +use crate::config::NetworkProxyConfig; use crate::runtime::network_proxy_state_for_policy; use crate::state::NetworkProxyState; use pretty_assertions::assert_eq; @@ -28,9 +28,7 @@ fn attribution_frame_has_bounded_binary_prefix() -> io::Result<()> { #[tokio::test] async fn framed_connection_receives_registered_execution_state() -> Result<(), BoxError> { - let state = Arc::new(network_proxy_state_for_policy( - NetworkProxySettings::default(), - )); + let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default())); state.register_execution("token-1", "local", "execution-1"); let listener = TcpListener::bind("127.0.0.1:0").await?; diff --git a/codex-rs/network-proxy/src/config.rs b/codex-rs/network-proxy/src/config.rs index 30cdfa7b2db6..3461c696acb5 100644 --- a/codex-rs/network-proxy/src/config.rs +++ b/codex-rs/network-proxy/src/config.rs @@ -15,19 +15,6 @@ use url::Url; use crate::mitm_hook::MitmHookConfig; -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] -pub struct NetworkProxyConfig { - #[serde(default)] - pub network: NetworkProxySettings, -} - -impl NetworkProxyConfig { - pub fn set_credential_broker_enabled(&mut self, enabled: bool) { - self.network.credential_broker = enabled; - self.network.mitm |= enabled; - } -} - /// Variant order encodes effective precedence for duplicate patterns: /// `None < Allow < Deny`, so deny wins over allow when entries conflict. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] @@ -125,7 +112,7 @@ pub struct NetworkUnixSocketPermissions { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(default)] -pub struct NetworkProxySettings { +pub struct NetworkProxyConfig { #[serde(default)] pub enabled: bool, #[serde(default = "default_proxy_url")] @@ -156,7 +143,7 @@ pub struct NetworkProxySettings { pub mitm_hooks: Vec, } -impl Default for NetworkProxySettings { +impl Default for NetworkProxyConfig { fn default() -> Self { Self { enabled: false, @@ -179,7 +166,12 @@ impl Default for NetworkProxySettings { } } -impl NetworkProxySettings { +impl NetworkProxyConfig { + pub fn set_credential_broker_enabled(&mut self, enabled: bool) { + self.credential_broker = enabled; + self.mitm |= enabled; + } + pub fn allowed_domains(&self) -> Option> { self.domain_entries(NetworkDomainPermission::Allow) } @@ -340,7 +332,7 @@ fn clamp_non_loopback( pub(crate) fn clamp_bind_addrs( http_addr: SocketAddr, socks_addr: SocketAddr, - cfg: &NetworkProxySettings, + cfg: &NetworkProxyConfig, ) -> (SocketAddr, SocketAddr) { let http_addr = clamp_non_loopback( http_addr, @@ -416,7 +408,7 @@ impl ValidatedUnixSocketPath { } pub(crate) fn validate_unix_socket_allowlist_paths(cfg: &NetworkProxyConfig) -> Result<()> { - for (index, socket_path) in cfg.network.allow_unix_sockets().iter().enumerate() { + for (index, socket_path) in cfg.allow_unix_sockets().iter().enumerate() { ValidatedUnixSocketPath::parse(socket_path) .with_context(|| format!("invalid network.allow_unix_sockets[{index}]"))?; } @@ -426,11 +418,11 @@ pub(crate) fn validate_unix_socket_allowlist_paths(cfg: &NetworkProxyConfig) -> pub fn resolve_runtime(cfg: &NetworkProxyConfig) -> Result { validate_unix_socket_allowlist_paths(cfg)?; - let http_addr = resolve_addr(&cfg.network.proxy_url, /*default_port*/ 3128) - .with_context(|| format!("invalid network.proxy_url: {}", cfg.network.proxy_url))?; - let socks_addr = resolve_addr(&cfg.network.socks_url, /*default_port*/ 8081) - .with_context(|| format!("invalid network.socks_url: {}", cfg.network.socks_url))?; - let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, &cfg.network); + let http_addr = resolve_addr(&cfg.proxy_url, /*default_port*/ 3128) + .with_context(|| format!("invalid network.proxy_url: {}", cfg.proxy_url))?; + let socks_addr = resolve_addr(&cfg.socks_url, /*default_port*/ 8081) + .with_context(|| format!("invalid network.socks_url: {}", cfg.socks_url))?; + let (http_addr, socks_addr) = clamp_bind_addrs(http_addr, socks_addr, cfg); Ok(RuntimeConfig { http_addr, @@ -575,8 +567,8 @@ mod tests { use pretty_assertions::assert_eq; - fn settings_with_unix_sockets(unix_sockets: &[&str]) -> NetworkProxySettings { - let mut settings = NetworkProxySettings::default(); + fn settings_with_unix_sockets(unix_sockets: &[&str]) -> NetworkProxyConfig { + let mut settings = NetworkProxyConfig::default(); if !unix_sockets.is_empty() { settings.set_allow_unix_sockets( unix_sockets @@ -591,8 +583,8 @@ mod tests { #[test] fn network_proxy_settings_default_matches_local_use_baseline() { assert_eq!( - NetworkProxySettings::default(), - NetworkProxySettings { + NetworkProxyConfig::default(), + NetworkProxyConfig { enabled: false, proxy_url: "http://127.0.0.1:3128".to_string(), enable_socks5: true, @@ -614,26 +606,19 @@ mod tests { } #[test] - fn partial_network_config_uses_struct_defaults_for_missing_fields() { - let config: NetworkProxyConfig = serde_json::from_str( - r#"{ - "network": { - "enabled": true - } - }"#, - ) - .unwrap(); - let expected = NetworkProxySettings { + fn network_proxy_config_uses_struct_defaults_for_missing_fields() { + let config: NetworkProxyConfig = serde_json::from_str(r#"{ "enabled": true }"#).unwrap(); + let expected = NetworkProxyConfig { enabled: true, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; - assert_eq!(config.network, expected); + assert_eq!(config, expected); } #[test] fn set_allowed_domains_preserves_existing_deny_for_same_pattern() { - let mut settings = NetworkProxySettings::default(); + let mut settings = NetworkProxyConfig::default(); settings.set_denied_domains(vec!["example.com".to_string()]); settings.set_allowed_domains(vec!["example.com".to_string()]); @@ -647,36 +632,34 @@ mod tests { #[test] fn network_domain_permissions_serialize_to_effective_map_shape() { - let mut settings = NetworkProxySettings::default(); + let mut settings = NetworkProxyConfig::default(); settings.set_denied_domains(vec!["example.com".to_string()]); settings.set_allowed_domains(vec!["example.com".to_string()]); - let config = NetworkProxyConfig { network: settings }; + let config = settings; let value = serde_json::to_value(&config).unwrap(); assert_eq!( value, serde_json::json!({ - "network": { - "enabled": false, - "proxy_url": "http://127.0.0.1:3128", - "enable_socks5": true, - "socks_url": "http://127.0.0.1:8081", - "enable_socks5_udp": true, - "allow_upstream_proxy": true, - "dangerously_allow_non_loopback_proxy": false, - "dangerously_allow_all_unix_sockets": false, - "mode": "full", - "domains": { - "example.com": "deny", - }, - "unix_sockets": null, - "allow_local_binding": false, - "mitm": false, - "credential_broker": false, - "dangerously_allow_plaintext_credential_injection": false, - "mitm_hooks": [], - } + "enabled": false, + "proxy_url": "http://127.0.0.1:3128", + "enable_socks5": true, + "socks_url": "http://127.0.0.1:8081", + "enable_socks5_udp": true, + "allow_upstream_proxy": true, + "dangerously_allow_non_loopback_proxy": false, + "dangerously_allow_all_unix_sockets": false, + "mode": "full", + "domains": { + "example.com": "deny", + }, + "unix_sockets": null, + "allow_local_binding": false, + "mitm": false, + "credential_broker": false, + "dangerously_allow_plaintext_credential_injection": false, + "mitm_hooks": [], }) ); } @@ -815,7 +798,7 @@ mod tests { #[test] fn clamp_bind_addrs_allows_non_loopback_when_enabled() { - let cfg = NetworkProxySettings { + let cfg = NetworkProxyConfig { dangerously_allow_non_loopback_proxy: true, ..Default::default() }; @@ -846,7 +829,7 @@ mod tests { #[test] fn clamp_bind_addrs_forces_loopback_when_all_unix_sockets_enabled() { - let cfg = NetworkProxySettings { + let cfg = NetworkProxyConfig { dangerously_allow_non_loopback_proxy: true, dangerously_allow_all_unix_sockets: true, ..Default::default() @@ -862,9 +845,7 @@ mod tests { #[test] fn resolve_runtime_rejects_relative_allow_unix_sockets_entries() { - let cfg = NetworkProxyConfig { - network: settings_with_unix_sockets(&["relative.sock"]), - }; + let cfg = settings_with_unix_sockets(&["relative.sock"]); let err = match resolve_runtime(&cfg) { Ok(runtime) => panic!( @@ -881,9 +862,7 @@ mod tests { #[test] fn resolve_runtime_accepts_unix_style_absolute_allow_unix_sockets_entries() { - let cfg = NetworkProxyConfig { - network: settings_with_unix_sockets(&["/private/tmp/example.sock"]), - }; + let cfg = settings_with_unix_sockets(&["/private/tmp/example.sock"]); assert!( resolve_runtime(&cfg).is_ok(), diff --git a/codex-rs/network-proxy/src/connect_policy.rs b/codex-rs/network-proxy/src/connect_policy.rs index b9425db7992a..ed0e56f733a8 100644 --- a/codex-rs/network-proxy/src/connect_policy.rs +++ b/codex-rs/network-proxy/src/connect_policy.rs @@ -107,7 +107,7 @@ impl TargetPolicy { #[cfg(test)] mod tests { use super::*; - use crate::config::NetworkProxySettings; + use crate::config::NetworkProxyConfig; use crate::state::network_proxy_state_for_policy; use rama_net::address::HostWithPort; use std::net::Ipv4Addr; @@ -120,7 +120,7 @@ mod tests { .expect("bind local listener"); let target = listener.local_addr().expect("local addr"); let connector = TargetCheckedTcpConnector::new(Arc::new(network_proxy_state_for_policy( - NetworkProxySettings::default(), + NetworkProxyConfig::default(), ))); let request: rama_tcp::client::Request = @@ -142,9 +142,9 @@ mod tests { .expect("bind local listener"); let target = listener.local_addr().expect("local addr"); let connector = TargetCheckedTcpConnector::new(Arc::new(network_proxy_state_for_policy( - NetworkProxySettings { + NetworkProxyConfig { allow_local_binding: true, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }, ))); diff --git a/codex-rs/network-proxy/src/http_proxy.rs b/codex-rs/network-proxy/src/http_proxy.rs index 59cad9c20a5e..e81640b6e0e1 100644 --- a/codex-rs/network-proxy/src/http_proxy.rs +++ b/codex-rs/network-proxy/src/http_proxy.rs @@ -1087,7 +1087,7 @@ mod tests { use super::*; use crate::config::NetworkMode; - use crate::config::NetworkProxySettings; + use crate::config::NetworkProxyConfig; use crate::runtime::network_proxy_state_for_policy; use pretty_assertions::assert_eq; use rama_http::Method; @@ -1106,7 +1106,7 @@ mod tests { #[tokio::test] async fn http_connect_accept_blocks_in_limited_mode() { let policy = { - let mut policy = NetworkProxySettings::default(); + let mut policy = NetworkProxyConfig::default(); policy.set_allowed_domains(vec!["example.com".to_string()]); policy }; @@ -1136,9 +1136,9 @@ mod tests { #[tokio::test] async fn http_connect_accept_allows_allowlisted_host_in_full_mode() { let policy = { - let mut policy = NetworkProxySettings { + let mut policy = NetworkProxyConfig { allow_local_binding: true, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; policy.set_allowed_domains(vec!["example.com".to_string()]); policy @@ -1163,9 +1163,7 @@ mod tests { #[tokio::test] async fn http_connect_accept_passes_environment_id_to_decider() { - let state = Arc::new(network_proxy_state_for_policy( - NetworkProxySettings::default(), - )); + let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default())); let seen_environment_id = Arc::new(Mutex::new(None)); let decider: Arc = Arc::new({ let seen_environment_id = seen_environment_id.clone(); @@ -1202,10 +1200,10 @@ mod tests { #[tokio::test] async fn http_connect_accept_defers_brokered_host_mitm_until_protocol_detection() { - let mut policy = NetworkProxySettings { + let mut policy = NetworkProxyConfig { credential_broker: true, mitm: true, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; policy.set_allowed_domains(vec!["github.com".to_string()]); let state = Arc::new(network_proxy_state_for_policy(policy)); @@ -1235,10 +1233,10 @@ mod tests { #[tokio::test] async fn plaintext_credential_injection_requires_explicit_opt_in() { let real_token = "ghp-real"; - let mut disabled_network = NetworkProxySettings { + let mut disabled_network = NetworkProxyConfig { credential_broker: true, mitm: true, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; disabled_network.set_allowed_domains(vec!["api.github.com".to_string()]); let disabled_state = Arc::new(network_proxy_state_for_policy(disabled_network)); @@ -1263,11 +1261,11 @@ mod tests { Some(&HeaderValue::from_str(&format!("Bearer {dummy_token}")).unwrap()) ); - let mut enabled_network = NetworkProxySettings { + let mut enabled_network = NetworkProxyConfig { credential_broker: true, dangerously_allow_plaintext_credential_injection: true, mitm: true, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; enabled_network.set_allowed_domains(vec!["api.github.com".to_string()]); let enabled_state = Arc::new(network_proxy_state_for_policy(enabled_network)); @@ -1295,7 +1293,7 @@ mod tests { #[tokio::test] async fn http_connect_accept_blocks_hooked_host_in_full_mode_without_mitm_state() { - let mut policy = NetworkProxySettings { + let mut policy = NetworkProxyConfig { mitm: true, mitm_hooks: vec![crate::mitm_hook::MitmHookConfig { host: "api.github.com".to_string(), @@ -1352,10 +1350,10 @@ mod tests { }); let state = Arc::new(network_proxy_state_for_policy({ - let mut network = NetworkProxySettings { + let mut network = NetworkProxyConfig { credential_broker: true, mitm: true, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; network.set_allowed_domains(vec!["127.0.0.1".to_string()]); network.allow_local_binding = true; @@ -1416,9 +1414,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn http_plain_proxy_blocks_unix_socket_when_method_not_allowed() { - let state = Arc::new(network_proxy_state_for_policy( - NetworkProxySettings::default(), - )); + let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default())); state .set_network_mode(NetworkMode::Limited) .await @@ -1447,9 +1443,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn http_plain_proxy_rejects_unix_socket_when_not_allowlisted() { - let state = Arc::new(network_proxy_state_for_policy( - NetworkProxySettings::default(), - )); + let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default())); let mut req = Request::builder() .method(Method::GET) @@ -1480,7 +1474,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn http_plain_proxy_attempts_allowed_unix_socket_proxy() { let state = Arc::new(network_proxy_state_for_policy({ - let mut network = NetworkProxySettings::default(); + let mut network = NetworkProxyConfig::default(); network.set_allow_unix_sockets(vec!["/tmp/test.sock".to_string()]); network })); @@ -1504,7 +1498,7 @@ mod tests { #[tokio::test] async fn http_connect_accept_denies_denylisted_host() { let policy = { - let mut policy = NetworkProxySettings::default(); + let mut policy = NetworkProxyConfig::default(); policy.set_allowed_domains(vec!["**.openai.com".to_string()]); policy.set_denied_domains(vec!["api.openai.com".to_string()]); policy @@ -1533,9 +1527,7 @@ mod tests { #[tokio::test] async fn http_plain_proxy_rejects_absolute_uri_host_header_mismatch() { - let state = Arc::new(network_proxy_state_for_policy( - NetworkProxySettings::default(), - )); + let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default())); let mut req = Request::builder() .method(Method::GET) .uri("http://raw.githubusercontent.com/openai/codex/main/README.md") diff --git a/codex-rs/network-proxy/src/lib.rs b/codex-rs/network-proxy/src/lib.rs index 6bf6d8eb3892..753a0b0f4e39 100644 --- a/codex-rs/network-proxy/src/lib.rs +++ b/codex-rs/network-proxy/src/lib.rs @@ -78,7 +78,6 @@ pub use runtime::NetworkProxyState; pub use state::NetworkProxyAuditMetadata; pub use state::NetworkProxyConstraintError; pub use state::NetworkProxyConstraints; -pub use state::PartialNetworkConfig; pub use state::PartialNetworkProxyConfig; pub use state::build_config_state; pub use state::validate_policy_against_constraints; diff --git a/codex-rs/network-proxy/src/mitm_hook.rs b/codex-rs/network-proxy/src/mitm_hook.rs index 6262dcd4ff7c..7ad370aafffb 100644 --- a/codex-rs/network-proxy/src/mitm_hook.rs +++ b/codex-rs/network-proxy/src/mitm_hook.rs @@ -169,12 +169,12 @@ pub enum HookEvaluation { } pub(crate) fn validate_mitm_hook_config(config: &NetworkProxyConfig) -> Result<()> { - let hooks = &config.network.mitm_hooks; + let hooks = &config.mitm_hooks; if hooks.is_empty() { return Ok(()); } - if !config.network.mitm { + if !config.mitm { return Err(anyhow!("network.mitm_hooks requires network.mitm = true")); } @@ -274,7 +274,7 @@ where validate_mitm_hook_config(config)?; let mut hooks_by_host = MitmHooksByHost::new(); - for hook in &config.network.mitm_hooks { + for hook in &config.mitm_hooks { let host = normalize_hook_host(&hook.host)?; let methods = normalize_methods(&hook.matcher.methods)?; let path_prefixes = compile_path_matchers(&hook.matcher.path_prefixes)?; @@ -644,7 +644,7 @@ fn parse_secret_file(path: &str) -> Result { mod tests { use super::*; use crate::NetworkMode; - use crate::config::NetworkProxySettings; + use crate::config::NetworkProxyConfig; use pretty_assertions::assert_eq; use rama_http::Body; use rama_http::Method; @@ -652,11 +652,9 @@ mod tests { fn base_config() -> NetworkProxyConfig { NetworkProxyConfig { - network: NetworkProxySettings { - mitm: true, - mode: NetworkMode::Limited, - ..NetworkProxySettings::default() - }, + mitm: true, + mode: NetworkMode::Limited, + ..NetworkProxyConfig::default() } } @@ -683,8 +681,8 @@ mod tests { #[test] fn validate_requires_mitm_for_hooks() { let mut config = base_config(); - config.network.mitm = false; - config.network.mitm_hooks = vec![github_hook()]; + config.mitm = false; + config.mitm_hooks = vec![github_hook()]; let err = validate_mitm_hook_config(&config).expect_err("hooks require mitm"); assert!( @@ -696,8 +694,8 @@ mod tests { #[test] fn validate_allows_hooks_in_full_mode() { let mut config = base_config(); - config.network.mode = NetworkMode::Full; - config.network.mitm_hooks = vec![github_hook()]; + config.mode = NetworkMode::Full; + config.mitm_hooks = vec![github_hook()]; validate_mitm_hook_config(&config).expect("hooks should be allowed in full mode"); } @@ -709,7 +707,7 @@ mod tests { hook.matcher.body = Some(MitmHookBodyConfig(serde_json::json!({ "repository": "openai/codex" }))); - config.network.mitm_hooks = vec![hook]; + config.mitm_hooks = vec![hook]; let err = validate_mitm_hook_config(&config).expect_err("body matchers are reserved"); assert!(err.to_string().contains("match.body is reserved")); @@ -721,7 +719,7 @@ mod tests { let mut hook = github_hook(); hook.actions.inject_request_headers[0].secret_env_var = None; hook.actions.inject_request_headers[0].secret_file = Some("token.txt".to_string()); - config.network.mitm_hooks = vec![hook]; + config.mitm_hooks = vec![hook]; let err = validate_mitm_hook_config(&config).expect_err("secret file must be absolute"); assert!(format!("{err:#}").contains("secret_file must be an absolute path")); @@ -732,7 +730,7 @@ mod tests { let mut config = base_config(); let mut hook = github_hook(); hook.actions.inject_request_headers[0].secret_file = Some("/tmp/github-token".to_string()); - config.network.mitm_hooks = vec![hook]; + config.mitm_hooks = vec![hook]; let err = validate_mitm_hook_config(&config).expect_err("dual secret sources invalid"); assert!(format!("{err:#}").contains("exactly one of secret_env_var or secret_file")); @@ -741,7 +739,7 @@ mod tests { #[test] fn compile_resolves_env_backed_injected_headers() { let mut config = base_config(); - config.network.mitm_hooks = vec![github_hook()]; + config.mitm_hooks = vec![github_hook()]; let hooks = compile_mitm_hooks_with_resolvers( &config, @@ -772,7 +770,7 @@ mod tests { hook.actions.inject_request_headers[0].secret_env_var = None; hook.actions.inject_request_headers[0].secret_file = Some(secret_file.path().display().to_string()); - config.network.mitm_hooks = vec![hook]; + config.mitm_hooks = vec![hook]; let hooks = compile_mitm_hooks(&config).unwrap(); let compiled = hooks.get("api.github.com").unwrap(); @@ -789,7 +787,7 @@ mod tests { first.matcher.path_prefixes = vec!["/repos/openai/".to_string()]; let mut second = github_hook(); second.actions.inject_request_headers[0].prefix = Some("Token ".to_string()); - config.network.mitm_hooks = vec![first, second]; + config.mitm_hooks = vec![first, second]; let hooks = compile_mitm_hooks_with_resolvers( &config, @@ -827,7 +825,7 @@ mod tests { "x-github-api-version".to_string(), vec!["2022-11-28".to_string()], )]); - config.network.mitm_hooks = vec![hook]; + config.mitm_hooks = vec![hook]; let hooks = compile_mitm_hooks_with_resolvers( &config, @@ -861,7 +859,7 @@ mod tests { "x-github-api-version".to_string(), vec!["pattern:2022*preview".to_string()], )]); - config.network.mitm_hooks = vec![hook]; + config.mitm_hooks = vec![hook]; let hooks = compile_mitm_hooks_with_resolvers( &config, @@ -889,7 +887,7 @@ mod tests { let mut config = base_config(); let mut hook = github_hook(); hook.matcher.path_prefixes = vec!["pattern:/repos/[".to_string()]; - config.network.mitm_hooks = vec![hook]; + config.mitm_hooks = vec![hook]; let err = validate_mitm_hook_config(&config).expect_err("invalid glob should fail"); assert!(format!("{err:#}").contains("invalid glob pattern")); @@ -900,7 +898,7 @@ mod tests { let mut config = base_config(); let mut hook = github_hook(); hook.matcher.path_prefixes = vec!["pattern:/repos/*/codex/issues*".to_string()]; - config.network.mitm_hooks = vec![hook]; + config.mitm_hooks = vec![hook]; let hooks = compile_mitm_hooks_with_resolvers( &config, @@ -930,7 +928,7 @@ mod tests { "x-github-api-version".to_string(), vec!["2022-11-28[preview]".to_string()], )]); - config.network.mitm_hooks = vec![hook]; + config.mitm_hooks = vec![hook]; let hooks = compile_mitm_hooks_with_resolvers( &config, @@ -973,7 +971,7 @@ mod tests { "x-github-api-version".to_string(), vec!["literal:pattern:*".to_string()], )]); - config.network.mitm_hooks = vec![hook]; + config.mitm_hooks = vec![hook]; let hooks = compile_mitm_hooks_with_resolvers( &config, @@ -1011,7 +1009,7 @@ mod tests { let mut config = base_config(); let mut hook = github_hook(); hook.matcher.query = BTreeMap::from([("state".to_string(), vec!["open".to_string()])]); - config.network.mitm_hooks = vec![hook]; + config.mitm_hooks = vec![hook]; let hooks = compile_mitm_hooks_with_resolvers( &config, diff --git a/codex-rs/network-proxy/src/mitm_tests.rs b/codex-rs/network-proxy/src/mitm_tests.rs index 18e650baa3a2..31e9fc8aaf11 100644 --- a/codex-rs/network-proxy/src/mitm_tests.rs +++ b/codex-rs/network-proxy/src/mitm_tests.rs @@ -1,6 +1,6 @@ use super::*; -use crate::config::NetworkProxySettings; +use crate::config::NetworkProxyConfig; use crate::reasons::REASON_METHOD_NOT_ALLOWED; use crate::reasons::REASON_MITM_HOOK_DENIED; use crate::reasons::REASON_NOT_ALLOWED_LOCAL; @@ -139,7 +139,7 @@ fn policy_ctx( #[tokio::test] async fn mitm_policy_blocks_disallowed_method_and_records_telemetry() { let app_state = Arc::new(network_proxy_state_for_policy({ - let mut network = NetworkProxySettings::default(); + let mut network = NetworkProxyConfig::default(); network.set_allowed_domains(vec!["example.com".to_string()]); network })); @@ -178,7 +178,7 @@ async fn mitm_policy_blocks_disallowed_method_and_records_telemetry() { #[tokio::test] async fn mitm_policy_rejects_host_mismatch() { let app_state = Arc::new(network_proxy_state_for_policy({ - let mut network = NetworkProxySettings::default(); + let mut network = NetworkProxyConfig::default(); network.set_allowed_domains(vec!["example.com".to_string()]); network })); @@ -207,7 +207,7 @@ async fn mitm_policy_rejects_host_mismatch() { #[tokio::test] async fn mitm_policy_rechecks_local_private_target_after_connect() { let app_state = Arc::new(network_proxy_state_for_policy({ - let mut network = NetworkProxySettings::default(); + let mut network = NetworkProxyConfig::default(); network.set_allowed_domains(vec!["example.com".to_string()]); network.allow_local_binding = false; network @@ -247,11 +247,11 @@ async fn mitm_policy_allows_matching_hooked_write_in_full_mode() { hook.actions.inject_request_headers[0].secret_env_var = None; hook.actions.inject_request_headers[0].secret_file = Some(secret_file.path().display().to_string()); - let mut network = NetworkProxySettings { + let mut network = NetworkProxyConfig { mitm: true, mitm_hooks: vec![hook], mode: NetworkMode::Full, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; network.set_allowed_domains(vec!["api.github.com".to_string()]); let app_state = Arc::new(network_proxy_state_for_policy(network)); @@ -281,11 +281,11 @@ async fn mitm_policy_allows_matching_hooked_write_in_full_mode() { async fn mitm_policy_blocks_matching_hooked_write_in_limited_mode() { let mut hook = github_write_hook(); hook.actions.inject_request_headers.clear(); - let mut network = NetworkProxySettings { + let mut network = NetworkProxyConfig { mitm: true, mitm_hooks: vec![hook], mode: NetworkMode::Limited, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; network.set_allowed_domains(vec!["api.github.com".to_string()]); let app_state = Arc::new(network_proxy_state_for_policy(network)); @@ -329,11 +329,11 @@ async fn mitm_policy_blocks_hook_miss_for_hooked_host_and_records_telemetry_in_f hook.actions.inject_request_headers[0].secret_env_var = None; hook.actions.inject_request_headers[0].secret_file = Some(secret_file.path().display().to_string()); - let mut network = NetworkProxySettings { + let mut network = NetworkProxyConfig { mitm: true, mitm_hooks: vec![hook], mode: NetworkMode::Full, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; network.set_allowed_domains(vec!["api.github.com".to_string()]); let app_state = Arc::new(network_proxy_state_for_policy(network)); diff --git a/codex-rs/network-proxy/src/network_policy.rs b/codex-rs/network-proxy/src/network_policy.rs index 956aa6f682df..05d9f06944fe 100644 --- a/codex-rs/network-proxy/src/network_policy.rs +++ b/codex-rs/network-proxy/src/network_policy.rs @@ -554,7 +554,6 @@ mod tests { use super::*; use crate::config::NetworkMode; use crate::config::NetworkProxyConfig; - use crate::config::NetworkProxySettings; use crate::reasons::REASON_DENIED; use crate::reasons::REASON_METHOD_NOT_ALLOWED; use crate::reasons::REASON_NOT_ALLOWED; @@ -595,12 +594,12 @@ mod tests { } fn state_with_metadata(metadata: NetworkProxyAuditMetadata) -> NetworkProxyState { - let network = NetworkProxySettings { + let network = NetworkProxyConfig { enabled: true, mode: NetworkMode::Full, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; - let config = NetworkProxyConfig { network }; + let config = network; let state = build_config_state(config, NetworkProxyConstraints::default()).unwrap(); let reloader = Arc::new(StaticReloader { state: state.clone(), @@ -628,7 +627,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn evaluate_host_policy_emits_domain_event_for_decider_allow_override() { - let state = network_proxy_state_for_policy(NetworkProxySettings::default()); + let state = network_proxy_state_for_policy(NetworkProxyConfig::default()); let calls = Arc::new(AtomicUsize::new(0)); let decider: Arc = Arc::new({ let calls = calls.clone(); @@ -697,7 +696,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn evaluate_host_policy_emits_execution_id_for_baseline_allow() { let state = network_proxy_state_for_policy({ - let mut network = NetworkProxySettings::default(); + let mut network = NetworkProxyConfig::default(); network.set_allowed_domains(vec!["example.com".to_string()]); network }); @@ -737,7 +736,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn evaluate_host_policy_emits_domain_event_for_baseline_deny() { let state = network_proxy_state_for_policy({ - let mut network = NetworkProxySettings::default(); + let mut network = NetworkProxyConfig::default(); network.set_allowed_domains(vec!["example.com".to_string()]); network.set_denied_domains(vec!["blocked.com".to_string()]); network @@ -789,7 +788,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn evaluate_host_policy_emits_domain_event_for_decider_ask() { - let state = network_proxy_state_for_policy(NetworkProxySettings::default()); + let state = network_proxy_state_for_policy(NetworkProxyConfig::default()); let decider: Arc = Arc::new(|_req| async { NetworkDecision::ask(REASON_NOT_ALLOWED) }); let request = NetworkPolicyRequest::new(NetworkPolicyRequestArgs { @@ -876,7 +875,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn emit_block_decision_audit_event_emits_non_domain_event() { - let state = network_proxy_state_for_policy(NetworkProxySettings::default()); + let state = network_proxy_state_for_policy(NetworkProxyConfig::default()); let (_, events) = capture_events(|| async { emit_block_decision_audit_event( @@ -925,7 +924,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn evaluate_host_policy_still_denies_not_allowed_local_without_decider_override() { let state = network_proxy_state_for_policy({ - let mut network = NetworkProxySettings::default(); + let mut network = NetworkProxyConfig::default(); network.set_allowed_domains(vec!["example.com".to_string()]); network.allow_local_binding = false; network diff --git a/codex-rs/network-proxy/src/proxy.rs b/codex-rs/network-proxy/src/proxy.rs index 476c68aca8bb..3e6a1dbe5309 100644 --- a/codex-rs/network-proxy/src/proxy.rs +++ b/codex-rs/network-proxy/src/proxy.rs @@ -183,55 +183,48 @@ impl NetworkProxyBuilder { .set_blocked_request_observer(self.blocked_request_observer.clone()) .await; let current_cfg = state.current_cfg().await?; - let (requested_http_addr, requested_socks_addr, reserved_listeners) = if self - .managed_by_codex - { - let runtime = config::resolve_runtime(¤t_cfg)?; - #[cfg(target_os = "windows")] - let (managed_http_addr, managed_socks_addr) = config::clamp_bind_addrs( - runtime.http_addr, - runtime.socks_addr, - ¤t_cfg.network, - ); - #[cfg(target_os = "windows")] - let reserved = reserve_windows_managed_listeners( - managed_http_addr, - managed_socks_addr, - current_cfg.network.enable_socks5, - ) - .context("reserve managed loopback proxy listeners")?; - #[cfg(not(target_os = "windows"))] - let reserved = reserve_loopback_ephemeral_listeners(current_cfg.network.enable_socks5) + let (requested_http_addr, requested_socks_addr, reserved_listeners) = + if self.managed_by_codex { + let runtime = config::resolve_runtime(¤t_cfg)?; + #[cfg(target_os = "windows")] + let (managed_http_addr, managed_socks_addr) = + config::clamp_bind_addrs(runtime.http_addr, runtime.socks_addr, ¤t_cfg); + #[cfg(target_os = "windows")] + let reserved = reserve_windows_managed_listeners( + managed_http_addr, + managed_socks_addr, + current_cfg.enable_socks5, + ) .context("reserve managed loopback proxy listeners")?; - let http_addr = reserved.http_addr()?; - let socks_addr = reserved.socks_addr(runtime.socks_addr)?; - ( - http_addr, - socks_addr, - Some(reserved.into_reserved_listeners()), - ) - } else { - let runtime = config::resolve_runtime(¤t_cfg)?; - ( - self.http_addr.unwrap_or(runtime.http_addr), - self.socks_addr.unwrap_or(runtime.socks_addr), - None, - ) - }; + #[cfg(not(target_os = "windows"))] + let reserved = reserve_loopback_ephemeral_listeners(current_cfg.enable_socks5) + .context("reserve managed loopback proxy listeners")?; + let http_addr = reserved.http_addr()?; + let socks_addr = reserved.socks_addr(runtime.socks_addr)?; + ( + http_addr, + socks_addr, + Some(reserved.into_reserved_listeners()), + ) + } else { + let runtime = config::resolve_runtime(¤t_cfg)?; + ( + self.http_addr.unwrap_or(runtime.http_addr), + self.socks_addr.unwrap_or(runtime.socks_addr), + None, + ) + }; // Reapply bind clamping for caller overrides so unix-socket proxying stays loopback-only. - let (http_addr, socks_addr) = config::clamp_bind_addrs( - requested_http_addr, - requested_socks_addr, - ¤t_cfg.network, - ); + let (http_addr, socks_addr) = + config::clamp_bind_addrs(requested_http_addr, requested_socks_addr, ¤t_cfg); Ok(NetworkProxy { state, http_addr, socks_addr, - socks_enabled: current_cfg.network.enable_socks5, - socks5_udp_enabled: current_cfg.network.enable_socks5_udp, + socks_enabled: current_cfg.enable_socks5, + socks5_udp_enabled: current_cfg.enable_socks5_udp, runtime_settings: Arc::new(RwLock::new(NetworkProxyRuntimeSettings::from_config( ¤t_cfg, )?)), @@ -317,16 +310,16 @@ struct NetworkProxyRuntimeSettings { impl NetworkProxyRuntimeSettings { fn from_config(config: &config::NetworkProxyConfig) -> Result { - let mitm_ca_trust_bundle = if config.network.mitm { + let mitm_ca_trust_bundle = if config.mitm { let env = crate::certs::ca_env_from_process(); Some(crate::certs::managed_ca_trust_bundle(&env)?) } else { None }; Ok(Self { - allow_local_binding: config.network.allow_local_binding, - allow_unix_sockets: config.network.allow_unix_sockets().into(), - dangerously_allow_all_unix_sockets: config.network.dangerously_allow_all_unix_sockets, + allow_local_binding: config.allow_local_binding, + allow_unix_sockets: config.allow_unix_sockets().into(), + dangerously_allow_all_unix_sockets: config.dangerously_allow_all_unix_sockets, mitm_ca_trust_bundle, }) } @@ -879,23 +872,23 @@ impl NetworkProxy { pub async fn replace_config_state(&self, new_state: ConfigState) -> Result<()> { let current_cfg = self.state.current_cfg().await?; anyhow::ensure!( - new_state.config.network.enabled == current_cfg.network.enabled, + new_state.config.enabled == current_cfg.enabled, "cannot update network.enabled on a running proxy" ); anyhow::ensure!( - new_state.config.network.proxy_url == current_cfg.network.proxy_url, + new_state.config.proxy_url == current_cfg.proxy_url, "cannot update network.proxy_url on a running proxy" ); anyhow::ensure!( - new_state.config.network.socks_url == current_cfg.network.socks_url, + new_state.config.socks_url == current_cfg.socks_url, "cannot update network.socks_url on a running proxy" ); anyhow::ensure!( - new_state.config.network.enable_socks5 == current_cfg.network.enable_socks5, + new_state.config.enable_socks5 == current_cfg.enable_socks5, "cannot update network.enable_socks5 on a running proxy" ); anyhow::ensure!( - new_state.config.network.enable_socks5_udp == current_cfg.network.enable_socks5_udp, + new_state.config.enable_socks5_udp == current_cfg.enable_socks5_udp, "cannot update network.enable_socks5_udp on a running proxy" ); @@ -922,7 +915,7 @@ impl NetworkProxy { "execution-scoped network proxy is already running" ); let current_cfg = self.state.current_cfg().await?; - if !current_cfg.network.enabled { + if !current_cfg.enabled { warn!("network.enabled is false; skipping proxy listeners"); return Ok(NetworkProxyHandle::noop()); } @@ -963,11 +956,11 @@ impl NetworkProxy { } }); - let socks_task = if current_cfg.network.enable_socks5 { + let socks_task = if current_cfg.enable_socks5 { let socks_state = self.state.clone(); let socks_decider = self.policy_decider.clone(); let socks_addr = self.socks_addr; - let enable_socks5_udp = current_cfg.network.enable_socks5_udp; + let enable_socks5_udp = current_cfg.enable_socks5_udp; Some(tokio::spawn(async move { match socks_listener { Some(listener) => { @@ -1095,7 +1088,7 @@ impl Drop for NetworkProxyHandle { #[cfg(test)] mod tests { use super::*; - use crate::config::NetworkProxySettings; + use crate::config::NetworkProxyConfig; use crate::state::network_proxy_state_for_policy; use pretty_assertions::assert_eq; use std::net::IpAddr; @@ -1111,10 +1104,10 @@ mod tests { drop(http_listener); drop(socks_listener); - let state = Arc::new(network_proxy_state_for_policy(NetworkProxySettings { + let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig { proxy_url: format!("http://{http_addr}"), socks_url: format!("http://{socks_addr}"), - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() })); let proxy = match NetworkProxy::builder().state(state).build().await { Ok(proxy) => proxy, @@ -1145,10 +1138,10 @@ mod tests { #[tokio::test] async fn non_codex_managed_proxy_builder_uses_configured_ports() { - let settings = NetworkProxySettings { + let settings = NetworkProxyConfig { proxy_url: "http://127.0.0.1:43128".to_string(), socks_url: "http://127.0.0.1:48081".to_string(), - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; let state = Arc::new(network_proxy_state_for_policy(settings)); let proxy = NetworkProxy::builder() @@ -1170,9 +1163,7 @@ mod tests { #[tokio::test] async fn prepare_for_environment_keeps_env_and_sandbox_ports_in_sync() -> Result<()> { - let state = Arc::new(network_proxy_state_for_policy( - NetworkProxySettings::default(), - )); + let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default())); let proxy = NetworkProxy::builder().state(state).build().await?; let handle = proxy.run().await?; @@ -1229,11 +1220,11 @@ mod tests { #[tokio::test] async fn managed_proxy_builder_does_not_reserve_socks_listener_when_disabled() { - let settings = NetworkProxySettings { + let settings = NetworkProxyConfig { enable_socks5: false, proxy_url: "http://127.0.0.1:43128".to_string(), socks_url: "http://127.0.0.1:43129".to_string(), - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; let state = Arc::new(network_proxy_state_for_policy(settings)); let proxy = match NetworkProxy::builder().state(state).build().await { diff --git a/codex-rs/network-proxy/src/runtime.rs b/codex-rs/network-proxy/src/runtime.rs index a51f1d8f165b..25954e83e90b 100644 --- a/codex-rs/network-proxy/src/runtime.rs +++ b/codex-rs/network-proxy/src/runtime.rs @@ -298,7 +298,7 @@ impl NetworkProxyState { blocked_request_observer: Option>, ) -> Self { Self { - credential_broker: CredentialBroker::new(state.config.network.credential_broker), + credential_broker: CredentialBroker::new(state.config.credential_broker), state: Arc::new(RwLock::new(state)), reloader, blocked_request_observer: Arc::new(RwLock::new(blocked_request_observer)), @@ -381,7 +381,6 @@ impl NetworkProxyState { let guard = self.state.read().await; Ok(guard .config - .network .dangerously_allow_plaintext_credential_injection) } @@ -397,15 +396,15 @@ impl NetworkProxyState { self.reload_if_needed().await?; let guard = self.state.read().await; Ok(( - guard.config.network.allowed_domains().unwrap_or_default(), - guard.config.network.denied_domains().unwrap_or_default(), + guard.config.allowed_domains().unwrap_or_default(), + guard.config.denied_domains().unwrap_or_default(), )) } pub async fn enabled(&self) -> Result { self.reload_if_needed().await?; let guard = self.state.read().await; - Ok(guard.config.network.enabled) + Ok(guard.config.enabled) } pub async fn force_reload(&self) -> Result<()> { @@ -457,11 +456,11 @@ impl NetworkProxyState { }; let (deny_set, allow_set, allow_local_binding, allowed_domains) = { let guard = self.state.read().await; - let allowed_domains = guard.config.network.allowed_domains(); + let allowed_domains = guard.config.allowed_domains(); ( guard.deny_set.clone(), guard.allow_set.clone(), - guard.config.network.allow_local_binding, + guard.config.allow_local_binding, allowed_domains, ) }; @@ -594,7 +593,7 @@ impl NetworkProxyState { } let guard = self.state.read().await; - if guard.config.network.dangerously_allow_all_unix_sockets { + if guard.config.dangerously_allow_all_unix_sockets { return Ok(true); } @@ -604,7 +603,7 @@ impl NetworkProxyState { Err(_) => return Ok(false), }; let requested_canonical = std::fs::canonicalize(requested_abs.as_path()).ok(); - for allowed in &guard.config.network.allow_unix_sockets() { + for allowed in &guard.config.allow_unix_sockets() { let allowed_path = match ValidatedUnixSocketPath::parse(allowed) { Ok(ValidatedUnixSocketPath::Native(path)) => path, Ok(ValidatedUnixSocketPath::UnixStyleAbsolute(_)) => continue, @@ -635,25 +634,25 @@ impl NetworkProxyState { pub async fn method_allowed(&self, method: &str) -> Result { self.reload_if_needed().await?; let guard = self.state.read().await; - Ok(guard.config.network.mode.allows_method(method)) + Ok(guard.config.mode.allows_method(method)) } pub async fn allow_upstream_proxy(&self) -> Result { self.reload_if_needed().await?; let guard = self.state.read().await; - Ok(guard.config.network.allow_upstream_proxy) + Ok(guard.config.allow_upstream_proxy) } pub async fn allow_local_binding(&self) -> Result { self.reload_if_needed().await?; let guard = self.state.read().await; - Ok(guard.config.network.allow_local_binding) + Ok(guard.config.allow_local_binding) } pub async fn network_mode(&self) -> Result { self.reload_if_needed().await?; let guard = self.state.read().await; - Ok(guard.config.network.mode) + Ok(guard.config.mode) } pub async fn set_network_mode(&self, mode: NetworkMode) -> Result<()> { @@ -662,7 +661,7 @@ impl NetworkProxyState { let (candidate, constraints) = { let guard = self.state.read().await; let mut candidate = guard.config.clone(); - candidate.network.mode = mode; + candidate.mode = mode; (candidate, guard.constraints.clone()) }; @@ -675,7 +674,7 @@ impl NetworkProxyState { drop(guard); continue; } - guard.config.network.mode = mode; + guard.config.mode = mode; info!("updated network mode to {mode:?}"); return Ok(()); } @@ -740,8 +739,8 @@ impl NetworkProxyState { }; let mut candidate = previous_cfg.clone(); - let target_entries = target.entries(&candidate.network); - let opposite_entries = target.opposite_entries(&candidate.network); + let target_entries = target.entries(&candidate); + let opposite_entries = target.opposite_entries(&candidate); let target_contains = target_entries .iter() .any(|entry| normalize_host(entry) == normalized_host); @@ -752,7 +751,7 @@ impl NetworkProxyState { return Ok(()); } - candidate.network.upsert_domain_permission( + candidate.upsert_domain_permission( normalized_host.clone(), target.permission(), normalize_host, @@ -809,7 +808,7 @@ impl NetworkProxyState { fn ensure_credential_broker_enablement_unchanged(&self, new_state: &ConfigState) -> Result<()> { anyhow::ensure!( - self.credential_broker.enabled() == new_state.config.network.credential_broker, + self.credential_broker.enabled() == new_state.config.credential_broker, "network.credential_broker cannot change while the proxy is running" ); Ok(()) @@ -844,14 +843,14 @@ impl DomainListKind { } } - fn entries(self, network: &crate::config::NetworkProxySettings) -> Vec { + fn entries(self, network: &crate::config::NetworkProxyConfig) -> Vec { match self { Self::Allow => network.allowed_domains().unwrap_or_default(), Self::Deny => network.denied_domains().unwrap_or_default(), } } - fn opposite_entries(self, network: &crate::config::NetworkProxySettings) -> Vec { + fn opposite_entries(self, network: &crate::config::NetworkProxyConfig) -> Vec { match self { Self::Allow => network.denied_domains().unwrap_or_default(), Self::Deny => network.allowed_domains().unwrap_or_default(), @@ -905,15 +904,15 @@ where } fn log_policy_changes(previous: &NetworkProxyConfig, next: &NetworkProxyConfig) { - let previous_allowed_domains = previous.network.allowed_domains().unwrap_or_default(); - let next_allowed_domains = next.network.allowed_domains().unwrap_or_default(); + let previous_allowed_domains = previous.allowed_domains().unwrap_or_default(); + let next_allowed_domains = next.allowed_domains().unwrap_or_default(); log_domain_list_changes( "allowlist", &previous_allowed_domains, &next_allowed_domains, ); - let previous_denied_domains = previous.network.denied_domains().unwrap_or_default(); - let next_denied_domains = next.network.denied_domains().unwrap_or_default(); + let previous_denied_domains = previous.denied_domains().unwrap_or_default(); + let next_denied_domains = next.denied_domains().unwrap_or_default(); log_domain_list_changes("denylist", &previous_denied_domains, &next_denied_domains); } @@ -980,13 +979,13 @@ fn unix_timestamp() -> i64 { #[cfg(test)] pub(crate) fn network_proxy_state_for_policy( - mut network: crate::config::NetworkProxySettings, + mut network: crate::config::NetworkProxyConfig, ) -> NetworkProxyState { network.enabled = true; - let config = NetworkProxyConfig { network }; + let config = network; let state = ConfigState { allow_set: crate::policy::compile_allowlist_globset( - &config.network.allowed_domains().unwrap_or_default(), + &config.allowed_domains().unwrap_or_default(), ) .unwrap(), blocked: VecDeque::new(), @@ -994,7 +993,7 @@ pub(crate) fn network_proxy_state_for_policy( config: config.clone(), constraints: NetworkProxyConstraints::default(), deny_set: crate::policy::compile_denylist_globset( - &config.network.denied_domains().unwrap_or_default(), + &config.denied_domains().unwrap_or_default(), ) .unwrap(), mitm: None, @@ -1027,7 +1026,6 @@ mod tests { use super::*; use crate::config::NetworkProxyConfig; - use crate::config::NetworkProxySettings; use crate::policy::compile_allowlist_globset; use crate::policy::compile_denylist_globset; use crate::state::NetworkProxyConstraints; @@ -1060,8 +1058,8 @@ mod tests { entries.iter().map(|entry| (*entry).to_string()).collect() } - fn network_settings(allowed_domains: &[&str], denied_domains: &[&str]) -> NetworkProxySettings { - let mut network = NetworkProxySettings::default(); + fn network_settings(allowed_domains: &[&str], denied_domains: &[&str]) -> NetworkProxyConfig { + let mut network = NetworkProxyConfig::default(); if !allowed_domains.is_empty() { network.set_allowed_domains(strings(allowed_domains)); } @@ -1075,7 +1073,7 @@ mod tests { allowed_domains: &[&str], denied_domains: &[&str], unix_sockets: &[String], - ) -> NetworkProxySettings { + ) -> NetworkProxyConfig { let mut network = network_settings(allowed_domains, denied_domains); if !unix_sockets.is_empty() { network.set_allow_unix_sockets(unix_sockets.to_vec()); @@ -1209,13 +1207,8 @@ mod tests { #[tokio::test] async fn add_allowed_domain_succeeds_when_managed_baseline_allows_expansion() { - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["managed.example.com"], &[]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["managed.example.com"], &[]); + config.enabled = true; let constraints = NetworkProxyConstraints { allowed_domains: Some(vec!["managed.example.com".to_string()]), allowlist_expansion_enabled: Some(true), @@ -1241,13 +1234,8 @@ mod tests { #[tokio::test] async fn add_allowed_domain_rejects_expansion_when_managed_baseline_is_fixed() { - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["managed.example.com"], &[]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["managed.example.com"], &[]); + config.enabled = true; let constraints = NetworkProxyConstraints { allowed_domains: Some(vec!["managed.example.com".to_string()]), allowlist_expansion_enabled: Some(false), @@ -1271,13 +1259,8 @@ mod tests { #[tokio::test] async fn add_denied_domain_rejects_expansion_when_managed_baseline_is_fixed() { - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&[], &["managed.example.com"]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&[], &["managed.example.com"]); + config.enabled = true; let constraints = NetworkProxyConstraints { denied_domains: Some(vec!["managed.example.com".to_string()]), denylist_expansion_enabled: Some(false), @@ -1301,7 +1284,7 @@ mod tests { #[tokio::test] async fn blocked_snapshot_does_not_consume_entries() { - let state = network_proxy_state_for_policy(NetworkProxySettings::default()); + let state = network_proxy_state_for_policy(NetworkProxyConfig::default()); state .record_blocked(BlockedRequest::new(BlockedRequestArgs { @@ -1340,7 +1323,7 @@ mod tests { #[tokio::test] async fn drain_blocked_returns_buffered_window() { - let state = network_proxy_state_for_policy(NetworkProxySettings::default()); + let state = network_proxy_state_for_policy(NetworkProxyConfig::default()); for idx in 0..(MAX_BLOCKED_EVENTS + 5) { state @@ -1492,7 +1475,7 @@ mod tests { #[tokio::test] async fn host_blocked_requires_exact_scoped_ipv6_allowlist_match() { - let state = network_proxy_state_for_policy(NetworkProxySettings { + let state = network_proxy_state_for_policy(NetworkProxyConfig { allow_local_binding: true, ..network_settings(&["fe80::1%eth0"], &[]) }); @@ -1515,7 +1498,7 @@ mod tests { #[tokio::test] async fn host_blocked_denies_scoped_ipv6_literal_before_local_binding() { - let state = network_proxy_state_for_policy(NetworkProxySettings { + let state = network_proxy_state_for_policy(NetworkProxyConfig { allow_local_binding: true, ..network_settings(&["*"], &["fd00::1"]) }); @@ -1531,7 +1514,7 @@ mod tests { #[tokio::test] async fn host_blocked_requires_exact_scoped_ipv6_denylist_match() { - let state = network_proxy_state_for_policy(NetworkProxySettings { + let state = network_proxy_state_for_policy(NetworkProxyConfig { allow_local_binding: true, ..network_settings(&["*"], &["fd00::1%eth0"]) }); @@ -1564,7 +1547,7 @@ mod tests { #[tokio::test] async fn host_blocked_rejects_loopback_when_allowlist_empty() { - let state = network_proxy_state_for_policy(NetworkProxySettings::default()); + let state = network_proxy_state_for_policy(NetworkProxyConfig::default()); assert_eq!( state.host_blocked("127.0.0.1", /*port*/ 80).await.unwrap(), @@ -1574,7 +1557,7 @@ mod tests { #[tokio::test] async fn host_blocked_rejects_allowlisted_hostname_when_dns_lookup_fails() { - let mut network = NetworkProxySettings::default(); + let mut network = NetworkProxyConfig::default(); network.set_allowed_domains(vec!["does-not-resolve.invalid".to_string()]); let state = network_proxy_state_for_policy(network); @@ -1653,13 +1636,8 @@ mod tests { ..NetworkProxyConstraints::default() }; - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["example.com", "evil.com"], &[]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["example.com", "evil.com"], &[]); + config.enabled = true; assert!(validate_policy_against_constraints(&config, &constraints).is_err()); } @@ -1672,13 +1650,8 @@ mod tests { ..NetworkProxyConstraints::default() }; - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["example.com", "api.openai.com"], &[]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["example.com", "api.openai.com"], &[]); + config.enabled = true; assert!(validate_policy_against_constraints(&config, &constraints).is_ok()); } @@ -1691,11 +1664,9 @@ mod tests { }; let config = NetworkProxyConfig { - network: NetworkProxySettings { - enabled: true, - mode: NetworkMode::Full, - ..NetworkProxySettings::default() - }, + enabled: true, + mode: NetworkMode::Full, + ..NetworkProxyConfig::default() }; assert!(validate_policy_against_constraints(&config, &constraints).is_err()); @@ -1708,13 +1679,8 @@ mod tests { ..NetworkProxyConstraints::default() }; - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["api.example.com"], &[]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["api.example.com"], &[]); + config.enabled = true; assert!(validate_policy_against_constraints(&config, &constraints).is_ok()); } @@ -1726,13 +1692,8 @@ mod tests { ..NetworkProxyConstraints::default() }; - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["**.example.com"], &[]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["**.example.com"], &[]); + config.enabled = true; assert!(validate_policy_against_constraints(&config, &constraints).is_err()); } @@ -1744,13 +1705,8 @@ mod tests { ..NetworkProxyConstraints::default() }; - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["api.example.com"], &[]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["api.example.com"], &[]); + config.enabled = true; assert!(validate_policy_against_constraints(&config, &constraints).is_err()); } @@ -1763,13 +1719,8 @@ mod tests { ..NetworkProxyConstraints::default() }; - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["api.example.com"], &[]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["api.example.com"], &[]); + config.enabled = true; assert!(validate_policy_against_constraints(&config, &constraints).is_err()); } @@ -1782,13 +1733,8 @@ mod tests { ..NetworkProxyConstraints::default() }; - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["api.example.com"], &[]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["api.example.com"], &[]); + config.enabled = true; assert!(validate_policy_against_constraints(&config, &constraints).is_err()); } @@ -1801,10 +1747,8 @@ mod tests { }; let config = NetworkProxyConfig { - network: NetworkProxySettings { - enabled: true, - ..NetworkProxySettings::default() - }, + enabled: true, + ..NetworkProxyConfig::default() }; assert!(validate_policy_against_constraints(&config, &constraints).is_err()); @@ -1818,13 +1762,8 @@ mod tests { ..NetworkProxyConstraints::default() }; - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&[], &["evil.com", "more-evil.com"]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&[], &["evil.com", "more-evil.com"]); + config.enabled = true; assert!(validate_policy_against_constraints(&config, &constraints).is_err()); } @@ -1837,10 +1776,8 @@ mod tests { }; let config = NetworkProxyConfig { - network: NetworkProxySettings { - enabled: true, - ..NetworkProxySettings::default() - }, + enabled: true, + ..NetworkProxyConfig::default() }; assert!(validate_policy_against_constraints(&config, &constraints).is_err()); @@ -1854,11 +1791,9 @@ mod tests { }; let config = NetworkProxyConfig { - network: NetworkProxySettings { - enabled: true, - allow_local_binding: true, - ..NetworkProxySettings::default() - }, + enabled: true, + allow_local_binding: true, + ..NetworkProxyConfig::default() }; assert!(validate_policy_against_constraints(&config, &constraints).is_err()); @@ -1873,11 +1808,9 @@ mod tests { }; let config = NetworkProxyConfig { - network: NetworkProxySettings { - enabled: true, - dangerously_allow_all_unix_sockets: true, - ..NetworkProxySettings::default() - }, + enabled: true, + dangerously_allow_all_unix_sockets: true, + ..NetworkProxyConfig::default() }; assert!(validate_policy_against_constraints(&config, &constraints).is_err()); @@ -1892,11 +1825,9 @@ mod tests { }; let config = NetworkProxyConfig { - network: NetworkProxySettings { - enabled: true, - dangerously_allow_all_unix_sockets: true, - ..NetworkProxySettings::default() - }, + enabled: true, + dangerously_allow_all_unix_sockets: true, + ..NetworkProxyConfig::default() }; assert!(validate_policy_against_constraints(&config, &constraints).is_err()); @@ -1910,11 +1841,9 @@ mod tests { }; let config = NetworkProxyConfig { - network: NetworkProxySettings { - enabled: true, - dangerously_allow_all_unix_sockets: true, - ..NetworkProxySettings::default() - }, + enabled: true, + dangerously_allow_all_unix_sockets: true, + ..NetworkProxyConfig::default() }; assert!(validate_policy_against_constraints(&config, &constraints).is_ok()); @@ -1925,11 +1854,9 @@ mod tests { let constraints = NetworkProxyConstraints::default(); let config = NetworkProxyConfig { - network: NetworkProxySettings { - enabled: true, - dangerously_allow_all_unix_sockets: true, - ..NetworkProxySettings::default() - }, + enabled: true, + dangerously_allow_all_unix_sockets: true, + ..NetworkProxyConfig::default() }; assert!(validate_policy_against_constraints(&config, &constraints).is_ok()); @@ -2005,52 +1932,32 @@ mod tests { #[test] fn build_config_state_allows_global_wildcard_allowed_domains() { - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["*"], &[]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["*"], &[]); + config.enabled = true; assert!(build_config_state(config, NetworkProxyConstraints::default()).is_ok()); } #[test] fn build_config_state_allows_bracketed_global_wildcard_allowed_domains() { - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["[*]"], &[]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["[*]"], &[]); + config.enabled = true; assert!(build_config_state(config, NetworkProxyConstraints::default()).is_ok()); } #[test] fn build_config_state_rejects_global_wildcard_denied_domains() { - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["example.com"], &["*"]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["example.com"], &["*"]); + config.enabled = true; assert!(build_config_state(config, NetworkProxyConstraints::default()).is_err()); } #[test] fn build_config_state_rejects_bracketed_global_wildcard_denied_domains() { - let config = NetworkProxyConfig { - network: { - let mut network = network_settings(&["example.com"], &["[*]"]); - network.enabled = true; - network - }, - }; + let mut config = network_settings(&["example.com"], &["[*]"]); + config.enabled = true; assert!(build_config_state(config, NetworkProxyConstraints::default()).is_err()); } diff --git a/codex-rs/network-proxy/src/socks5.rs b/codex-rs/network-proxy/src/socks5.rs index d1c193198f59..f2fa36b8ef9e 100644 --- a/codex-rs/network-proxy/src/socks5.rs +++ b/codex-rs/network-proxy/src/socks5.rs @@ -809,7 +809,6 @@ mod tests { use super::*; use crate::config::NetworkMode; use crate::config::NetworkProxyConfig; - use crate::config::NetworkProxySettings; use crate::mitm_hook::MitmHookConfig; use crate::mitm_hook::MitmHookMatchConfig; use crate::network_policy::test_support::POLICY_DECISION_EVENT_NAME; @@ -855,12 +854,9 @@ mod tests { } } - fn state_for_settings(network: NetworkProxySettings) -> Arc { - let config = NetworkProxyConfig { network }; - let _mitm_config_state_guard = config - .network - .mitm - .then(|| MITM_CONFIG_STATE_LOCK.lock().unwrap()); + fn state_for_settings(network: NetworkProxyConfig) -> Arc { + let config = network; + let _mitm_config_state_guard = config.mitm.then(|| MITM_CONFIG_STATE_LOCK.lock().unwrap()); let state = build_config_state(config, NetworkProxyConstraints::default()).unwrap(); let reloader = Arc::new(StaticReloader { state: state.clone(), @@ -870,10 +866,10 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn handle_socks5_tcp_emits_block_decision_for_proxy_disabled() { - let state = state_for_settings(NetworkProxySettings { + let state = state_for_settings(NetworkProxyConfig { enabled: false, mode: NetworkMode::Full, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }); let mut request = TcpRequest::new(HostWithPort::try_from("example.com:443").expect("valid authority")); @@ -912,11 +908,11 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn handle_socks5_tcp_uses_mitm_in_limited_mode() { - let mut settings = NetworkProxySettings { + let mut settings = NetworkProxyConfig { enabled: true, mode: NetworkMode::Limited, mitm: true, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; settings.set_allowed_domains(vec!["example.com".to_string()]); let state = state_for_settings(settings); @@ -938,10 +934,10 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn handle_socks5_tcp_blocks_non_https_in_limited_mode() { - let mut settings = NetworkProxySettings { + let mut settings = NetworkProxyConfig { enabled: true, mode: NetworkMode::Limited, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; settings.set_allowed_domains(vec!["example.com".to_string()]); let state = state_for_settings(settings); @@ -985,12 +981,12 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn handle_socks5_tcp_detects_tls_for_brokered_nonstandard_port_in_full_mode() { - let mut settings = NetworkProxySettings { + let mut settings = NetworkProxyConfig { enabled: true, mode: NetworkMode::Full, mitm: true, credential_broker: true, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; settings.set_allowed_domains(vec!["api.openai.com".to_string()]); let state = state_for_settings(settings); @@ -1015,10 +1011,10 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn handle_socks5_tcp_blocks_limited_mode_without_mitm_state() { - let mut settings = NetworkProxySettings { + let mut settings = NetworkProxyConfig { enabled: true, mode: NetworkMode::Limited, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; settings.set_allowed_domains(vec!["example.com".to_string()]); let state = state_for_settings(settings); @@ -1043,7 +1039,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn handle_socks5_tcp_uses_mitm_for_hooked_host_in_full_mode() { - let mut settings = NetworkProxySettings { + let mut settings = NetworkProxyConfig { enabled: true, mode: NetworkMode::Full, mitm: true, @@ -1056,7 +1052,7 @@ mod tests { }, ..MitmHookConfig::default() }], - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; settings.set_allowed_domains(vec!["api.github.com".to_string()]); let state = state_for_settings(settings); @@ -1078,7 +1074,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn handle_socks5_tcp_blocks_hooked_non_https_host_in_full_mode() { - let mut settings = NetworkProxySettings { + let mut settings = NetworkProxyConfig { enabled: true, mode: NetworkMode::Full, mitm: true, @@ -1091,7 +1087,7 @@ mod tests { }, ..MitmHookConfig::default() }], - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }; settings.set_allowed_domains(vec!["api.github.com".to_string()]); let state = state_for_settings(settings); @@ -1116,10 +1112,10 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn inspect_socks5_udp_emits_block_decision_for_mode_guard_deny() { - let state = state_for_settings(NetworkProxySettings { + let state = state_for_settings(NetworkProxyConfig { enabled: true, mode: NetworkMode::Limited, - ..NetworkProxySettings::default() + ..NetworkProxyConfig::default() }); let request = RelayRequest { direction: RelayDirection::South, diff --git a/codex-rs/network-proxy/src/state.rs b/codex-rs/network-proxy/src/state.rs index 9a5287dac4aa..c61f6b161728 100644 --- a/codex-rs/network-proxy/src/state.rs +++ b/codex-rs/network-proxy/src/state.rs @@ -40,12 +40,6 @@ pub struct NetworkProxyConstraints { #[derive(Debug, Clone, Deserialize)] pub struct PartialNetworkProxyConfig { - #[serde(default)] - pub network: PartialNetworkConfig, -} - -#[derive(Debug, Default, Clone, Deserialize)] -pub struct PartialNetworkConfig { pub enabled: Option, pub mode: Option, pub allow_upstream_proxy: Option, @@ -69,20 +63,20 @@ pub fn build_config_state( ) -> anyhow::Result { crate::config::validate_unix_socket_allowlist_paths(&config)?; anyhow::ensure!( - !config.network.credential_broker || config.network.mitm, + !config.credential_broker || config.mitm, "network.credential_broker requires network.mitm = true" ); - let allowed_domains = config.network.allowed_domains().unwrap_or_default(); - let denied_domains = config.network.denied_domains().unwrap_or_default(); + let allowed_domains = config.allowed_domains().unwrap_or_default(); + let denied_domains = config.denied_domains().unwrap_or_default(); validate_non_global_wildcard_domain_patterns("network.denied_domains", &denied_domains) .map_err(NetworkProxyConstraintError::into_anyhow)?; let deny_set = compile_denylist_globset(&denied_domains)?; let allow_set = compile_allowlist_globset(&allowed_domains)?; let mitm_hooks = compile_mitm_hooks(&config)?; - let mitm = if config.network.mitm { + let mitm = if config.mitm { Some(Arc::new(MitmState::new(MitmUpstreamConfig { - allow_upstream_proxy: config.network.allow_upstream_proxy, - allow_local_binding: config.network.allow_local_binding, + allow_upstream_proxy: config.allow_upstream_proxy, + allow_local_binding: config.allow_local_binding, })?)) } else { None @@ -122,14 +116,14 @@ pub fn validate_policy_against_constraints( validator(&candidate) } - let enabled = config.network.enabled; - let config_allowed_domains = config.network.allowed_domains().unwrap_or_default(); - let config_denied_domains = config.network.denied_domains().unwrap_or_default(); + let enabled = config.enabled; + let config_allowed_domains = config.allowed_domains().unwrap_or_default(); + let config_denied_domains = config.denied_domains().unwrap_or_default(); let denied_domain_overrides: HashSet = config_denied_domains .iter() .map(|entry| entry.to_ascii_lowercase()) .collect(); - let config_allow_unix_sockets = config.network.allow_unix_sockets(); + let config_allow_unix_sockets = config.allow_unix_sockets(); validate_mitm_hook_config(config).map_err(invalid_mitm_hook_configuration)?; validate_non_global_wildcard_domain_patterns("network.denied_domains", &config_denied_domains)?; if let Some(max_enabled) = constraints.enabled { @@ -147,7 +141,7 @@ pub fn validate_policy_against_constraints( } if let Some(max_mode) = constraints.mode { - validate(config.network.mode, move |candidate| { + validate(config.mode, move |candidate| { if network_mode_rank(*candidate) > network_mode_rank(max_mode) { Err(invalid_value( "network.mode", @@ -162,7 +156,7 @@ pub fn validate_policy_against_constraints( let allow_upstream_proxy = constraints.allow_upstream_proxy; validate( - config.network.allow_upstream_proxy, + config.allow_upstream_proxy, move |candidate| match allow_upstream_proxy { Some(true) | None => Ok(()), Some(false) => { @@ -181,7 +175,7 @@ pub fn validate_policy_against_constraints( let allow_non_loopback_proxy = constraints.dangerously_allow_non_loopback_proxy; validate( - config.network.dangerously_allow_non_loopback_proxy, + config.dangerously_allow_non_loopback_proxy, move |candidate| match allow_non_loopback_proxy { Some(true) | None => Ok(()), Some(false) => { @@ -202,7 +196,7 @@ pub fn validate_policy_against_constraints( .dangerously_allow_all_unix_sockets .unwrap_or(constraints.allow_unix_sockets.is_none()); validate( - config.network.dangerously_allow_all_unix_sockets, + config.dangerously_allow_all_unix_sockets, move |candidate| { if *candidate && !allow_all_unix_sockets { Err(invalid_value( @@ -217,7 +211,7 @@ pub fn validate_policy_against_constraints( )?; if let Some(allow_local_binding) = constraints.allow_local_binding { - validate(config.network.allow_local_binding, move |candidate| { + validate(config.allow_local_binding, move |candidate| { if *candidate && !allow_local_binding { Err(invalid_value( "network.allow_local_binding", diff --git a/codex-rs/sandboxing/src/seatbelt_tests.rs b/codex-rs/sandboxing/src/seatbelt_tests.rs index b15019a8cb78..fef2da4405cd 100644 --- a/codex-rs/sandboxing/src/seatbelt_tests.rs +++ b/codex-rs/sandboxing/src/seatbelt_tests.rs @@ -658,11 +658,9 @@ async fn create_seatbelt_args_merges_proxy_and_explicit_unix_socket_paths() -> a let network_socket = "/tmp/codex-proxy-use"; let explicit_socket = "/tmp/codex-browser-use"; let mut network_config = NetworkProxyConfig::default(); - network_config.network.enabled = true; - network_config.network.mode = NetworkMode::Full; - network_config - .network - .set_allow_unix_sockets(vec![network_socket.to_string()]); + network_config.enabled = true; + network_config.mode = NetworkMode::Full; + network_config.set_allow_unix_sockets(vec![network_socket.to_string()]); let state = build_config_state(network_config, NetworkProxyConstraints::default())?; let network_proxy = NetworkProxy::builder() .state(Arc::new(NetworkProxyState::with_reloader( From efb3a03108e7497c58c1c83d9f5ab512ac7575a4 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 9 Jul 2026 11:47:17 +0100 Subject: [PATCH 2/2] Fix network proxy test initialization --- codex-rs/core/src/guardian/tests.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/codex-rs/core/src/guardian/tests.rs b/codex-rs/core/src/guardian/tests.rs index 011573f0ae78..22a11ee5f19f 100644 --- a/codex-rs/core/src/guardian/tests.rs +++ b/codex-rs/core/src/guardian/tests.rs @@ -2974,8 +2974,10 @@ async fn guardian_review_session_config_clears_legacy_notify() { #[tokio::test] async fn guardian_review_session_config_uses_live_network_proxy_state() { let mut parent_config = test_config().await; - let mut parent_network = NetworkProxyConfig::default(); - parent_network.enabled = true; + let mut parent_network = NetworkProxyConfig { + enabled: true, + ..Default::default() + }; parent_network.set_allowed_domains(vec!["parent.example".to_string()]); parent_config.permissions.network = Some( NetworkProxySpec::from_config_and_constraints( @@ -2986,8 +2988,10 @@ async fn guardian_review_session_config_uses_live_network_proxy_state() { .expect("parent network proxy spec"), ); - let mut live_network = NetworkProxyConfig::default(); - live_network.enabled = true; + let mut live_network = NetworkProxyConfig { + enabled: true, + ..Default::default() + }; live_network.set_allowed_domains(vec!["github.com".to_string()]); let guardian_config = build_guardian_review_session_config_for_test(