diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index e90a13c32f..1c440738fd 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -1364,7 +1364,7 @@ async fn cli_main( .build() .await .map_err(anyhow::Error::from); - let http_client_factory = updater_http_client_factory(config); + let http_client_factory = updater_http_client_factory(config)?; if matches!( daemon_cli.subcommand, AppServerDaemonSubcommand::Update { .. } @@ -2358,16 +2358,10 @@ async fn print_app_server_daemon_output(command: AppServerLifecycleCommand) -> a fn updater_http_client_factory( config: anyhow::Result, -) -> codex_http_client::HttpClientFactory { - match config { - Ok(config) => config.http_client_factory(), - Err(error) => { - eprintln!("warning: failed to load updater network configuration: {error}"); - codex_http_client::HttpClientFactory::new( - codex_http_client::OutboundProxyPolicy::ReqwestDefault, - ) - } - } +) -> anyhow::Result { + let mut config = config?; + codex_app_server::in_process::EmbeddedNetworkPolicy::default().activate(&mut config); + Ok(config.http_client_factory()) } async fn print_app_server_remote_control_output( @@ -2804,17 +2798,66 @@ mod tests { .expect("config should load"); assert_eq!( - updater_http_client_factory(Ok(config)).outbound_proxy_policy(), + updater_http_client_factory(Ok(config)) + .expect("updater configuration") + .outbound_proxy_policy(), codex_http_client::OutboundProxyPolicy::RespectSystemProxy ); } #[test] - fn updater_http_client_factory_falls_back_when_config_load_fails() { + fn updater_http_client_factory_propagates_config_errors() { assert_eq!( updater_http_client_factory(Err(anyhow::anyhow!("invalid config"))) - .outbound_proxy_policy(), - codex_http_client::OutboundProxyPolicy::ReqwestDefault + .expect_err("invalid configuration must prevent updates") + .to_string(), + "invalid config" + ); + } + + #[tokio::test] + async fn updater_http_client_factory_enforces_application_requirements() { + let home = tempfile::tempdir().expect("temporary Codex home"); + let requirements = home.path().join("requirements.toml"); + std::fs::write( + &requirements, + r#" +[application.network] +enabled = true +[application.network.domains] +"bedrock-mantle.us-gov-west-1.api.aws" = "allow" +"chatgpt.com" = "deny" +"releases.openai.com" = "deny" +"#, + ) + .expect("write managed requirements"); + let mut overrides = LoaderOverrides::without_managed_config_for_tests(); + overrides.system_requirements_path = Some(requirements); + let config = ConfigBuilder::default() + .codex_home(home.path().to_path_buf()) + .loader_overrides(overrides) + .build() + .await + .expect("load managed configuration"); + let factory = updater_http_client_factory(Ok(config)).expect("updater configuration"); + for host in ["chatgpt.com", "releases.openai.com", "unlisted.example"] { + assert_eq!( + factory + .network_policy() + .acquire(&format!("https://{host}/").parse().expect("URL")) + .err(), + Some(codex_http_client::NetworkPolicyDenied::Destination) + ); + } + assert!( + factory + .network_policy() + .acquire( + &"https://bedrock-mantle.us-gov-west-1.api.aws/" + .parse() + .expect("URL") + ) + .is_ok() ); }