Enforce application network policy for daemon updates (#52223)

## Why

Daemon updates must honor application network restrictions and stop when their configuration cannot be loaded.

## What changed

Activate the configured application network policy before creating the HTTP client for daemon updates and the PID update loop. Propagate configuration errors instead of falling back to a default HTTP client.

## Testing

Add tests for configuration error propagation and managed network requirements, covering denied and unlisted destinations as well as an allowed destination. Retain coverage for `features.respect_system_proxy`.

GitOrigin-RevId: 960485c7c57f34654402d7316cab9e0f66f24af3
This commit is contained in:
jackz
2026-10-08 18:53:17 +00:00
committed by copyberry
parent 89d68bc8d2
commit d570b3632b
+58 -15
View File
@@ -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_core::config::Config>,
) -> 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<codex_http_client::HttpClientFactory> {
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()
);
}