mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
feat(network-proxy): expose managed domain policy
Co-authored-by: Codex noreply@openai.com
This commit is contained in:
co-authored by
Codex noreply@openai.com
parent
ba5dd1fd3a
commit
048683163d
@@ -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,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()],
|
||||
|
||||
Reference in New Issue
Block a user