Skip to content
Merged
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
27 changes: 7 additions & 20 deletions codex-rs/core/src/tools/runtimes/unified_exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
}
return self
.manager
.open_session_with_exec_env(
.open_session_with_prepared_exec_env(
req.process_id,
&prepared.exec_request,
req.tty,
Expand Down Expand Up @@ -459,33 +459,20 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
error @ ToolError::Codex(_) => error,
})?;
let options = unified_exec_options(attempt.network_denial_cancellation_token.clone());
let mut exec_env = attempt
.env_for(
command,
options,
managed_network,
Some(&req.turn_environment.environment_id),
)
.map_err(ToolError::Codex)?;
exec_env.exec_server_env_config = req.exec_server_env_config.clone();
self.manager
.open_session_with_exec_env(
req.process_id,
&exec_env,
command,
options,
attempt,
managed_network,
/*environment_id*/ Some(&req.turn_environment.environment_id),
req.exec_server_env_config.clone(),
req.tty,
Box::new(NoopSpawnLifecycle),
req.turn_environment.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()),
})
}
}

Expand Down
31 changes: 31 additions & 0 deletions codex-rs/core/src/tools/sandboxing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,37 @@ impl<'a> SandboxAttempt<'a> {
self.workspace_roots.to_vec(),
))
}

pub fn env_for_exec_server(
&self,
command: SandboxCommand,
options: ExecOptions,
network: Option<&NetworkProxy>,
environment_id: Option<&str>,
) -> Result<crate::sandboxing::ExecRequest, CodexErr> {
let request = self
.manager
.transform(SandboxTransformRequest {
command,
permissions: self.permissions,
// 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,
sandbox_policy_cwd: self.sandbox_cwd,
codex_linux_sandbox_exe: None,
use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
})
.map_err(CodexErr::from)?;
Ok(crate::sandboxing::ExecRequest::from_sandbox_exec_request(
request,
options,
self.workspace_roots.to_vec(),
))
}
}

#[cfg(test)]
Expand Down
60 changes: 60 additions & 0 deletions codex-rs/core/src/tools/sandboxing_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ use crate::tools::hook_names::HookToolName;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::GranularApprovalConfig;
use codex_sandboxing::SandboxCommand;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxType;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::collections::HashMap;

#[test]
fn bash_permission_request_payload_omits_missing_description() {
Expand Down Expand Up @@ -193,3 +200,56 @@ fn deny_read_blocks_explicit_escalation_and_policy_bypass() {
"exec-policy allow rules would drop deny-read filesystem policy, so keep the first attempt sandboxed",
);
}

#[test]
fn exec_server_env_keeps_command_native() {
Comment thread
jif-oai marked this conversation as resolved.
let cwd: AbsolutePathBuf = std::env::current_dir()
.expect("current dir")
.try_into()
.expect("absolute cwd");
let cwd_uri = PathUri::from_abs_path(&cwd);
let permissions = codex_protocol::models::PermissionProfile::from_runtime_permissions(
&FileSystemSandboxPolicy::default(),
NetworkSandboxPolicy::Restricted,
);
let manager = SandboxManager::new();
let attempt = SandboxAttempt {
sandbox: SandboxType::MacosSeatbelt,
permissions: &permissions,
enforce_managed_network: false,
manager: &manager,
sandbox_cwd: &cwd_uri,
workspace_roots: std::slice::from_ref(&cwd),
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
network_denial_cancellation_token: None,
};
let command = SandboxCommand {
program: "/bin/bash".into(),
args: vec!["-lc".to_string(), "pwd".to_string()],
cwd: cwd_uri.clone(),
env: HashMap::new(),
additional_permissions: None,
};
let options = crate::sandboxing::ExecOptions {
expiration: crate::exec::ExecExpiration::DefaultTimeout,
capture_policy: crate::exec::ExecCapturePolicy::ShellTool,
};

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

assert_eq!(
request.command,
vec![
"/bin/bash".to_string(),
"-lc".to_string(),
"pwd".to_string()
]
);
assert_eq!(request.arg0, None);
assert_eq!(request.sandbox, SandboxType::None);
}
8 changes: 4 additions & 4 deletions codex-rs/core/src/unified_exec/mod_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ async fn exec_command_with_tty(

let process = Arc::new(
manager
.open_session_with_exec_env(
.open_session_with_prepared_exec_env(
process_id,
&request,
tty,
Expand Down Expand Up @@ -811,7 +811,7 @@ async fn completed_pipe_commands_preserve_exit_code() -> anyhow::Result<()> {

let environment = codex_exec_server::Environment::default_for_tests();
let process = UnifiedExecProcessManager::default()
.open_session_with_exec_env(
.open_session_with_prepared_exec_env(
/*process_id*/ 1234,
&request,
/*tty*/ false,
Expand Down Expand Up @@ -853,7 +853,7 @@ async fn unified_exec_uses_remote_exec_server_when_configured() -> anyhow::Resul

let manager = UnifiedExecProcessManager::default();
let process = manager
.open_session_with_exec_env(
.open_session_with_prepared_exec_env(
/*process_id*/ 1234,
&request,
/*tty*/ true,
Expand Down Expand Up @@ -910,7 +910,7 @@ async fn remote_exec_server_rejects_inherited_fd_launches() -> anyhow::Result<()

let manager = UnifiedExecProcessManager::default();
let err = manager
.open_session_with_exec_env(
.open_session_with_prepared_exec_env(
/*process_id*/ 1234,
&request,
/*tty*/ true,
Expand Down
44 changes: 44 additions & 0 deletions codex-rs/core/src/unified_exec/process_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::codex_thread::BackgroundTerminalInfo;
use crate::exec_env::CODEX_THREAD_ID_ENV_VAR;
use crate::exec_env::create_env;
use crate::exec_policy::ExecApprovalRequest;
use crate::sandboxing::ExecOptions;
use crate::sandboxing::ExecRequest;
use crate::sandboxing::ExecServerEnvConfig;
use crate::tools::context::ExecCommandToolOutput;
Expand All @@ -26,6 +27,7 @@ use crate::tools::network_approval::finish_deferred_network_approval;
use crate::tools::orchestrator::ToolOrchestrator;
use crate::tools::runtimes::unified_exec::UnifiedExecRequest as UnifiedExecToolRequest;
use crate::tools::runtimes::unified_exec::UnifiedExecRuntime;
use crate::tools::sandboxing::SandboxAttempt;
use crate::tools::sandboxing::ToolCtx;
use crate::tools::sandboxing::ToolError;
use crate::unified_exec::ExecCommandRequest;
Expand All @@ -50,10 +52,12 @@ use crate::unified_exec::process::OutputBuffer;
use crate::unified_exec::process::OutputHandles;
use crate::unified_exec::process::SpawnLifecycleHandle;
use crate::unified_exec::process::UnifiedExecProcess;
use codex_network_proxy::NetworkProxy;
use codex_protocol::config_types::ShellEnvironmentPolicy;
use codex_protocol::error::CodexErr;
use codex_protocol::error::SandboxErr;
use codex_protocol::protocol::ExecCommandSource;
use codex_sandboxing::SandboxCommand;
use codex_tools::ToolName;
use codex_utils_output_truncation::approx_token_count;
use codex_utils_path_uri::PathUri;
Expand Down Expand Up @@ -886,7 +890,47 @@ impl UnifiedExecProcessManager {
);
}

#[allow(clippy::too_many_arguments)]
pub(crate) async fn open_session_with_exec_env(
&self,
process_id: i32,
command: SandboxCommand,
options: ExecOptions,
attempt: &SandboxAttempt<'_>,
network: Option<&NetworkProxy>,
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)
} else {
attempt.env_for(command, options, network, environment_id)
}
.map_err(ToolError::Codex)?;
request.exec_server_env_config = exec_server_env_config;
self.open_session_with_prepared_exec_env(
process_id,
&request,
tty,
spawn_lifecycle,
environment,
)
.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()),
})
}

pub(crate) async fn open_session_with_prepared_exec_env(
&self,
process_id: i32,
request: &ExecRequest,
Expand Down
Loading