feat(network-proxy): expose managed domain policy

Co-authored-by: Codex noreply@openai.com
This commit is contained in:
viyatb-oai
2026-07-08 13:47:35 -07:00
co-authored by Codex noreply@openai.com
parent ba5dd1fd3a
commit 048683163d
8 changed files with 100 additions and 8 deletions
@@ -326,10 +326,11 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
let (env, managed_network_context) = match managed_network {
Some(network) => {
let prepared = network
.prepare_for_optional_environment(
.prepare_for_optional_environment_with_current_policy(
env,
Some(&req.turn_environment.environment_id),
)
.await
.map_err(|err| {
ToolError::Codex(CodexErr::Io(io::Error::other(format!(
"failed to prepare network proxy for environment `{}`: {err}",
@@ -232,6 +232,7 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
let managed_network = ManagedNetworkSandboxContext {
loopback_ports: vec![43123],
allow_local_binding: false,
domain_policy: None,
};
let command = || SandboxCommand {
program: "/bin/bash".into(),
@@ -117,6 +117,7 @@ fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() {
let managed_network = ManagedNetworkSandboxContext {
loopback_ports: vec![43123],
allow_local_binding: false,
domain_policy: None,
};
let mut request = ExecRequest {
command: vec!["bash".to_string(), "-lc".to_string(), "true".to_string()],
@@ -573,6 +573,7 @@ mod tests {
use super::ProcessId;
use super::ShellInfo;
use codex_file_system::FileSystemSandboxContext;
use codex_network_proxy::ManagedNetworkDomainPolicy;
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_protocol::models::PermissionProfile;
use codex_utils_path_uri::PathUri;
@@ -598,6 +599,10 @@ mod tests {
managed_network: Some(ManagedNetworkSandboxContext {
loopback_ports: vec![43123, 48081],
allow_local_binding: false,
domain_policy: Some(ManagedNetworkDomainPolicy {
allowed_domains: vec!["**.example.com".to_string()],
denied_domains: vec!["blocked.example.com".to_string()],
}),
}),
};
@@ -607,12 +612,31 @@ mod tests {
serde_json::json!({
"loopbackPorts": [43123, 48081],
"allowLocalBinding": false,
"domainPolicy": {
"allowedDomains": ["**.example.com"],
"deniedDomains": ["blocked.example.com"],
},
})
);
let round_trip: ExecParams =
serde_json::from_value(serialized.clone()).expect("deserialize exec params");
assert_eq!(round_trip, params);
let mut without_domain_policy = serialized.clone();
without_domain_policy["managedNetwork"]
.as_object_mut()
.expect("managed network object")
.remove("domainPolicy");
let legacy_context: ExecParams = serde_json::from_value(without_domain_policy)
.expect("deserialize legacy managed network context");
assert_eq!(
legacy_context
.managed_network
.expect("managed network context")
.domain_policy,
None
);
serialized
.as_object_mut()
.expect("exec params object")
@@ -110,6 +110,7 @@ fn sandbox_request_allows_prepared_managed_proxy_port() {
managed_network: Some(ManagedNetworkSandboxContext {
loopback_ports: vec![43123],
allow_local_binding: false,
domain_policy: None,
}),
};
+1
View File
@@ -54,6 +54,7 @@ pub use proxy::Args;
#[cfg(target_os = "macos")]
pub use proxy::CODEX_PROXY_GIT_SSH_COMMAND_MARKER;
pub use proxy::DEFAULT_NO_PROXY_VALUE;
pub use proxy::ManagedNetworkDomainPolicy;
pub use proxy::ManagedNetworkSandboxContext;
pub use proxy::NO_PROXY_ENV_KEYS;
pub use proxy::NetworkProxy;
+69 -7
View File
@@ -338,6 +338,26 @@ struct EnvironmentProxyAddrs {
socks_addr: SocketAddr,
}
/// Domain policy snapshot supplied to an operating-system sandbox.
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManagedNetworkDomainPolicy {
pub allowed_domains: Vec<String>,
pub denied_domains: Vec<String>,
}
fn managed_network_domain_policy(
config: &config::NetworkProxyConfig,
) -> Option<ManagedNetworkDomainPolicy> {
config
.network
.allowed_domains()
.map(|allowed_domains| ManagedNetworkDomainPolicy {
allowed_domains,
denied_domains: config.network.denied_domains().unwrap_or_default(),
})
}
/// Portable managed-network facts needed by an operating-system sandbox.
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -348,6 +368,9 @@ pub struct ManagedNetworkSandboxContext {
/// Whether the command may bind local sockets and exchange loopback traffic.
#[serde(default)]
pub allow_local_binding: bool,
/// Domain policy to enforce when resolving names for the command.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub domain_policy: Option<ManagedNetworkDomainPolicy>,
}
/// Environment-specific managed-network settings prepared for one command launch.
@@ -692,6 +715,7 @@ impl NetworkProxy {
&self,
mut env: HashMap<String, String>,
addrs: EnvironmentProxyAddrs,
domain_policy: Option<ManagedNetworkDomainPolicy>,
) -> PreparedManagedNetwork {
let runtime_settings = self.runtime_settings();
// Enforce proxying for child processes. Proxy endpoint values are always rewritten;
@@ -727,6 +751,7 @@ impl NetworkProxy {
sandbox_context: ManagedNetworkSandboxContext {
loopback_ports,
allow_local_binding: runtime_settings.allow_local_binding,
domain_policy,
},
}
}
@@ -736,7 +761,7 @@ impl NetworkProxy {
env: &mut HashMap<String, String>,
addrs: EnvironmentProxyAddrs,
) {
let prepared = self.prepare_for_addrs(std::mem::take(env), addrs);
let prepared = self.prepare_for_addrs(std::mem::take(env), addrs, None);
*env = prepared.env;
}
@@ -788,7 +813,22 @@ impl NetworkProxy {
socks_addr: self.socks_addr,
},
};
Ok(self.prepare_for_addrs(env, addrs))
Ok(self.prepare_for_addrs(env, addrs, None))
}
/// Prepares a command using the latest reloaded domain policy.
///
/// The returned policy is a launch-time snapshot, like the command's other
/// operating-system sandbox inputs.
pub async fn prepare_for_optional_environment_with_current_policy(
&self,
env: HashMap<String, String>,
environment_id: Option<&str>,
) -> Result<PreparedManagedNetwork> {
let mut prepared = self.prepare_for_optional_environment(env, environment_id)?;
let config = self.state.current_cfg().await?;
prepared.sandbox_context.domain_policy = managed_network_domain_policy(&config);
Ok(prepared)
}
fn environment_proxy_addrs(&self, environment_id: &str) -> Result<EnvironmentProxyAddrs> {
@@ -1170,15 +1210,20 @@ 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 mut settings = NetworkProxySettings::default();
settings.set_allowed_domains(vec!["**.example.com".to_string()]);
settings.set_denied_domains(vec!["blocked.example.com".to_string()]);
let state = Arc::new(network_proxy_state_for_policy(settings));
let proxy = NetworkProxy::builder().state(state).build().await?;
let handle = proxy.run().await?;
let base_env = HashMap::from([("PRESERVED".to_string(), "value".to_string())]);
let local = proxy.prepare_for_optional_environment(base_env.clone(), Some("local"))?;
let remote = proxy.prepare_for_optional_environment(HashMap::new(), Some("remote"))?;
let local = proxy
.prepare_for_optional_environment_with_current_policy(base_env.clone(), Some("local"))
.await?;
let remote = proxy
.prepare_for_optional_environment_with_current_policy(HashMap::new(), Some("remote"))
.await?;
assert_eq!(
local.env.get("PRESERVED").map(String::as_str),
@@ -1216,6 +1261,10 @@ mod tests {
ManagedNetworkSandboxContext {
loopback_ports: expected_ports,
allow_local_binding: false,
domain_policy: Some(ManagedNetworkDomainPolicy {
allowed_domains: vec!["**.example.com".to_string()],
denied_domains: vec!["blocked.example.com".to_string()],
}),
}
);
}
@@ -1223,6 +1272,19 @@ mod tests {
proxy.apply_to_env_for_environment(&mut legacy_env, "local")?;
assert_eq!(legacy_env, local.env);
proxy.add_denied_domain("newly-blocked.example.com").await?;
let updated = proxy
.prepare_for_optional_environment_with_current_policy(HashMap::new(), Some("local"))
.await?;
assert!(
updated
.sandbox_context
.domain_policy
.expect("managed domain policy")
.denied_domains
.contains(&"newly-blocked.example.com".to_string())
);
handle.shutdown().await?;
Ok(())
}
@@ -269,6 +269,7 @@ fn prepared_managed_network_context_allows_only_its_proxy_ports() {
let managed_network = ManagedNetworkSandboxContext {
loopback_ports: vec![43123, 48081],
allow_local_binding: false,
domain_policy: None,
};
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: vec!["/bin/true".to_string()],