mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
Add exec-server sandbox launch intent
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user