fix(exec-server): separate proxy launch inputs

Co-authored-by: Codex noreply@openai.com
This commit is contained in:
viyatb-oai
2026-07-09 10:04:13 -07:00
co-authored by Codex noreply@openai.com
parent 2af252a7b8
commit 6d48c777bc
26 changed files with 206 additions and 43 deletions
+1
View File
@@ -69,6 +69,7 @@ pub use proxy::PreparedManagedNetwork;
pub use proxy::has_proxy_url_env_vars;
pub use proxy::proxy_url_env_value;
pub use remote_config::RemoteNetworkProxyConfig;
pub use remote_config::RemoteNetworkProxyLaunchConfig;
pub use runtime::BlockedRequest;
pub use runtime::BlockedRequestArgs;
pub use runtime::BlockedRequestObserver;
+23 -5
View File
@@ -348,9 +348,6 @@ pub struct ManagedNetworkSandboxContext {
/// Whether the command may bind local sockets and exchange loopback traffic.
#[serde(default)]
pub allow_local_binding: bool,
/// Effective proxy settings for an exec-server that must start its own local listeners.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proxy_config: Option<crate::RemoteNetworkProxyConfig>,
}
/// Environment-specific managed-network settings prepared for one command launch.
@@ -660,6 +657,29 @@ impl NetworkProxy {
self.state.current_cfg().await
}
/// Captures the static inputs needed to launch a matching executor-local proxy.
pub async fn remote_launch_config(&self) -> Result<crate::RemoteNetworkProxyLaunchConfig> {
let proxy = crate::RemoteNetworkProxyConfig::from_effective_config(
&self.state.current_cfg().await?,
)?;
let (environment_id, execution_id) = self
.execution_scope
.as_ref()
.map(|scope| {
(
Some(scope.environment_id.clone()),
Some(scope.execution_id.clone()),
)
})
.unwrap_or_default();
Ok(crate::RemoteNetworkProxyLaunchConfig {
proxy,
audit_metadata: self.state.audit_metadata().clone(),
environment_id,
execution_id,
})
}
pub async fn add_allowed_domain(&self, host: &str) -> Result<()> {
self.state.add_allowed_domain(host).await
}
@@ -730,7 +750,6 @@ impl NetworkProxy {
sandbox_context: ManagedNetworkSandboxContext {
loopback_ports,
allow_local_binding: runtime_settings.allow_local_binding,
proxy_config: None,
},
}
}
@@ -1220,7 +1239,6 @@ mod tests {
ManagedNetworkSandboxContext {
loopback_ports: expected_ports,
allow_local_binding: false,
proxy_config: None,
}
);
}
@@ -2,6 +2,7 @@ use super::*;
pub(super) struct ExecutionScope {
pub(super) environment_id: String,
pub(super) execution_id: String,
pub(super) attribution_token: String,
state: Arc<NetworkProxyState>,
}
@@ -30,6 +31,7 @@ impl NetworkProxy {
let mut proxy = self.clone();
proxy.execution_scope = Some(Arc::new(ExecutionScope {
environment_id: environment_id.to_string(),
execution_id: execution_id.to_string(),
attribution_token,
state: Arc::clone(&self.state),
}));
@@ -5,9 +5,49 @@ use serde::Serialize;
use crate::NetworkDomainPermissions;
use crate::NetworkMode;
use crate::NetworkProxyAuditMetadata;
use crate::NetworkProxyConfig;
use crate::NetworkUnixSocketPermissions;
/// Executor-local proxy launch inputs transported with one process start.
///
/// Unlike [`crate::ManagedNetworkSandboxContext`], this describes how the executor should create
/// proxy listeners. The sandbox context is materialized only after those listeners are running.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct RemoteNetworkProxyLaunchConfig {
pub proxy: RemoteNetworkProxyConfig,
#[serde(default)]
pub audit_metadata: NetworkProxyAuditMetadata,
#[serde(default)]
pub environment_id: Option<String>,
#[serde(default)]
pub execution_id: Option<String>,
}
impl RemoteNetworkProxyLaunchConfig {
pub fn new(proxy: RemoteNetworkProxyConfig) -> Self {
Self {
proxy,
audit_metadata: NetworkProxyAuditMetadata::default(),
environment_id: None,
execution_id: None,
}
}
pub fn with_audit_metadata(mut self, audit_metadata: NetworkProxyAuditMetadata) -> Self {
self.audit_metadata = audit_metadata;
self
}
pub fn for_execution(mut self, environment_id: String, execution_id: String) -> Self {
self.environment_id = Some(environment_id);
self.execution_id = Some(execution_id);
self
}
}
/// Effective network proxy settings that are safe to send to a remote executor.
///
/// Listener addresses are deliberately omitted because the executor chooses its own loopback
@@ -1,8 +1,11 @@
use pretty_assertions::assert_eq;
use super::RemoteNetworkProxyConfig;
use super::RemoteNetworkProxyLaunchConfig;
use crate::NetworkMode;
use crate::NetworkProxyAuditMetadata;
use crate::NetworkProxyConfig;
use crate::NetworkProxyState;
#[test]
fn round_trip_preserves_supported_effective_settings() {
@@ -44,3 +47,27 @@ fn rejects_mitm_configuration() {
"remote exec-server network proxy does not support MITM, credential injection, or MITM hooks"
);
}
#[test]
fn launch_config_materializes_audit_and_execution_attribution() {
let proxy = RemoteNetworkProxyConfig::from_effective_config(&NetworkProxyConfig::default())
.expect("supported remote config");
let audit_metadata = NetworkProxyAuditMetadata {
conversation_id: Some("conversation-1".to_string()),
user_account_id: Some("account-1".to_string()),
originator: Some("codex_cli_rs".to_string()),
model: Some("model-1".to_string()),
..NetworkProxyAuditMetadata::default()
};
let state = NetworkProxyState::from_remote_launch_config(RemoteNetworkProxyLaunchConfig {
proxy,
audit_metadata: audit_metadata.clone(),
environment_id: Some("remote".to_string()),
execution_id: Some("execution-1".to_string()),
})
.expect("remote launch state");
assert_eq!(state.audit_metadata(), &audit_metadata);
assert_eq!(state.environment_id(), Some("remote"));
assert_eq!(state.execution_id().as_deref(), Some("execution-1"));
}
+26 -1
View File
@@ -23,6 +23,7 @@ use anyhow::Context;
use anyhow::Result;
use codex_utils_absolute_path::AbsolutePathBuf;
use globset::GlobSet;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::collections::HashSet;
@@ -47,7 +48,8 @@ const MAX_BLOCKED_EVENTS: usize = 200;
const DNS_LOOKUP_TIMEOUT: Duration = Duration::from_secs(2);
const NETWORK_POLICY_VIOLATION_PREFIX: &str = "CODEX_NETWORK_POLICY_VIOLATION";
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NetworkProxyAuditMetadata {
pub conversation_id: Option<String>,
pub app_version: Option<String>,
@@ -286,6 +288,29 @@ impl NetworkProxyState {
))
}
/// Builds immutable runtime state for one executor-local proxy launch.
pub fn from_remote_launch_config(
launch: crate::RemoteNetworkProxyLaunchConfig,
) -> Result<Self> {
let crate::RemoteNetworkProxyLaunchConfig {
proxy,
audit_metadata,
environment_id,
execution_id,
} = launch;
let config = proxy.into_network_proxy_config();
let state = build_config_state(config, NetworkProxyConstraints::default())?;
Ok(Self {
environment_id: environment_id.map(Into::into),
execution_id: execution_id.map(Into::into),
..Self::with_reloader_and_audit_metadata(
state.clone(),
Arc::new(StaticConfigReloader { state }),
audit_metadata,
)
})
}
pub fn with_reloader(state: ConfigState, reloader: Arc<dyn ConfigReloader>) -> Self {
Self::with_reloader_and_audit_metadata(
state,