mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Prepare managed network sandbox context (#29456)
## Why Managed network configures commands to use local HTTP and SOCKS proxies. For commands delegated to the exec server, the proxy environment and the sandbox policy were prepared separately. On macOS, that meant a command could receive `HTTPS_PROXY=http://127.0.0.1:43123` while Seatbelt still denied access to port `43123`. ## What changed `NetworkProxy` now prepares the command environment and sandbox context together from the same runtime snapshot: ```text Prepared managed network ├── command environment: HTTPS_PROXY=http://127.0.0.1:43123 └── sandbox context: allow outbound to 127.0.0.1:43123 ``` That context travels with remote exec requests. The exec server preserves the managed proxy and CA environment, and macOS Seatbelt allows only the prepared loopback proxy ports without enabling broad network access or local binding. The protocol field is optional and the existing enforcement flag remains in place, preserving compatibility with callers that do not send the new context.
This commit is contained in:
@@ -47,6 +47,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::ManagedNetworkSandboxContext;
|
||||
pub use proxy::NO_PROXY_ENV_KEYS;
|
||||
pub use proxy::NetworkProxy;
|
||||
pub use proxy::NetworkProxyBuilder;
|
||||
@@ -56,6 +57,7 @@ pub use proxy::PROXY_ENV_KEYS;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use proxy::PROXY_GIT_SSH_COMMAND_ENV_KEY;
|
||||
pub use proxy::PROXY_URL_ENV_KEYS;
|
||||
pub use proxy::PreparedManagedNetwork;
|
||||
pub use proxy::has_proxy_url_env_vars;
|
||||
pub use proxy::proxy_url_env_value;
|
||||
pub use runtime::BlockedRequest;
|
||||
|
||||
@@ -10,6 +10,8 @@ use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::TcpListener as StdTcpListener;
|
||||
@@ -328,6 +330,27 @@ struct EnvironmentProxyAddrs {
|
||||
socks_addr: SocketAddr,
|
||||
}
|
||||
|
||||
/// Portable managed-network facts needed by an operating-system sandbox.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ManagedNetworkSandboxContext {
|
||||
/// Loopback proxy ports that sandboxed commands may connect to.
|
||||
#[serde(default)]
|
||||
pub loopback_ports: Vec<u16>,
|
||||
/// Whether the command may bind local sockets and exchange loopback traffic.
|
||||
#[serde(default)]
|
||||
pub allow_local_binding: bool,
|
||||
}
|
||||
|
||||
/// Environment-specific managed-network settings prepared for one command launch.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PreparedManagedNetwork {
|
||||
/// Complete command environment with managed proxy variables applied.
|
||||
pub env: HashMap<String, String>,
|
||||
/// Matching portable sandbox inputs for the command environment.
|
||||
pub sandbox_context: ManagedNetworkSandboxContext,
|
||||
}
|
||||
|
||||
struct EnvironmentProxy {
|
||||
addrs: EnvironmentProxyAddrs,
|
||||
http_task: JoinHandle<Result<()>>,
|
||||
@@ -653,22 +676,49 @@ impl NetworkProxy {
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_to_env_for_addrs(
|
||||
fn prepare_for_addrs(
|
||||
&self,
|
||||
env: &mut HashMap<String, String>,
|
||||
mut env: HashMap<String, String>,
|
||||
addrs: EnvironmentProxyAddrs,
|
||||
) {
|
||||
) -> PreparedManagedNetwork {
|
||||
let runtime_settings = self.runtime_settings();
|
||||
// Enforce proxying for child processes. Proxy endpoint values are always rewritten;
|
||||
// managed MITM CA vars preserve child-scoped overrides after proxy startup.
|
||||
apply_proxy_env_overrides(
|
||||
env,
|
||||
&mut env,
|
||||
addrs.http_addr,
|
||||
addrs.socks_addr,
|
||||
self.socks_enabled,
|
||||
runtime_settings.allow_local_binding,
|
||||
runtime_settings.mitm_ca_trust_bundle.as_ref(),
|
||||
);
|
||||
let mut loopback_ports = [
|
||||
Some(addrs.http_addr),
|
||||
self.socks_enabled.then_some(addrs.socks_addr),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|addr| addr.ip().is_loopback())
|
||||
.map(|addr| addr.port())
|
||||
.collect::<Vec<_>>();
|
||||
loopback_ports.sort_unstable();
|
||||
loopback_ports.dedup();
|
||||
PreparedManagedNetwork {
|
||||
env,
|
||||
sandbox_context: ManagedNetworkSandboxContext {
|
||||
loopback_ports,
|
||||
allow_local_binding: runtime_settings.allow_local_binding,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_to_env_for_addrs(
|
||||
&self,
|
||||
env: &mut HashMap<String, String>,
|
||||
addrs: EnvironmentProxyAddrs,
|
||||
) {
|
||||
let prepared = self.prepare_for_addrs(std::mem::take(env), addrs);
|
||||
*env = prepared.env;
|
||||
}
|
||||
|
||||
pub fn apply_to_env(&self, env: &mut HashMap<String, String>) {
|
||||
@@ -705,6 +755,23 @@ impl NetworkProxy {
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies the environment-specific proxy settings and returns the matching portable sandbox
|
||||
/// projection from the same runtime configuration snapshot.
|
||||
pub fn prepare_for_optional_environment(
|
||||
&self,
|
||||
env: HashMap<String, String>,
|
||||
environment_id: Option<&str>,
|
||||
) -> Result<PreparedManagedNetwork> {
|
||||
let addrs = match environment_id {
|
||||
Some(environment_id) => self.environment_proxy_addrs(environment_id)?,
|
||||
None => EnvironmentProxyAddrs {
|
||||
http_addr: self.http_addr,
|
||||
socks_addr: self.socks_addr,
|
||||
},
|
||||
};
|
||||
Ok(self.prepare_for_addrs(env, addrs))
|
||||
}
|
||||
|
||||
fn environment_proxy_addrs(&self, environment_id: &str) -> Result<EnvironmentProxyAddrs> {
|
||||
let mut proxies = self
|
||||
.environment_proxies
|
||||
@@ -1071,27 +1138,59 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn apply_to_env_for_environment_uses_distinct_proxy_ports() -> Result<()> {
|
||||
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 proxy = NetworkProxy::builder().state(state).build().await?;
|
||||
let handle = proxy.run().await?;
|
||||
|
||||
let mut local_env = HashMap::new();
|
||||
proxy.apply_to_env_for_environment(&mut local_env, "local")?;
|
||||
let mut remote_env = HashMap::new();
|
||||
proxy.apply_to_env_for_environment(&mut remote_env, "remote")?;
|
||||
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"))?;
|
||||
|
||||
assert_ne!(local_env.get("HTTP_PROXY"), remote_env.get("HTTP_PROXY"));
|
||||
assert_eq!(
|
||||
local.env.get("PRESERVED").map(String::as_str),
|
||||
Some("value")
|
||||
);
|
||||
assert_ne!(local.env.get("HTTP_PROXY"), remote.env.get("HTTP_PROXY"));
|
||||
assert_ne!(
|
||||
local_env.get("HTTP_PROXY"),
|
||||
local.env.get("HTTP_PROXY"),
|
||||
Some(&format!("http://{}", proxy.http_addr()))
|
||||
);
|
||||
assert_ne!(
|
||||
remote_env.get("HTTP_PROXY"),
|
||||
remote.env.get("HTTP_PROXY"),
|
||||
Some(&format!("http://{}", proxy.http_addr()))
|
||||
);
|
||||
for prepared in [&local, &remote] {
|
||||
let http_port = prepared
|
||||
.env
|
||||
.get("HTTP_PROXY")
|
||||
.and_then(|value| value.strip_prefix("http://"))
|
||||
.and_then(|value| value.parse::<SocketAddr>().ok())
|
||||
.map(|addr| addr.port())
|
||||
.expect("managed HTTP proxy address");
|
||||
let socks_port = prepared
|
||||
.env
|
||||
.get("ALL_PROXY")
|
||||
.and_then(|value| value.strip_prefix("socks5h://"))
|
||||
.and_then(|value| value.parse::<SocketAddr>().ok())
|
||||
.map(|addr| addr.port())
|
||||
.expect("managed SOCKS proxy address");
|
||||
let mut expected_ports = vec![http_port, socks_port];
|
||||
expected_ports.sort_unstable();
|
||||
expected_ports.dedup();
|
||||
assert_eq!(
|
||||
prepared.sandbox_context,
|
||||
ManagedNetworkSandboxContext {
|
||||
loopback_ports: expected_ports,
|
||||
allow_local_binding: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
let mut legacy_env = base_env;
|
||||
proxy.apply_to_env_for_environment(&mut legacy_env, "local")?;
|
||||
assert_eq!(legacy_env, local.env);
|
||||
|
||||
handle.shutdown().await?;
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user