diff --git a/codex-rs/network-proxy/README.md b/codex-rs/network-proxy/README.md index 3d8c20307d3..d1df71ca3da 100644 --- a/codex-rs/network-proxy/README.md +++ b/codex-rs/network-proxy/README.md @@ -17,7 +17,7 @@ It enforces an allow/deny policy and a "limited" mode intended for read-only net Example config: ```toml -[network_proxy] +[network] enabled = true proxy_url = "http://127.0.0.1:3128" admin_url = "http://127.0.0.1:8080" @@ -35,7 +35,6 @@ dangerously_allow_non_loopback_proxy = false dangerously_allow_non_loopback_admin = false mode = "full" # default when unset; use "limited" for read-only mode -[network_proxy.policy] # Hosts must match the allowlist (unless denied). # If `allowed_domains` is empty, the proxy blocks requests until an allowlist is configured. allowed_domains = ["*.openai.com"] diff --git a/codex-rs/network-proxy/src/config.rs b/codex-rs/network-proxy/src/config.rs index ae4a4501791..e4ef202a458 100644 --- a/codex-rs/network-proxy/src/config.rs +++ b/codex-rs/network-proxy/src/config.rs @@ -11,7 +11,7 @@ use url::Url; #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct NetworkProxyConfig { #[serde(default)] - pub network_proxy: NetworkProxySettings, + pub network: NetworkProxySettings, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -37,7 +37,13 @@ pub struct NetworkProxySettings { #[serde(default)] pub mode: NetworkMode, #[serde(default)] - pub policy: NetworkPolicy, + pub allowed_domains: Vec, + #[serde(default)] + pub denied_domains: Vec, + #[serde(default)] + pub allow_unix_sockets: Vec, + #[serde(default)] + pub allow_local_binding: bool, } impl Default for NetworkProxySettings { @@ -53,23 +59,14 @@ impl Default for NetworkProxySettings { dangerously_allow_non_loopback_proxy: false, dangerously_allow_non_loopback_admin: false, mode: NetworkMode::default(), - policy: NetworkPolicy::default(), + allowed_domains: Vec::new(), + denied_domains: Vec::new(), + allow_unix_sockets: Vec::new(), + allow_local_binding: false, } } } -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct NetworkPolicy { - #[serde(default)] - pub allowed_domains: Vec, - #[serde(default)] - pub denied_domains: Vec, - #[serde(default)] - pub allow_unix_sockets: Vec, - #[serde(default)] - pub allow_local_binding: bool, -} - #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] #[serde(rename_all = "lowercase")] pub enum NetworkMode { @@ -142,7 +139,7 @@ pub(crate) fn clamp_bind_addrs( cfg.dangerously_allow_non_loopback_admin, "admin API", ); - if cfg.policy.allow_unix_sockets.is_empty() { + if cfg.allow_unix_sockets.is_empty() { return (http_addr, socks_addr, admin_addr); } @@ -179,26 +176,14 @@ pub struct RuntimeConfig { } pub fn resolve_runtime(cfg: &NetworkProxyConfig) -> Result { - let http_addr = resolve_addr(&cfg.network_proxy.proxy_url, 3128).with_context(|| { - format!( - "invalid network_proxy.proxy_url: {}", - cfg.network_proxy.proxy_url - ) - })?; - let socks_addr = resolve_addr(&cfg.network_proxy.socks_url, 8081).with_context(|| { - format!( - "invalid network_proxy.socks_url: {}", - cfg.network_proxy.socks_url - ) - })?; - let admin_addr = resolve_addr(&cfg.network_proxy.admin_url, 8080).with_context(|| { - format!( - "invalid network_proxy.admin_url: {}", - cfg.network_proxy.admin_url - ) - })?; + let http_addr = resolve_addr(&cfg.network.proxy_url, 3128) + .with_context(|| format!("invalid network.proxy_url: {}", cfg.network.proxy_url))?; + let socks_addr = resolve_addr(&cfg.network.socks_url, 8081) + .with_context(|| format!("invalid network.socks_url: {}", cfg.network.socks_url))?; + let admin_addr = resolve_addr(&cfg.network.admin_url, 8080) + .with_context(|| format!("invalid network.admin_url: {}", cfg.network.admin_url))?; let (http_addr, socks_addr, admin_addr) = - clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg.network_proxy); + clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg.network); Ok(RuntimeConfig { http_addr, @@ -453,10 +438,7 @@ mod tests { let cfg = NetworkProxySettings { dangerously_allow_non_loopback_proxy: true, dangerously_allow_non_loopback_admin: true, - policy: NetworkPolicy { - allow_unix_sockets: vec!["/tmp/docker.sock".to_string()], - ..Default::default() - }, + allow_unix_sockets: vec!["/tmp/docker.sock".to_string()], ..Default::default() }; let http_addr = "0.0.0.0:3128".parse::().unwrap(); diff --git a/codex-rs/network-proxy/src/http_proxy.rs b/codex-rs/network-proxy/src/http_proxy.rs index 08499490f38..e9bb4a6c943 100644 --- a/codex-rs/network-proxy/src/http_proxy.rs +++ b/codex-rs/network-proxy/src/http_proxy.rs @@ -688,7 +688,7 @@ mod tests { use super::*; use crate::config::NetworkMode; - use crate::config::NetworkPolicy; + use crate::config::NetworkProxySettings; use crate::runtime::network_proxy_state_for_policy; use pretty_assertions::assert_eq; use rama_http::Method; @@ -697,7 +697,7 @@ mod tests { #[tokio::test] async fn http_connect_accept_blocks_in_limited_mode() { - let policy = NetworkPolicy { + let policy = NetworkProxySettings { allowed_domains: vec!["example.com".to_string()], ..Default::default() }; diff --git a/codex-rs/network-proxy/src/network_policy.rs b/codex-rs/network-proxy/src/network_policy.rs index aa79acbd1fb..b70c3e0b170 100644 --- a/codex-rs/network-proxy/src/network_policy.rs +++ b/codex-rs/network-proxy/src/network_policy.rs @@ -217,7 +217,7 @@ fn map_decider_decision(decision: NetworkDecision) -> NetworkDecision { mod tests { use super::*; - use crate::config::NetworkPolicy; + use crate::config::NetworkProxySettings; use crate::reasons::REASON_DENIED; use crate::reasons::REASON_NOT_ALLOWED_LOCAL; use crate::state::network_proxy_state_for_policy; @@ -228,7 +228,7 @@ mod tests { #[tokio::test] async fn evaluate_host_policy_invokes_decider_for_not_allowed() { - let state = network_proxy_state_for_policy(NetworkPolicy::default()); + let state = network_proxy_state_for_policy(NetworkProxySettings::default()); let calls = Arc::new(AtomicUsize::new(0)); let decider: Arc = Arc::new({ let calls = calls.clone(); @@ -259,10 +259,10 @@ mod tests { #[tokio::test] async fn evaluate_host_policy_skips_decider_for_denied() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["example.com".to_string()], denied_domains: vec!["blocked.com".to_string()], - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); let calls = Arc::new(AtomicUsize::new(0)); let decider: Arc = Arc::new({ @@ -299,10 +299,10 @@ mod tests { #[tokio::test] async fn evaluate_host_policy_skips_decider_for_not_allowed_local() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["example.com".to_string()], allow_local_binding: false, - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); let calls = Arc::new(AtomicUsize::new(0)); let decider: Arc = Arc::new({ diff --git a/codex-rs/network-proxy/src/proxy.rs b/codex-rs/network-proxy/src/proxy.rs index cb09b9959dd..ab347c2c034 100644 --- a/codex-rs/network-proxy/src/proxy.rs +++ b/codex-rs/network-proxy/src/proxy.rs @@ -66,7 +66,7 @@ impl NetworkProxyBuilder { self.http_addr.unwrap_or(runtime.http_addr), runtime.socks_addr, self.admin_addr.unwrap_or(runtime.admin_addr), - ¤t_cfg.network_proxy, + ¤t_cfg.network, ); Ok(NetworkProxy { @@ -95,8 +95,8 @@ impl NetworkProxy { pub async fn run(&self) -> Result { let current_cfg = self.state.current_cfg().await?; - if !current_cfg.network_proxy.enabled { - warn!("network_proxy.enabled is false; skipping proxy listeners"); + if !current_cfg.network.enabled { + warn!("network.enabled is false; skipping proxy listeners"); return Ok(NetworkProxyHandle::noop()); } @@ -109,12 +109,12 @@ impl NetworkProxy { self.http_addr, self.policy_decider.clone(), )); - let socks_task = if current_cfg.network_proxy.enable_socks5 { + let socks_task = if current_cfg.network.enable_socks5 { Some(tokio::spawn(socks5::run_socks5( self.state.clone(), self.socks_addr, self.policy_decider.clone(), - current_cfg.network_proxy.enable_socks5_udp, + current_cfg.network.enable_socks5_udp, ))) } else { None diff --git a/codex-rs/network-proxy/src/runtime.rs b/codex-rs/network-proxy/src/runtime.rs index 1c29672cfac..e529d9dfce5 100644 --- a/codex-rs/network-proxy/src/runtime.rs +++ b/codex-rs/network-proxy/src/runtime.rs @@ -161,15 +161,15 @@ impl NetworkProxyState { self.reload_if_needed().await?; let guard = self.state.read().await; Ok(( - guard.config.network_proxy.policy.allowed_domains.clone(), - guard.config.network_proxy.policy.denied_domains.clone(), + guard.config.network.allowed_domains.clone(), + guard.config.network.denied_domains.clone(), )) } pub async fn enabled(&self) -> Result { self.reload_if_needed().await?; let guard = self.state.read().await; - Ok(guard.config.network_proxy.enabled) + Ok(guard.config.network.enabled) } pub async fn force_reload(&self) -> Result<()> { @@ -209,9 +209,9 @@ impl NetworkProxyState { ( guard.deny_set.clone(), guard.allow_set.clone(), - guard.config.network_proxy.policy.allow_local_binding, - guard.config.network_proxy.policy.allowed_domains.is_empty(), - guard.config.network_proxy.policy.allowed_domains.clone(), + guard.config.network.allow_local_binding, + guard.config.network.allowed_domains.is_empty(), + guard.config.network.allowed_domains.clone(), ) }; @@ -305,7 +305,7 @@ impl NetworkProxyState { Err(_) => return Ok(false), }; let requested_canonical = std::fs::canonicalize(requested_abs.as_path()).ok(); - for allowed in &guard.config.network_proxy.policy.allow_unix_sockets { + for allowed in &guard.config.network.allow_unix_sockets { if allowed == path { return Ok(true); } @@ -327,19 +327,19 @@ 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_proxy.mode.allows_method(method)) + Ok(guard.config.network.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_proxy.allow_upstream_proxy) + Ok(guard.config.network.allow_upstream_proxy) } pub async fn network_mode(&self) -> Result { self.reload_if_needed().await?; let guard = self.state.read().await; - Ok(guard.config.network_proxy.mode) + Ok(guard.config.network.mode) } pub async fn set_network_mode(&self, mode: NetworkMode) -> Result<()> { @@ -348,19 +348,19 @@ impl NetworkProxyState { let (candidate, constraints) = { let guard = self.state.read().await; let mut candidate = guard.config.clone(); - candidate.network_proxy.mode = mode; + candidate.network.mode = mode; (candidate, guard.constraints.clone()) }; validate_policy_against_constraints(&candidate, &constraints) - .context("network_proxy.mode constrained by managed config")?; + .context("network.mode constrained by managed config")?; let mut guard = self.state.write().await; if guard.constraints != constraints { drop(guard); continue; } - guard.config.network_proxy.mode = mode; + guard.config.network.mode = mode; info!("updated network mode to {mode:?}"); return Ok(()); } @@ -417,13 +417,13 @@ async fn host_resolves_to_non_public_ip(host: &str, port: u16) -> bool { fn log_policy_changes(previous: &NetworkProxyConfig, next: &NetworkProxyConfig) { log_domain_list_changes( "allowlist", - &previous.network_proxy.policy.allowed_domains, - &next.network_proxy.policy.allowed_domains, + &previous.network.allowed_domains, + &next.network.allowed_domains, ); log_domain_list_changes( "denylist", - &previous.network_proxy.policy.denied_domains, - &next.network_proxy.policy.denied_domains, + &previous.network.denied_domains, + &next.network.denied_domains, ); } @@ -483,21 +483,14 @@ fn unix_timestamp() -> i64 { #[cfg(test)] pub(crate) fn network_proxy_state_for_policy( - policy: crate::config::NetworkPolicy, + mut network: crate::config::NetworkProxySettings, ) -> NetworkProxyState { - let config = NetworkProxyConfig { - network_proxy: crate::config::NetworkProxySettings { - enabled: true, - mode: NetworkMode::Full, - policy, - ..crate::config::NetworkProxySettings::default() - }, - }; + network.enabled = true; + network.mode = NetworkMode::Full; + let config = NetworkProxyConfig { network }; - let allow_set = - crate::policy::compile_globset(&config.network_proxy.policy.allowed_domains).unwrap(); - let deny_set = - crate::policy::compile_globset(&config.network_proxy.policy.denied_domains).unwrap(); + let allow_set = crate::policy::compile_globset(&config.network.allowed_domains).unwrap(); + let deny_set = crate::policy::compile_globset(&config.network.denied_domains).unwrap(); let state = ConfigState { config, @@ -518,7 +511,6 @@ pub(crate) fn network_proxy_state_for_policy( mod tests { use super::*; - use crate::config::NetworkPolicy; use crate::config::NetworkProxyConfig; use crate::config::NetworkProxySettings; use crate::policy::compile_globset; @@ -528,10 +520,10 @@ mod tests { #[tokio::test] async fn host_blocked_denied_wins_over_allowed() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["example.com".to_string()], denied_domains: vec!["example.com".to_string()], - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert_eq!( @@ -542,9 +534,9 @@ mod tests { #[tokio::test] async fn host_blocked_requires_allowlist_match() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["example.com".to_string()], - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert_eq!( @@ -561,9 +553,9 @@ mod tests { #[tokio::test] async fn host_blocked_subdomain_wildcards_exclude_apex() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["*.openai.com".to_string()], - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert_eq!( @@ -578,10 +570,10 @@ mod tests { #[tokio::test] async fn host_blocked_rejects_loopback_when_local_binding_disabled() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["example.com".to_string()], allow_local_binding: false, - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert_eq!( @@ -596,10 +588,10 @@ mod tests { #[tokio::test] async fn host_blocked_rejects_loopback_when_allowlist_is_wildcard() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["*".to_string()], allow_local_binding: false, - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert_eq!( @@ -610,10 +602,10 @@ mod tests { #[tokio::test] async fn host_blocked_rejects_private_ip_literal_when_allowlist_is_wildcard() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["*".to_string()], allow_local_binding: false, - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert_eq!( @@ -624,10 +616,10 @@ mod tests { #[tokio::test] async fn host_blocked_allows_loopback_when_explicitly_allowlisted_and_local_binding_disabled() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["localhost".to_string()], allow_local_binding: false, - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert_eq!( @@ -638,10 +630,10 @@ mod tests { #[tokio::test] async fn host_blocked_allows_private_ip_literal_when_explicitly_allowlisted() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["10.0.0.1".to_string()], allow_local_binding: false, - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert_eq!( @@ -652,10 +644,10 @@ mod tests { #[tokio::test] async fn host_blocked_rejects_scoped_ipv6_literal_when_not_allowlisted() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["example.com".to_string()], allow_local_binding: false, - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert_eq!( @@ -666,10 +658,10 @@ mod tests { #[tokio::test] async fn host_blocked_allows_scoped_ipv6_literal_when_explicitly_allowlisted() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["fe80::1%lo0".to_string()], allow_local_binding: false, - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert_eq!( @@ -680,10 +672,10 @@ mod tests { #[tokio::test] async fn host_blocked_rejects_private_ip_literals_when_local_binding_disabled() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["example.com".to_string()], allow_local_binding: false, - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert_eq!( @@ -694,10 +686,10 @@ mod tests { #[tokio::test] async fn host_blocked_rejects_loopback_when_allowlist_empty() { - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec![], allow_local_binding: false, - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert_eq!( @@ -714,12 +706,9 @@ mod tests { }; let config = NetworkProxyConfig { - network_proxy: NetworkProxySettings { + network: NetworkProxySettings { enabled: true, - policy: NetworkPolicy { - allowed_domains: vec!["example.com".to_string(), "evil.com".to_string()], - ..NetworkPolicy::default() - }, + allowed_domains: vec!["example.com".to_string(), "evil.com".to_string()], ..NetworkProxySettings::default() }, }; @@ -735,7 +724,7 @@ mod tests { }; let config = NetworkProxyConfig { - network_proxy: NetworkProxySettings { + network: NetworkProxySettings { enabled: true, mode: NetworkMode::Full, ..NetworkProxySettings::default() @@ -753,12 +742,9 @@ mod tests { }; let config = NetworkProxyConfig { - network_proxy: NetworkProxySettings { + network: NetworkProxySettings { enabled: true, - policy: NetworkPolicy { - allowed_domains: vec!["api.example.com".to_string()], - ..NetworkPolicy::default() - }, + allowed_domains: vec!["api.example.com".to_string()], ..NetworkProxySettings::default() }, }; @@ -774,12 +760,9 @@ mod tests { }; let config = NetworkProxyConfig { - network_proxy: NetworkProxySettings { + network: NetworkProxySettings { enabled: true, - policy: NetworkPolicy { - allowed_domains: vec!["**.example.com".to_string()], - ..NetworkPolicy::default() - }, + allowed_domains: vec!["**.example.com".to_string()], ..NetworkProxySettings::default() }, }; @@ -795,12 +778,9 @@ mod tests { }; let config = NetworkProxyConfig { - network_proxy: NetworkProxySettings { + network: NetworkProxySettings { enabled: true, - policy: NetworkPolicy { - denied_domains: vec![], - ..NetworkPolicy::default() - }, + denied_domains: vec![], ..NetworkProxySettings::default() }, }; @@ -816,7 +796,7 @@ mod tests { }; let config = NetworkProxyConfig { - network_proxy: NetworkProxySettings { + network: NetworkProxySettings { enabled: true, ..NetworkProxySettings::default() }, @@ -833,12 +813,9 @@ mod tests { }; let config = NetworkProxyConfig { - network_proxy: NetworkProxySettings { + network: NetworkProxySettings { enabled: true, - policy: NetworkPolicy { - allow_local_binding: true, - ..NetworkPolicy::default() - }, + allow_local_binding: true, ..NetworkProxySettings::default() }, }; @@ -854,7 +831,7 @@ mod tests { }; let config = NetworkProxyConfig { - network_proxy: NetworkProxySettings { + network: NetworkProxySettings { enabled: true, dangerously_allow_non_loopback_admin: true, ..NetworkProxySettings::default() @@ -872,7 +849,7 @@ mod tests { }; let config = NetworkProxyConfig { - network_proxy: NetworkProxySettings { + network: NetworkProxySettings { enabled: true, dangerously_allow_non_loopback_admin: true, ..NetworkProxySettings::default() @@ -935,10 +912,10 @@ mod tests { #[tokio::test] async fn unix_socket_allowlist_is_respected_on_macos() { let socket_path = "/tmp/example.sock".to_string(); - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["example.com".to_string()], allow_unix_sockets: vec![socket_path.clone()], - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert!(state.is_unix_socket_allowed(&socket_path).await.unwrap()); @@ -970,10 +947,10 @@ mod tests { let real_s = real.to_str().unwrap().to_string(); let link_s = link.to_str().unwrap().to_string(); - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["example.com".to_string()], allow_unix_sockets: vec![real_s], - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert!(state.is_unix_socket_allowed(&link_s).await.unwrap()); @@ -983,10 +960,10 @@ mod tests { #[tokio::test] async fn unix_socket_allowlist_is_rejected_on_non_macos() { let socket_path = "/tmp/example.sock".to_string(); - let state = network_proxy_state_for_policy(NetworkPolicy { + let state = network_proxy_state_for_policy(NetworkProxySettings { allowed_domains: vec!["example.com".to_string()], allow_unix_sockets: vec![socket_path.clone()], - ..NetworkPolicy::default() + ..NetworkProxySettings::default() }); assert!(!state.is_unix_socket_allowed(&socket_path).await.unwrap()); diff --git a/codex-rs/network-proxy/src/state.rs b/codex-rs/network-proxy/src/state.rs index 20c328c7505..179d33dec45 100644 --- a/codex-rs/network-proxy/src/state.rs +++ b/codex-rs/network-proxy/src/state.rs @@ -8,7 +8,6 @@ use anyhow::Context; use anyhow::Result; use codex_app_server_protocol::ConfigLayerSource; use codex_core::config::CONFIG_TOML_FILE; -use codex_core::config::Constrained; use codex_core::config::ConstraintError; use codex_core::config::find_codex_home; use codex_core::config_loader::CloudRequirementsLoader; @@ -56,8 +55,8 @@ pub(crate) async fn build_config_state() -> Result { let constraints = enforce_trusted_constraints(&config_layer_stack, &config)?; let layer_mtimes = collect_layer_mtimes(&config_layer_stack); - let deny_set = compile_globset(&config.network_proxy.policy.denied_domains)?; - let allow_set = compile_globset(&config.network_proxy.policy.allowed_domains)?; + let deny_set = compile_globset(&config.network.denied_domains)?; + let allow_set = compile_globset(&config.network.allowed_domains)?; Ok(ConfigState { config, allow_set, @@ -94,22 +93,16 @@ fn collect_layer_mtimes(stack: &ConfigLayerStack) -> Vec { #[derive(Debug, Default, Deserialize)] struct PartialConfig { #[serde(default)] - network_proxy: PartialNetworkProxyConfig, + network: PartialNetworkConfig, } #[derive(Debug, Default, Deserialize)] -struct PartialNetworkProxyConfig { +struct PartialNetworkConfig { enabled: Option, mode: Option, allow_upstream_proxy: Option, dangerously_allow_non_loopback_proxy: Option, dangerously_allow_non_loopback_admin: Option, - #[serde(default)] - policy: PartialNetworkPolicy, -} - -#[derive(Debug, Default, Deserialize)] -struct PartialNetworkPolicy { #[serde(default)] allowed_domains: Option>, #[serde(default)] @@ -137,13 +130,13 @@ fn enforce_trusted_constraints( layers: &codex_core::config_loader::ConfigLayerStack, config: &NetworkProxyConfig, ) -> Result { - let constraints = network_proxy_constraints_from_trusted_layers(layers)?; + let constraints = network_constraints_from_trusted_layers(layers)?; validate_policy_against_constraints(config, &constraints) .context("network proxy constraints")?; Ok(constraints) } -fn network_proxy_constraints_from_trusted_layers( +fn network_constraints_from_trusted_layers( layers: &codex_core::config_loader::ConfigLayerStack, ) -> Result { let mut constraints = NetworkProxyConstraints::default(); @@ -163,38 +156,38 @@ fn network_proxy_constraints_from_trusted_layers( .try_into() .context("failed to deserialize trusted config layer")?; - if let Some(enabled) = partial.network_proxy.enabled { + if let Some(enabled) = partial.network.enabled { constraints.enabled = Some(enabled); } - if let Some(mode) = partial.network_proxy.mode { + if let Some(mode) = partial.network.mode { constraints.mode = Some(mode); } - if let Some(allow_upstream_proxy) = partial.network_proxy.allow_upstream_proxy { + if let Some(allow_upstream_proxy) = partial.network.allow_upstream_proxy { constraints.allow_upstream_proxy = Some(allow_upstream_proxy); } if let Some(dangerously_allow_non_loopback_proxy) = - partial.network_proxy.dangerously_allow_non_loopback_proxy + partial.network.dangerously_allow_non_loopback_proxy { constraints.dangerously_allow_non_loopback_proxy = Some(dangerously_allow_non_loopback_proxy); } if let Some(dangerously_allow_non_loopback_admin) = - partial.network_proxy.dangerously_allow_non_loopback_admin + partial.network.dangerously_allow_non_loopback_admin { constraints.dangerously_allow_non_loopback_admin = Some(dangerously_allow_non_loopback_admin); } - if let Some(allowed_domains) = partial.network_proxy.policy.allowed_domains { + if let Some(allowed_domains) = partial.network.allowed_domains { constraints.allowed_domains = Some(allowed_domains); } - if let Some(denied_domains) = partial.network_proxy.policy.denied_domains { + if let Some(denied_domains) = partial.network.denied_domains { constraints.denied_domains = Some(denied_domains); } - if let Some(allow_unix_sockets) = partial.network_proxy.policy.allow_unix_sockets { + if let Some(allow_unix_sockets) = partial.network.allow_unix_sockets { constraints.allow_unix_sockets = Some(allow_unix_sockets); } - if let Some(allow_local_binding) = partial.network_proxy.policy.allow_local_binding { + if let Some(allow_local_binding) = partial.network.allow_local_binding { constraints.allow_local_binding = Some(allow_local_binding); } } @@ -227,12 +220,19 @@ pub(crate) fn validate_policy_against_constraints( } } - let enabled = config.network_proxy.enabled; + fn validate( + candidate: T, + validator: impl FnOnce(&T) -> std::result::Result<(), ConstraintError>, + ) -> std::result::Result<(), ConstraintError> { + validator(&candidate) + } + + let enabled = config.network.enabled; if let Some(max_enabled) = constraints.enabled { - let _ = Constrained::new(enabled, move |candidate| { + validate(enabled, move |candidate| { if *candidate && !max_enabled { Err(invalid_value( - "network_proxy.enabled", + "network.enabled", "true", "false (disabled by managed config)", )) @@ -243,10 +243,10 @@ pub(crate) fn validate_policy_against_constraints( } if let Some(max_mode) = constraints.mode { - let _ = Constrained::new(config.network_proxy.mode, move |candidate| { + validate(config.network.mode, move |candidate| { if network_mode_rank(*candidate) > network_mode_rank(max_mode) { Err(invalid_value( - "network_proxy.mode", + "network.mode", format!("{candidate:?}"), format!("{max_mode:?} or more restrictive"), )) @@ -257,14 +257,14 @@ pub(crate) fn validate_policy_against_constraints( } let allow_upstream_proxy = constraints.allow_upstream_proxy; - let _ = Constrained::new( - config.network_proxy.allow_upstream_proxy, + validate( + config.network.allow_upstream_proxy, move |candidate| match allow_upstream_proxy { Some(true) | None => Ok(()), Some(false) => { if *candidate { Err(invalid_value( - "network_proxy.allow_upstream_proxy", + "network.allow_upstream_proxy", "true", "false (disabled by managed config)", )) @@ -276,14 +276,14 @@ pub(crate) fn validate_policy_against_constraints( )?; let allow_non_loopback_admin = constraints.dangerously_allow_non_loopback_admin; - let _ = Constrained::new( - config.network_proxy.dangerously_allow_non_loopback_admin, + validate( + config.network.dangerously_allow_non_loopback_admin, move |candidate| match allow_non_loopback_admin { Some(true) | None => Ok(()), Some(false) => { if *candidate { Err(invalid_value( - "network_proxy.dangerously_allow_non_loopback_admin", + "network.dangerously_allow_non_loopback_admin", "true", "false (disabled by managed config)", )) @@ -295,14 +295,14 @@ pub(crate) fn validate_policy_against_constraints( )?; let allow_non_loopback_proxy = constraints.dangerously_allow_non_loopback_proxy; - let _ = Constrained::new( - config.network_proxy.dangerously_allow_non_loopback_proxy, + validate( + config.network.dangerously_allow_non_loopback_proxy, move |candidate| match allow_non_loopback_proxy { Some(true) | None => Ok(()), Some(false) => { if *candidate { Err(invalid_value( - "network_proxy.dangerously_allow_non_loopback_proxy", + "network.dangerously_allow_non_loopback_proxy", "true", "false (disabled by managed config)", )) @@ -314,20 +314,17 @@ pub(crate) fn validate_policy_against_constraints( )?; if let Some(allow_local_binding) = constraints.allow_local_binding { - let _ = Constrained::new( - config.network_proxy.policy.allow_local_binding, - move |candidate| { - if *candidate && !allow_local_binding { - Err(invalid_value( - "network_proxy.policy.allow_local_binding", - "true", - "false (disabled by managed config)", - )) - } else { - Ok(()) - } - }, - )?; + validate(config.network.allow_local_binding, move |candidate| { + if *candidate && !allow_local_binding { + Err(invalid_value( + "network.allow_local_binding", + "true", + "false (disabled by managed config)", + )) + } else { + Ok(()) + } + })?; } if let Some(allowed_domains) = &constraints.allowed_domains { @@ -335,30 +332,27 @@ pub(crate) fn validate_policy_against_constraints( .iter() .map(|entry| DomainPattern::parse_for_constraints(entry)) .collect(); - let _ = Constrained::new( - config.network_proxy.policy.allowed_domains.clone(), - move |candidate| { - let mut invalid = Vec::new(); - for entry in candidate { - let candidate_pattern = DomainPattern::parse_for_constraints(entry); - if !managed_patterns - .iter() - .any(|managed| managed.allows(&candidate_pattern)) - { - invalid.push(entry.clone()); - } - } - if invalid.is_empty() { - Ok(()) - } else { - Err(invalid_value( - "network_proxy.policy.allowed_domains", - format!("{invalid:?}"), - "subset of managed allowed_domains", - )) + validate(config.network.allowed_domains.clone(), move |candidate| { + let mut invalid = Vec::new(); + for entry in candidate { + let candidate_pattern = DomainPattern::parse_for_constraints(entry); + if !managed_patterns + .iter() + .any(|managed| managed.allows(&candidate_pattern)) + { + invalid.push(entry.clone()); } - }, - )?; + } + if invalid.is_empty() { + Ok(()) + } else { + Err(invalid_value( + "network.allowed_domains", + format!("{invalid:?}"), + "subset of managed allowed_domains", + )) + } + })?; } if let Some(denied_domains) = &constraints.denied_domains { @@ -366,27 +360,24 @@ pub(crate) fn validate_policy_against_constraints( .iter() .map(|s| s.to_ascii_lowercase()) .collect(); - let _ = Constrained::new( - config.network_proxy.policy.denied_domains.clone(), - move |candidate| { - let candidate_set: HashSet = - candidate.iter().map(|s| s.to_ascii_lowercase()).collect(); - let missing: Vec = required_set - .iter() - .filter(|entry| !candidate_set.contains(*entry)) - .cloned() - .collect(); - if missing.is_empty() { - Ok(()) - } else { - Err(invalid_value( - "network_proxy.policy.denied_domains", - "missing managed denied_domains entries", - format!("{missing:?}"), - )) - } - }, - )?; + validate(config.network.denied_domains.clone(), move |candidate| { + let candidate_set: HashSet = + candidate.iter().map(|s| s.to_ascii_lowercase()).collect(); + let missing: Vec = required_set + .iter() + .filter(|entry| !candidate_set.contains(*entry)) + .cloned() + .collect(); + if missing.is_empty() { + Ok(()) + } else { + Err(invalid_value( + "network.denied_domains", + "missing managed denied_domains entries", + format!("{missing:?}"), + )) + } + })?; } if let Some(allow_unix_sockets) = &constraints.allow_unix_sockets { @@ -394,8 +385,8 @@ pub(crate) fn validate_policy_against_constraints( .iter() .map(|s| s.to_ascii_lowercase()) .collect(); - let _ = Constrained::new( - config.network_proxy.policy.allow_unix_sockets.clone(), + validate( + config.network.allow_unix_sockets.clone(), move |candidate| { let mut invalid = Vec::new(); for entry in candidate { @@ -407,7 +398,7 @@ pub(crate) fn validate_policy_against_constraints( Ok(()) } else { Err(invalid_value( - "network_proxy.policy.allow_unix_sockets", + "network.allow_unix_sockets", format!("{invalid:?}"), "subset of managed allow_unix_sockets", ))