diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index bf7cc246da..700d7b8351 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -1372,6 +1372,7 @@ fn analytics_rpc_transport(transport: &AppServerTransport) -> AppServerRpcTransp #[cfg(test)] mod tests { use super::LogFormat; + #[cfg(target_os = "windows")] use super::is_unrecoverable_windows_network_config_error; #[cfg(debug_assertions)] use super::loader_overrides_with_test_user_config_file; diff --git a/codex-rs/config/src/config_requirements.rs b/codex-rs/config/src/config_requirements.rs index fd19c79a78..45058ef82a 100644 --- a/codex-rs/config/src/config_requirements.rs +++ b/codex-rs/config/src/config_requirements.rs @@ -756,10 +756,13 @@ impl WindowsRequirementsToml { } pub fn allows_only_elevated_sandbox(&self) -> bool { - matches!( - self.allowed_sandbox_implementations.as_deref(), - Some([WindowsSandboxModeToml::Elevated]) - ) + let Some(modes) = self.allowed_sandbox_implementations.as_deref() else { + return false; + }; + !modes.is_empty() + && modes + .iter() + .all(|&mode| mode == WindowsSandboxModeToml::Elevated) } } diff --git a/codex-rs/core/src/context/environment_context.rs b/codex-rs/core/src/context/environment_context.rs index 2f795a8ea3..cae2d00ea2 100644 --- a/codex-rs/core/src/context/environment_context.rs +++ b/codex-rs/core/src/context/environment_context.rs @@ -212,22 +212,26 @@ pub(crate) fn push_xml_escaped_text(rendered: &mut String, value: &str) { #[derive(Debug, Clone, PartialEq, Eq, Default)] pub(crate) struct NetworkContext { + enabled: bool, allowed_domains: Vec, denied_domains: Vec, } impl NetworkContext { - pub(crate) fn new(allowed_domains: Vec, denied_domains: Vec) -> Self { + pub(crate) fn new(enabled: bool, allowed: Vec, denied: Vec) -> Self { Self { - allowed_domains, - denied_domains, + enabled, + allowed_domains: allowed, + denied_domains: denied, } } pub(super) fn render(&self) -> String { - let mut rendered = "".to_string(); - Self::push_rendered_domain_element(&mut rendered, "allowed", &self.allowed_domains); - Self::push_rendered_domain_element(&mut rendered, "denied", &self.denied_domains); + let mut rendered = format!("", self.enabled); + if self.enabled { + Self::push_rendered_domain_element(&mut rendered, "allowed", &self.allowed_domains); + Self::push_rendered_domain_element(&mut rendered, "denied", &self.denied_domains); + } rendered.push_str(""); rendered } diff --git a/codex-rs/core/src/context/world_state/environment.rs b/codex-rs/core/src/context/world_state/environment.rs index d34124ecf5..c2fd170e30 100644 --- a/codex-rs/core/src/context/world_state/environment.rs +++ b/codex-rs/core/src/context/world_state/environment.rs @@ -369,6 +369,7 @@ fn network_from_turn_context(turn_context: &TurnContext) -> Option Result<()> { current_date: Some("2026-06-19".to_string()), timezone: Some("UTC".to_string()), network: Some(NetworkContext::new( + true, vec!["old.example.com".to_string()], vec![], )), @@ -131,6 +132,7 @@ fn persisted_turn_context_values_render_a_diff() -> Result<()> { current_date: Some("2026-06-20".to_string()), timezone: Some("America/Los_Angeles".to_string()), network: Some(NetworkContext::new( + true, vec!["new.example.com".to_string()], vec!["blocked.example.com".to_string()], )), diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index df2ead5481..f98958bf26 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -8020,17 +8020,30 @@ async fn spawn_task_does_not_update_previous_turn_settings_for_non_run_turn_task } #[tokio::test] -async fn record_context_updates_emits_environment_item_for_network_changes() { - let (session, previous_context) = make_session_and_context().await; - let previous_context = Arc::new(previous_context); - let mut current_context = previous_context - .with_model( - previous_context.model_info.slug.clone(), - &session.services.models_manager, +async fn record_context_updates_emits_disabled_network_transition() { + let (session, mut previous_context) = make_session_and_context().await; + let permission_profile = PermissionProfile::workspace_write(); + let mut network_config = NetworkProxyConfig::default(); + network_config.enabled = true; + let network_spec = crate::config::NetworkProxySpec::from_config_and_constraints( + network_config, + /*requirements*/ None, + &permission_profile, + ) + .expect("build network proxy spec"); + let started_proxy = network_spec + .start_proxy( + &permission_profile, + /*policy_decider*/ None, + /*blocked_request_observer*/ None, + /*enable_network_approval_flow*/ false, + crate::config::NetworkProxyAuditMetadata::default(), ) - .await; + .await + .expect("start network proxy"); + previous_context.network = Some(started_proxy.proxy()); - let mut config = (*current_context.config).clone(); + let mut config = (*previous_context.config).clone(); let mut requirements = config.config_layer_stack.requirements().clone(); requirements.network = Some(Sourced::new( NetworkConstraints { @@ -8065,7 +8078,15 @@ async fn record_context_updates_emits_environment_item_for_network_changes() { config.config_layer_stack.requirements_toml().clone(), ) .expect("rebuild config layer stack with network requirements"); - current_context.config = Arc::new(config); + previous_context.config = Arc::new(config); + let previous_context = Arc::new(previous_context); + let mut current_context = previous_context + .with_model( + previous_context.model_info.slug.clone(), + &session.services.models_manager, + ) + .await; + current_context.network = None; let update_items = record_context_update_items(&session, previous_context, current_context).await; @@ -8074,9 +8095,7 @@ async fn record_context_updates_emits_environment_item_for_network_changes() { .into_iter() .find(|text| text.contains("")) .expect("environment update item should be emitted"); - assert!(environment_update.contains( - "api.example.comblocked.example.com" - )); + assert!(environment_update.contains("")); } #[tokio::test]