mirror of
https://github.com/openai/codex.git
synced 2026-11-03 19:53:25 +00:00
fix(exec-server): separate proxy launch inputs
Co-authored-by: Codex noreply@openai.com
This commit is contained in:
co-authored by
Codex noreply@openai.com
parent
2af252a7b8
commit
6d48c777bc
@@ -461,6 +461,7 @@ pub(crate) async fn execute_exec_request(
|
||||
exec_server_sandbox: _,
|
||||
exec_server_enforce_managed_network: _,
|
||||
exec_server_managed_network: _,
|
||||
exec_server_network_proxy: _,
|
||||
} = exec_request;
|
||||
|
||||
// TODO(anp): Keep PathUri through the local process launch boundary.
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
|
||||
use codex_file_system::FileSystemSandboxContext;
|
||||
use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
@@ -65,6 +66,7 @@ pub struct ExecRequest {
|
||||
pub(crate) exec_server_sandbox: Option<FileSystemSandboxContext>,
|
||||
pub(crate) exec_server_enforce_managed_network: bool,
|
||||
pub(crate) exec_server_managed_network: Option<ManagedNetworkSandboxContext>,
|
||||
pub(crate) exec_server_network_proxy: Option<RemoteNetworkProxyLaunchConfig>,
|
||||
}
|
||||
|
||||
impl ExecRequest {
|
||||
@@ -110,6 +112,7 @@ impl ExecRequest {
|
||||
exec_server_sandbox: None,
|
||||
exec_server_enforce_managed_network: false,
|
||||
exec_server_managed_network: None,
|
||||
exec_server_network_proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +172,7 @@ impl ExecRequest {
|
||||
exec_server_sandbox: None,
|
||||
exec_server_enforce_managed_network: false,
|
||||
exec_server_managed_network: None,
|
||||
exec_server_network_proxy: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +231,7 @@ pub(crate) async fn execute_user_shell_command(
|
||||
exec_server_sandbox: None,
|
||||
exec_server_enforce_managed_network: false,
|
||||
exec_server_managed_network: None,
|
||||
exec_server_network_proxy: None,
|
||||
};
|
||||
|
||||
let stdout_stream = Some(StdoutStream {
|
||||
|
||||
@@ -169,6 +169,7 @@ pub(super) async fn try_run_zsh_fork(
|
||||
exec_server_sandbox: _,
|
||||
exec_server_enforce_managed_network: _,
|
||||
exec_server_managed_network: _,
|
||||
exec_server_network_proxy: _,
|
||||
} = sandbox_exec_request;
|
||||
let ParsedShellCommand { script, login, .. } = extract_shell_script(&command)?;
|
||||
let effective_timeout = Duration::from_millis(
|
||||
@@ -904,6 +905,7 @@ impl CoreShellCommandExecutor {
|
||||
exec_server_sandbox: None,
|
||||
exec_server_enforce_managed_network: false,
|
||||
exec_server_managed_network: None,
|
||||
exec_server_network_proxy: None,
|
||||
},
|
||||
/*stdout_stream*/ None,
|
||||
after_spawn,
|
||||
|
||||
@@ -42,7 +42,6 @@ use crate::unified_exec::UnifiedExecProcess;
|
||||
use crate::unified_exec::UnifiedExecProcessManager;
|
||||
use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_network_proxy::RemoteNetworkProxyConfig;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::SandboxErr;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
@@ -324,26 +323,12 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
launch_sandbox_permissions,
|
||||
));
|
||||
let env = exec_env_for_sandbox_permissions(&req.env, launch_sandbox_permissions);
|
||||
let (env, managed_network_context) = match managed_network {
|
||||
let (env, managed_network_context, network_proxy_launch) = match managed_network {
|
||||
Some(network) if environment_is_remote => {
|
||||
let config = network.current_cfg().await.map_err(|err| {
|
||||
ToolError::Codex(CodexErr::Io(io::Error::other(format!(
|
||||
"failed to read effective network proxy config: {err}"
|
||||
))))
|
||||
let launch = network.remote_launch_config().await.map_err(|err| {
|
||||
ToolError::Codex(CodexErr::Io(io::Error::other(err.to_string())))
|
||||
})?;
|
||||
let proxy_config = RemoteNetworkProxyConfig::from_effective_config(&config)
|
||||
.map_err(|err| {
|
||||
ToolError::Codex(CodexErr::Io(io::Error::other(err.to_string())))
|
||||
})?;
|
||||
let allow_local_binding = proxy_config.allow_local_binding;
|
||||
(
|
||||
env,
|
||||
Some(codex_network_proxy::ManagedNetworkSandboxContext {
|
||||
loopback_ports: Vec::new(),
|
||||
allow_local_binding,
|
||||
proxy_config: Some(proxy_config),
|
||||
}),
|
||||
)
|
||||
(env, None, Some(launch))
|
||||
}
|
||||
Some(network) => {
|
||||
let prepared = network
|
||||
@@ -357,9 +342,9 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
req.turn_environment.environment_id
|
||||
))))
|
||||
})?;
|
||||
(prepared.env, Some(prepared.sandbox_context))
|
||||
(prepared.env, Some(prepared.sandbox_context), None)
|
||||
}
|
||||
None => (env, None),
|
||||
None => (env, None, None),
|
||||
};
|
||||
let explicit_env_overrides = req.explicit_env_overrides.clone();
|
||||
#[cfg(unix)]
|
||||
@@ -496,6 +481,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
options,
|
||||
attempt,
|
||||
managed_network,
|
||||
network_proxy_launch,
|
||||
/*environment_id*/ Some(&req.turn_environment.environment_id),
|
||||
req.exec_server_env_config.clone(),
|
||||
req.tty,
|
||||
|
||||
@@ -232,7 +232,6 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
|
||||
let managed_network = ManagedNetworkSandboxContext {
|
||||
loopback_ports: vec![43123],
|
||||
allow_local_binding: false,
|
||||
proxy_config: None,
|
||||
};
|
||||
let command = || SandboxCommand {
|
||||
program: "/bin/bash".into(),
|
||||
|
||||
@@ -192,6 +192,7 @@ fn exec_server_params_for_request(
|
||||
sandbox: request.exec_server_sandbox.clone(),
|
||||
enforce_managed_network: request.exec_server_enforce_managed_network,
|
||||
managed_network: request.exec_server_managed_network.clone(),
|
||||
network_proxy: request.exec_server_network_proxy.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -915,6 +916,7 @@ impl UnifiedExecProcessManager {
|
||||
options: ExecOptions,
|
||||
attempt: &SandboxAttempt<'_>,
|
||||
network: Option<&NetworkProxy>,
|
||||
network_proxy_launch: Option<codex_network_proxy::RemoteNetworkProxyLaunchConfig>,
|
||||
environment_id: Option<&str>,
|
||||
exec_server_env_config: Option<ExecServerEnvConfig>,
|
||||
tty: bool,
|
||||
@@ -927,6 +929,7 @@ impl UnifiedExecProcessManager {
|
||||
attempt.env_for(command, options, network, environment_id)
|
||||
}
|
||||
.map_err(ToolError::Codex)?;
|
||||
request.exec_server_network_proxy = network_proxy_launch;
|
||||
request.exec_server_env_config = exec_server_env_config;
|
||||
self.open_session_with_prepared_exec_env(
|
||||
process_id,
|
||||
|
||||
@@ -117,7 +117,6 @@ fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() {
|
||||
let managed_network = ManagedNetworkSandboxContext {
|
||||
loopback_ports: vec![43123],
|
||||
allow_local_binding: false,
|
||||
proxy_config: None,
|
||||
};
|
||||
let mut request = ExecRequest {
|
||||
command: vec!["bash".to_string(), "-lc".to_string(), "true".to_string()],
|
||||
@@ -175,6 +174,7 @@ fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() {
|
||||
exec_server_sandbox: None,
|
||||
exec_server_enforce_managed_network: true,
|
||||
exec_server_managed_network: Some(managed_network.clone()),
|
||||
exec_server_network_proxy: None,
|
||||
};
|
||||
|
||||
let params =
|
||||
|
||||
@@ -5,6 +5,7 @@ use codex_file_system::FileSystemSandboxContext;
|
||||
pub use codex_file_system::WalkOptions;
|
||||
pub use codex_file_system::WalkOutcome;
|
||||
use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
|
||||
use codex_protocol::config_types::ShellEnvironmentPolicyInherit;
|
||||
use codex_shell_command::shell_detect::DetectedShell;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
@@ -144,6 +145,9 @@ pub struct ExecParams {
|
||||
/// continue to fail closed. This preserves compatibility with older clients.
|
||||
#[serde(default)]
|
||||
pub managed_network: Option<ManagedNetworkSandboxContext>,
|
||||
/// Optional instructions for starting an executor-local managed-network proxy.
|
||||
#[serde(default)]
|
||||
pub network_proxy: Option<RemoteNetworkProxyLaunchConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -574,13 +578,17 @@ mod tests {
|
||||
use super::ShellInfo;
|
||||
use codex_file_system::FileSystemSandboxContext;
|
||||
use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
use codex_network_proxy::RemoteNetworkProxyConfig;
|
||||
use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn exec_params_managed_network_context_round_trips_and_defaults_for_legacy_peers() {
|
||||
fn exec_params_keeps_proxy_launch_separate_from_sandbox_facts() {
|
||||
let cwd =
|
||||
PathUri::from_host_native_path(std::env::current_dir().expect("current directory"))
|
||||
.expect("cwd URI");
|
||||
@@ -598,8 +606,18 @@ mod tests {
|
||||
managed_network: Some(ManagedNetworkSandboxContext {
|
||||
loopback_ports: vec![43123, 48081],
|
||||
allow_local_binding: false,
|
||||
proxy_config: None,
|
||||
}),
|
||||
network_proxy: Some(
|
||||
RemoteNetworkProxyLaunchConfig::new(
|
||||
RemoteNetworkProxyConfig::from_effective_config(&NetworkProxyConfig::default())
|
||||
.expect("supported remote config"),
|
||||
)
|
||||
.with_audit_metadata(NetworkProxyAuditMetadata {
|
||||
conversation_id: Some("conversation-1".to_string()),
|
||||
..NetworkProxyAuditMetadata::default()
|
||||
})
|
||||
.for_execution("remote".to_string(), "execution-1".to_string()),
|
||||
),
|
||||
};
|
||||
|
||||
let mut serialized = serde_json::to_value(¶ms).expect("serialize exec params");
|
||||
@@ -610,6 +628,10 @@ mod tests {
|
||||
"allowLocalBinding": false,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
serialized["networkProxy"]["auditMetadata"]["conversationId"],
|
||||
"conversation-1"
|
||||
);
|
||||
let round_trip: ExecParams =
|
||||
serde_json::from_value(serialized.clone()).expect("deserialize exec params");
|
||||
assert_eq!(round_trip, params);
|
||||
@@ -618,10 +640,15 @@ mod tests {
|
||||
.as_object_mut()
|
||||
.expect("exec params object")
|
||||
.remove("managedNetwork");
|
||||
serialized
|
||||
.as_object_mut()
|
||||
.expect("exec params object")
|
||||
.remove("networkProxy");
|
||||
let legacy: ExecParams =
|
||||
serde_json::from_value(serialized).expect("deserialize legacy exec params");
|
||||
assert!(legacy.enforce_managed_network);
|
||||
assert_eq!(legacy.managed_network, None);
|
||||
assert_eq!(legacy.network_proxy, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1387,6 +1387,7 @@ mod tests {
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.instrument(parent_span)
|
||||
.await
|
||||
|
||||
@@ -1176,6 +1176,7 @@ mod tests {
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await
|
||||
.expect("start process");
|
||||
@@ -1216,6 +1217,7 @@ mod tests {
|
||||
sandbox: Some(sandbox),
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await;
|
||||
let Err(err) = result else {
|
||||
|
||||
@@ -1010,6 +1010,7 @@ mod tests {
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_network_proxy::NetworkProxyHandle;
|
||||
use codex_network_proxy::NetworkProxyState;
|
||||
use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
|
||||
use codex_network_proxy::is_managed_mitm_ca_trust_bundle_path;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_sandboxing::SandboxCommand;
|
||||
@@ -38,8 +39,12 @@ pub(crate) async fn prepare_exec_request(
|
||||
env: HashMap<String, String>,
|
||||
runtime_paths: Option<&ExecServerRuntimePaths>,
|
||||
) -> Result<PreparedExecRequest, JSONRPCErrorError> {
|
||||
let (env, managed_network, network_proxy_handle) =
|
||||
prepare_managed_network(params.managed_network.as_ref(), env).await?;
|
||||
let (env, managed_network, network_proxy_handle) = prepare_managed_network(
|
||||
params.managed_network.as_ref(),
|
||||
params.network_proxy.as_ref(),
|
||||
env,
|
||||
)
|
||||
.await?;
|
||||
let Some(sandbox_context) = params.sandbox.as_ref() else {
|
||||
return Ok(PreparedExecRequest {
|
||||
command: params.argv.clone(),
|
||||
@@ -155,6 +160,7 @@ pub(crate) async fn prepare_exec_request(
|
||||
|
||||
async fn prepare_managed_network(
|
||||
managed_network: Option<&ManagedNetworkSandboxContext>,
|
||||
network_proxy: Option<&RemoteNetworkProxyLaunchConfig>,
|
||||
env: HashMap<String, String>,
|
||||
) -> Result<
|
||||
(
|
||||
@@ -164,11 +170,10 @@ async fn prepare_managed_network(
|
||||
),
|
||||
JSONRPCErrorError,
|
||||
> {
|
||||
let Some(proxy_config) = managed_network.and_then(|context| context.proxy_config.clone())
|
||||
else {
|
||||
let Some(network_proxy) = network_proxy.cloned() else {
|
||||
return Ok((env, managed_network.cloned(), None));
|
||||
};
|
||||
let state = NetworkProxyState::from_static_config(proxy_config.into_network_proxy_config())
|
||||
let state = NetworkProxyState::from_remote_launch_config(network_proxy)
|
||||
.map_err(|err| invalid_params(format!("invalid network proxy config: {err}")))?;
|
||||
let proxy = NetworkProxy::builder()
|
||||
.state(Arc::new(state))
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::time::Duration;
|
||||
use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
use codex_network_proxy::RemoteNetworkProxyConfig;
|
||||
use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
@@ -53,6 +54,7 @@ async fn sandbox_request_wraps_native_argv_on_executor() {
|
||||
sandbox: Some(sandbox),
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
};
|
||||
|
||||
let prepared = prepare_exec_request(¶ms, HashMap::new(), Some(&runtime_paths))
|
||||
@@ -117,8 +119,8 @@ async fn sandbox_request_allows_prepared_managed_proxy_port() {
|
||||
managed_network: Some(ManagedNetworkSandboxContext {
|
||||
loopback_ports: vec![43123],
|
||||
allow_local_binding: false,
|
||||
proxy_config: None,
|
||||
}),
|
||||
network_proxy: None,
|
||||
};
|
||||
|
||||
let prepared = prepare_exec_request(¶ms, HashMap::new(), Some(&runtime_paths))
|
||||
@@ -153,6 +155,7 @@ async fn native_request_preserves_native_launch_fields() {
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
};
|
||||
|
||||
let prepared = prepare_exec_request(¶ms, env.clone(), /*runtime_paths*/ None)
|
||||
@@ -189,11 +192,11 @@ async fn remote_proxy_config_starts_executor_local_proxy() {
|
||||
arg0: None,
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: Some(ManagedNetworkSandboxContext {
|
||||
loopback_ports: Vec::new(),
|
||||
allow_local_binding: false,
|
||||
proxy_config: Some(proxy_config),
|
||||
}),
|
||||
managed_network: None,
|
||||
network_proxy: Some(
|
||||
RemoteNetworkProxyLaunchConfig::new(proxy_config)
|
||||
.for_execution("remote".to_string(), "execution-1".to_string()),
|
||||
),
|
||||
};
|
||||
let stale_proxy = "http://127.0.0.1:9".to_string();
|
||||
let env = HashMap::from([("HTTP_PROXY".to_string(), stale_proxy.clone())]);
|
||||
|
||||
@@ -37,6 +37,7 @@ fn exec_params_with_argv(process_id: &str, argv: Vec<String>) -> ExecParams {
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -594,6 +594,7 @@ mod tests {
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ async fn assert_exec_process_starts_and_exits(use_remote: bool) -> Result<()> {
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), "proc-1");
|
||||
@@ -229,6 +230,7 @@ async fn assert_exec_process_streams_output(use_remote: bool) -> Result<()> {
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), process_id);
|
||||
@@ -263,6 +265,7 @@ async fn assert_exec_process_pushes_events(use_remote: bool) -> Result<()> {
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), process_id);
|
||||
@@ -313,6 +316,7 @@ async fn assert_exec_process_replays_events_after_close(use_remote: bool) -> Res
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), process_id);
|
||||
@@ -364,6 +368,7 @@ async fn assert_exec_process_retains_output_after_exit_until_streams_close(
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), process_id);
|
||||
@@ -440,6 +445,7 @@ async fn assert_exec_process_write_then_read(use_remote: bool) -> Result<()> {
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), process_id);
|
||||
@@ -480,6 +486,7 @@ async fn assert_exec_process_write_then_read_without_tty(use_remote: bool) -> Re
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), process_id);
|
||||
@@ -516,6 +523,7 @@ async fn assert_exec_process_rejects_write_without_pipe_stdin(use_remote: bool)
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), process_id);
|
||||
@@ -553,6 +561,7 @@ async fn assert_exec_process_signal_interrupts_process(use_remote: bool) -> Resu
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), process_id);
|
||||
@@ -609,6 +618,7 @@ async fn assert_exec_process_signal_reports_unsupported_on_windows(use_remote: b
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -652,6 +662,7 @@ async fn assert_exec_process_preserves_queued_events_before_subscribe(
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -713,6 +724,7 @@ async fn remote_exec_process_recovers_after_transport_disconnect() -> Result<()>
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -153,6 +153,7 @@ async fn remote_environment_routes_encrypted_exec_server_rpc() -> Result<()> {
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -510,6 +510,7 @@ impl ExecutorStdioServerLauncher {
|
||||
sandbox: None,
|
||||
enforce_managed_network: false,
|
||||
managed_network: None,
|
||||
network_proxy: None,
|
||||
})
|
||||
.await
|
||||
.map_err(io::Error::other)?;
|
||||
|
||||
@@ -269,7 +269,6 @@ fn prepared_managed_network_context_allows_only_its_proxy_ports() {
|
||||
let managed_network = ManagedNetworkSandboxContext {
|
||||
loopback_ports: vec![43123, 48081],
|
||||
allow_local_binding: false,
|
||||
proxy_config: None,
|
||||
};
|
||||
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
|
||||
command: vec!["/bin/true".to_string()],
|
||||
|
||||
Reference in New Issue
Block a user