Add exec-server sandbox launch intent

This commit is contained in:
starr-openai
2026-05-29 18:23:29 -07:00
parent a5a94ee5a7
commit de8a76beee
25 changed files with 843 additions and 44 deletions
@@ -41,6 +41,7 @@ use crate::unified_exec::UnifiedExecError;
use crate::unified_exec::UnifiedExecProcess;
use crate::unified_exec::UnifiedExecProcessManager;
use codex_exec_server::Environment;
use codex_exec_server::ExecServerCapability;
use codex_network_proxy::NetworkProxy;
use codex_protocol::error::CodexErr;
use codex_protocol::error::SandboxErr;
@@ -110,6 +111,24 @@ fn unified_exec_options(
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum DirectExecServerLaunch {
Materialized,
SandboxIntent,
}
fn direct_exec_server_launch(
environment_is_remote: bool,
supports_sandbox_intent: bool,
requires_core_network_realization: bool,
) -> DirectExecServerLaunch {
if environment_is_remote && supports_sandbox_intent && !requires_core_network_realization {
DirectExecServerLaunch::SandboxIntent
} else {
DirectExecServerLaunch::Materialized
}
}
impl<'a> UnifiedExecRuntime<'a> {
/// Creates a runtime bound to the shared unified-exec process manager.
pub fn new(manager: &'a UnifiedExecProcessManager, shell_mode: UnifiedExecShellMode) -> Self {
@@ -370,6 +389,41 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
let command =
build_sandbox_command(&command, &req.cwd, &env, req.additional_permissions.clone())
.map_err(|_| ToolError::Rejected("missing command line for PTY".to_string()))?;
let supports_sandbox_intent = if environment_is_remote {
req.environment
.supports_exec_server_capability(ExecServerCapability::ProcessStartSandboxIntent)
.await
.map_err(|err| ToolError::Rejected(err.to_string()))?
} else {
false
};
if direct_exec_server_launch(
environment_is_remote,
supports_sandbox_intent,
managed_network.is_some() || attempt.enforce_managed_network,
) == DirectExecServerLaunch::SandboxIntent
{
return self
.manager
.open_session_with_exec_server_sandbox_intent(
req.process_id,
command,
attempt.exec_server_sandbox_intent(),
req.exec_server_env_config.as_ref(),
req.tty,
req.environment.as_ref(),
)
.await
.map_err(|err| match err {
UnifiedExecError::SandboxDenied { output, .. } => {
ToolError::Codex(CodexErr::Sandbox(SandboxErr::Denied {
output: Box::new(output),
network_policy_decision: None,
}))
}
other => ToolError::Rejected(other.to_string()),
});
}
let options = unified_exec_options(attempt.network_denial_cancellation_token.clone());
let mut exec_env = attempt
.env_for(command, options, managed_network)
@@ -427,6 +481,38 @@ mod tests {
}
}
#[test]
fn direct_remote_exec_server_uses_sandbox_intent_only_when_network_stays_executor_local() {
assert_eq!(
direct_exec_server_launch(
/*environment_is_remote*/ true, /*supports_sandbox_intent*/ true,
/*requires_core_network_realization*/ false,
),
DirectExecServerLaunch::SandboxIntent,
);
assert_eq!(
direct_exec_server_launch(
/*environment_is_remote*/ true, /*supports_sandbox_intent*/ false,
/*requires_core_network_realization*/ false,
),
DirectExecServerLaunch::Materialized,
);
assert_eq!(
direct_exec_server_launch(
/*environment_is_remote*/ true, /*supports_sandbox_intent*/ true,
/*requires_core_network_realization*/ true,
),
DirectExecServerLaunch::Materialized,
);
assert_eq!(
direct_exec_server_launch(
/*environment_is_remote*/ false, /*supports_sandbox_intent*/ true,
/*requires_core_network_realization*/ false,
),
DirectExecServerLaunch::Materialized,
);
}
#[tokio::test]
async fn unified_exec_uses_the_trusted_sandbox_cwd() {
let cwd_dir = tempdir().expect("create process temp dir");
+16
View File
@@ -421,6 +421,22 @@ pub(crate) struct SandboxAttempt<'a> {
}
impl<'a> SandboxAttempt<'a> {
pub fn exec_server_sandbox_intent(&self) -> codex_exec_server::ExecSandboxIntent {
codex_exec_server::ExecSandboxIntent {
sandbox: if self.sandbox == SandboxType::None {
codex_exec_server::ExecSandboxMode::None
} else {
codex_exec_server::ExecSandboxMode::Platform
},
permissions: self.permissions.clone(),
sandbox_policy_cwd: self.sandbox_cwd.clone(),
use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
additional_permissions: None,
}
}
pub fn env_for(
&self,
command: SandboxCommand,
@@ -129,19 +129,20 @@ fn env_overlay_for_exec_server(
.collect()
}
fn exec_server_env_for_request(
request: &ExecRequest,
fn exec_server_env_for_command(
env: &HashMap<String, String>,
exec_server_env_config: Option<&ExecServerEnvConfig>,
) -> (
Option<codex_exec_server::ExecEnvPolicy>,
HashMap<String, String>,
) {
if let Some(exec_server_env_config) = &request.exec_server_env_config {
if let Some(exec_server_env_config) = exec_server_env_config {
(
Some(exec_server_env_config.policy.clone()),
env_overlay_for_exec_server(&request.env, &exec_server_env_config.local_policy_env),
env_overlay_for_exec_server(env, &exec_server_env_config.local_policy_env),
)
} else {
(None, request.env.clone())
(None, env.clone())
}
}
@@ -150,7 +151,8 @@ fn exec_server_params_for_request(
request: &ExecRequest,
tty: bool,
) -> codex_exec_server::ExecParams {
let (env_policy, env) = exec_server_env_for_request(request);
let (env_policy, env) =
exec_server_env_for_command(&request.env, request.exec_server_env_config.as_ref());
codex_exec_server::ExecParams {
process_id: exec_server_process_id(process_id).into(),
argv: request.command.clone(),
@@ -160,9 +162,45 @@ fn exec_server_params_for_request(
tty,
pipe_stdin: false,
arg0: request.arg0.clone(),
launch: codex_exec_server::ExecLaunch::Materialized,
}
}
fn exec_server_params_for_sandbox_intent(
process_id: i32,
command: codex_sandboxing::SandboxCommand,
mut intent: codex_exec_server::ExecSandboxIntent,
exec_server_env_config: Option<&ExecServerEnvConfig>,
tty: bool,
) -> Result<codex_exec_server::ExecParams, UnifiedExecError> {
let codex_sandboxing::SandboxCommand {
program,
args,
cwd,
env,
additional_permissions,
} = command;
let program = program.into_string().map_err(|program| {
UnifiedExecError::create_process(format!(
"exec-server sandbox intent program is not UTF-8: {}",
program.to_string_lossy()
))
})?;
let (env_policy, env) = exec_server_env_for_command(&env, exec_server_env_config);
intent.additional_permissions = additional_permissions;
Ok(codex_exec_server::ExecParams {
process_id: exec_server_process_id(process_id).into(),
argv: std::iter::once(program).chain(args).collect(),
cwd: cwd.to_path_buf(),
env_policy,
env,
tty,
pipe_stdin: false,
arg0: None,
launch: codex_exec_server::ExecLaunch::SandboxIntent { intent },
})
}
/// Borrowed process state prepared for a `write_stdin` or poll operation.
struct PreparedProcessHandles {
process: Arc<UnifiedExecProcess>,
@@ -989,6 +1027,40 @@ impl UnifiedExecProcessManager {
UnifiedExecProcess::from_spawned(spawned, request.sandbox, spawn_lifecycle).await
}
pub(crate) async fn open_session_with_exec_server_sandbox_intent(
&self,
process_id: i32,
command: codex_sandboxing::SandboxCommand,
intent: codex_exec_server::ExecSandboxIntent,
exec_server_env_config: Option<&ExecServerEnvConfig>,
tty: bool,
environment: &codex_exec_server::Environment,
) -> Result<UnifiedExecProcess, UnifiedExecError> {
if !environment.is_remote() {
return Err(UnifiedExecError::create_process(
"exec-server sandbox intent requires a remote environment".to_string(),
));
}
let started = environment
.get_exec_backend()
.start(exec_server_params_for_sandbox_intent(
process_id,
command,
intent,
exec_server_env_config,
tty,
)?)
.await
.map_err(|err| UnifiedExecError::create_process(err.to_string()))?;
let sandbox = started.sandbox.ok_or_else(|| {
UnifiedExecError::create_process(
"exec-server sandbox intent response omitted materialized sandbox".to_string(),
)
})?;
UnifiedExecProcess::from_exec_server_started(started, sandbox).await
}
pub(super) async fn open_session_with_sandbox(
&self,
request: &ExecCommandRequest,
@@ -123,6 +123,55 @@ fn exec_server_params_use_env_policy_overlay_contract() {
("CODEX_THREAD_ID".to_string(), "thread-1".to_string()),
])
);
assert_eq!(params.launch, codex_exec_server::ExecLaunch::Materialized);
}
#[test]
fn exec_server_sandbox_intent_params_keep_pre_transform_command() {
let cwd: codex_utils_absolute_path::AbsolutePathBuf = std::env::current_dir()
.expect("current dir")
.try_into()
.expect("absolute path");
let command = codex_sandboxing::SandboxCommand {
program: "bash".into(),
args: vec!["-lc".to_string(), "true".to_string()],
cwd: cwd.clone(),
env: HashMap::from([("PATH".to_string(), "/sandbox-path".to_string())]),
additional_permissions: None,
};
let intent = codex_exec_server::ExecSandboxIntent {
sandbox: codex_exec_server::ExecSandboxMode::None,
permissions: codex_protocol::models::PermissionProfile::Disabled,
sandbox_policy_cwd: cwd,
use_legacy_landlock: false,
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
additional_permissions: None,
};
let params = exec_server_params_for_sandbox_intent(
/*process_id*/ 123,
command,
intent.clone(),
/*exec_server_env_config*/ None,
/*tty*/ true,
)
.expect("sandbox intent params");
assert_eq!(
params,
codex_exec_server::ExecParams {
process_id: "123".into(),
argv: vec!["bash".to_string(), "-lc".to_string(), "true".to_string()],
cwd: std::env::current_dir().expect("current dir"),
env_policy: None,
env: HashMap::from([("PATH".to_string(), "/sandbox-path".to_string())]),
tty: true,
pipe_stdin: false,
arg0: None,
launch: codex_exec_server::ExecLaunch::SandboxIntent { intent },
}
);
}
#[test]
@@ -79,6 +79,7 @@ async fn remote_process(write_status: WriteStatus) -> UnifiedExecProcess {
read_responses: Mutex::new(VecDeque::new()),
wake_tx,
}),
sandbox: None,
};
UnifiedExecProcess::from_exec_server_started(started, SandboxType::None)
@@ -145,6 +146,7 @@ async fn remote_process_waits_for_early_exit_event() {
}])),
wake_tx: wake_tx.clone(),
}),
sandbox: None,
};
tokio::spawn(async move {