Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions codex-rs/core/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions codex-rs/core/src/sandboxing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/src/tasks/user_shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 10 additions & 3 deletions codex-rs/core/src/tools/runtimes/unified_exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,13 @@ 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 launch = network.remote_launch_config().await.map_err(|err| {
ToolError::Codex(CodexErr::Io(io::Error::other(err.to_string())))
})?;
(env, None, Some(launch))
}
Some(network) => {
let prepared = network
.prepare_for_optional_environment(
Expand All @@ -336,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)]
Expand Down Expand Up @@ -475,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,
Expand Down
7 changes: 2 additions & 5 deletions codex-rs/core/src/tools/sandboxing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,10 +475,7 @@ impl<'a> SandboxAttempt<'a> {
&self,
command: SandboxCommand,
options: ExecOptions,
network: Option<&NetworkProxy>,
environment_id: Option<&str>,
) -> Result<crate::sandboxing::ExecRequest, CodexErr> {
let network = self.network_proxy(network);
let managed_network = command.managed_network.clone();
let exec_server_permissions = effective_permission_profile(
self.exec_server_permissions,
Expand All @@ -492,8 +489,8 @@ impl<'a> SandboxAttempt<'a> {
// The exec-server must receive the native command, not this host's wrapper.
sandbox: SandboxType::None,
enforce_managed_network: self.enforce_managed_network,
environment_id,
network,
environment_id: None,
network: None,
sandbox_policy_cwd: self.sandbox_cwd,
codex_linux_sandbox_exe: None,
use_legacy_landlock: self.use_legacy_landlock,
Expand Down
4 changes: 2 additions & 2 deletions codex-rs/core/src/tools/sandboxing_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
capture_policy: crate::exec::ExecCapturePolicy::ShellTool,
};
let request = attempt
.env_for_exec_server(command(), options(), /*network*/ None, Some("remote"))
.env_for_exec_server(command(), options())
.expect("prepare remote exec request");

assert_eq!(
Expand Down Expand Up @@ -278,7 +278,7 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {

attempt.sandbox_requested = false;
let request = attempt
.env_for_exec_server(command(), options(), /*network*/ None, Some("remote"))
.env_for_exec_server(command(), options())
.expect("prepare unsandboxed remote exec request");

assert_eq!(request.exec_server_sandbox, None);
Expand Down
5 changes: 4 additions & 1 deletion codex-rs/core/src/unified_exec/process_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}

Expand Down Expand Up @@ -915,18 +916,20 @@ 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,
spawn_lifecycle: SpawnLifecycleHandle,
environment: &codex_exec_server::Environment,
) -> Result<UnifiedExecProcess, ToolError> {
let mut request = if environment.is_remote() {
attempt.env_for_exec_server(command, options, network, environment_id)
attempt.env_for_exec_server(command, options)
} else {
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,
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/src/unified_exec/process_manager_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,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 =
Expand Down
30 changes: 29 additions & 1 deletion codex-rs/exec-server-protocol/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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");
Expand All @@ -599,6 +607,17 @@ mod tests {
loopback_ports: vec![43123, 48081],
allow_local_binding: false,
}),
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(&params).expect("serialize exec params");
Expand All @@ -609,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);
Expand All @@ -617,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]
Expand Down
1 change: 1 addition & 0 deletions codex-rs/exec-server/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1387,6 +1387,7 @@ mod tests {
sandbox: None,
enforce_managed_network: false,
managed_network: None,
network_proxy: None,
})
.instrument(parent_span)
.await
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/exec-server/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,7 @@ mod tests {
sandbox: None,
enforce_managed_network: false,
managed_network: None,
network_proxy: None,
})
.await
.expect("start process");
Expand Down Expand Up @@ -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 {
Expand Down
18 changes: 14 additions & 4 deletions codex-rs/exec-server/src/local_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::sync::atomic::Ordering;
use std::time::Duration;

use codex_exec_server_protocol::JSONRPCErrorError;
use codex_network_proxy::NetworkProxyHandle;
use codex_protocol::config_types::EnvironmentVariablePattern;
use codex_protocol::config_types::ShellEnvironmentPolicy;
use codex_protocol::exec_output::ExecToolCallOutput;
Expand Down Expand Up @@ -97,6 +98,7 @@ struct RunningProcess {
termination_requested: bool,
sandbox: SandboxType,
sandbox_denied: bool,
network_proxy_handle: Option<NetworkProxyHandle>,
}

/// Bounded cache of stdin write ids that have already been accepted for one process.
Expand Down Expand Up @@ -235,7 +237,7 @@ impl LocalProcess {
) -> Result<(ExecResponse, watch::Sender<u64>, ExecProcessEventLog), JSONRPCErrorError> {
let process_id = params.process_id.clone();
let prepared =
prepare_exec_request(&params, child_env(&params), self.runtime_paths.as_ref())?;
prepare_exec_request(&params, child_env(&params), self.runtime_paths.as_ref()).await?;
let (program, args) = prepared
.command
.split_first()
Expand Down Expand Up @@ -338,6 +340,7 @@ impl LocalProcess {
termination_requested: false,
sandbox: prepared.sandbox,
sandbox_denied: false,
network_proxy_handle: prepared.network_proxy_handle,
})),
);
}
Expand Down Expand Up @@ -823,7 +826,7 @@ async fn watch_exit(
output_notify: Arc<Notify>,
) {
let exit_code = exit_rx.await.unwrap_or(-1);
let sandboxed = {
let (sandboxed, network_proxy_handle) = {
let mut processes = inner.processes.lock().await;
match processes.get_mut(&process_id) {
Some(ProcessEntry::Running(process)) => {
Expand All @@ -837,11 +840,16 @@ async fn watch_exit(
"error"
});
}
sandboxed
(sandboxed, process.network_proxy_handle.take())
}
Some(ProcessEntry::Starting(_)) | None => false,
Some(ProcessEntry::Starting(_)) | None => (false, None),
}
};
if let Some(network_proxy_handle) = network_proxy_handle

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ties the proxy lifetime to the direct child, but exec-server explicitly allows descendants to outlive it
Background commands inherit these proxy URLs, so leader exit removes their only sandbox-permitted egress while they’re still running. Is this expected?

&& let Err(err) = network_proxy_handle.shutdown().await
{
tracing::warn!("failed to shut down executor network proxy: {err}");
}
if sandboxed {
let _ = tokio::time::timeout(Duration::from_millis(20), output_notify.notified()).await;
}
Expand Down Expand Up @@ -1002,6 +1010,7 @@ mod tests {
sandbox: None,
enforce_managed_network: false,
managed_network: None,
network_proxy: None,
}
}

Expand Down Expand Up @@ -1310,6 +1319,7 @@ mod tests {
termination_requested: false,
sandbox: SandboxType::None,
sandbox_denied: false,
network_proxy_handle: None,
})),
);
assert!(previous.is_none());
Expand Down
Loading
Loading