/*Runtime: shellExecutes shell requests under the orchestrator: asks for approval when needed,builds sandbox transform inputs, and runs them under the current SandboxAttempt.*/#[cfg(unix)]pub(crate) mod unix_escalation;pub(crate) mod zsh_fork_backend;use crate::command_canonicalization::canonicalize_command_for_approval;use crate::exec::ExecCapturePolicy;use crate::guardian::GuardianApprovalRequest;use crate::guardian::GuardianNetworkAccessTrigger;use crate::guardian::review_approval_request;use crate::sandboxing::ExecOptions;use crate::sandboxing::SandboxPermissions;use crate::sandboxing::execute_env;use crate::session::turn_context::TurnEnvironment;use crate::shell::ShellType;use crate::tools::flat_tool_name;use crate::tools::network_approval::NetworkApprovalMode;use crate::tools::network_approval::NetworkApprovalSpec;use crate::tools::runtimes::RuntimePathPrepends;#[cfg(unix)]use crate::tools::runtimes::apply_zsh_fork_path_prepend;use crate::tools::runtimes::build_sandbox_command;use crate::tools::runtimes::disable_powershell_profile_for_elevated_windows_sandbox;use crate::tools::runtimes::exec_env_for_sandbox_permissions;use crate::tools::runtimes::maybe_wrap_shell_lc_with_snapshot;use crate::tools::sandboxing::Approvable;use crate::tools::sandboxing::ApprovalCtx;use crate::tools::sandboxing::ExecApprovalRequirement;use crate::tools::sandboxing::PermissionRequestPayload;use crate::tools::sandboxing::SandboxAttempt;use crate::tools::sandboxing::Sandboxable;use crate::tools::sandboxing::ToolCtx;use crate::tools::sandboxing::ToolError;use crate::tools::sandboxing::ToolRuntime;use crate::tools::sandboxing::managed_network_for_sandbox_permissions;use crate::tools::sandboxing::sandbox_permissions_preserving_denied_reads;use crate::tools::sandboxing::with_cached_approval;use codex_network_proxy::NetworkProxy;use codex_protocol::exec_output::ExecToolCallOutput;use codex_protocol::models::AdditionalPermissionProfile;use codex_protocol::protocol::ReviewDecision;use codex_sandboxing::SandboxablePreference;use codex_shell_command::powershell::prefix_powershell_script_with_utf8;use codex_utils_absolute_path::AbsolutePathBuf;use futures::future::BoxFuture;use std::collections::HashMap;use tokio_util::sync::CancellationToken;#[derive(Clone, Debug)]pub struct ShellRequest { pub command: Vec<String>, pub turn_environment: TurnEnvironment, pub shell_type: Option<ShellType>, pub hook_command: String, pub cwd: AbsolutePathBuf, pub timeout_ms: Option<u64>, pub cancellation_token: CancellationToken, pub env: HashMap<String, String>, pub explicit_env_overrides: HashMap<String, String>, pub network: Option<NetworkProxy>, pub sandbox_permissions: SandboxPermissions, pub additional_permissions: Option<AdditionalPermissionProfile>, #[cfg(unix)] pub additional_permissions_preapproved: bool, pub justification: Option<String>, pub exec_approval_requirement: ExecApprovalRequirement,}/// Selects `ShellRuntime` behavior for different callers.#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub(crate) enum ShellRuntimeBackend { /// Legacy backend for the `shell_command` tool. /// /// Keeps `shell_command` on the standard shell runtime flow without the /// zsh-fork shell-escalation adapter. ShellCommandClassic, /// zsh-fork backend for the `shell_command` tool. /// /// On Unix, attempts to run via the zsh-fork + `codex-shell-escalation` /// adapter, with fallback to the standard shell runtime flow if /// prerequisites are not met. ShellCommandZshFork,}pub struct ShellRuntime { backend: ShellRuntimeBackend,}#[derive(serde::Serialize, Clone, Debug, Eq, PartialEq, Hash)]pub(crate) struct ApprovalKey { command: Vec<String>, cwd: AbsolutePathBuf, sandbox_permissions: SandboxPermissions, additional_permissions: Option<AdditionalPermissionProfile>,}impl ShellRuntime { pub(crate) fn for_shell_command(backend: ShellRuntimeBackend) -> Self { Self { backend } } fn stdout_stream(ctx: &ToolCtx) -> Option<crate::exec::StdoutStream> { Some(crate::exec::StdoutStream { sub_id: ctx.turn.sub_id.clone(), call_id: ctx.call_id.clone(), tx_event: ctx.session.get_tx_event(), }) }}impl Sandboxable for ShellRuntime { fn sandbox_preference(&self) -> SandboxablePreference { SandboxablePreference::Auto } fn escalate_on_failure(&self) -> bool { true }}impl Approvable<ShellRequest> for ShellRuntime { type ApprovalKey = ApprovalKey; fn approval_keys(&self, req: &ShellRequest) -> Vec<Self::ApprovalKey> { vec![ApprovalKey { command: canonicalize_command_for_approval(&req.command), cwd: req.cwd.clone(), sandbox_permissions: req.sandbox_permissions, additional_permissions: req.additional_permissions.clone(), }] } fn start_approval_async<'a>( &'a mut self, req: &'a ShellRequest, ctx: ApprovalCtx<'a>, ) -> BoxFuture<'a, ReviewDecision> { let keys = self.approval_keys(req); let command = req.command.clone(); let cwd = req.cwd.clone(); let retry_reason = ctx.retry_reason.clone(); let reason = retry_reason.clone().or_else(|| req.justification.clone()); let session = ctx.session; let turn = ctx.turn; let call_id = ctx.call_id.to_string(); let guardian_review_id = ctx.guardian_review_id.clone(); Box::pin(async move { if let Some(review_id) = guardian_review_id { return review_approval_request( session, turn, review_id, GuardianApprovalRequest::Shell { id: call_id, command, cwd: cwd.clone(), sandbox_permissions: req.sandbox_permissions, additional_permissions: req.additional_permissions.clone(), justification: req.justification.clone(), }, retry_reason, ) .await; } with_cached_approval(&session.services, "shell", keys, move || async move { let available_decisions = None; session .request_command_approval( turn, call_id, /*approval_id*/ None, command, cwd, reason, ctx.network_approval_context.clone(), req.exec_approval_requirement .proposed_execpolicy_amendment() .cloned(), req.additional_permissions.clone(), available_decisions, ) .await }) .await }) } fn exec_approval_requirement(&self, req: &ShellRequest) -> Option<ExecApprovalRequirement> { Some(req.exec_approval_requirement.clone()) } fn permission_request_payload(&self, req: &ShellRequest) -> Option<PermissionRequestPayload> { Some(PermissionRequestPayload::bash( req.hook_command.clone(), req.justification.clone(), )) } fn sandbox_permissions(&self, req: &ShellRequest) -> SandboxPermissions { req.sandbox_permissions }}impl ToolRuntime<ShellRequest, ExecToolCallOutput> for ShellRuntime { fn network_approval_spec( &self, req: &ShellRequest, ctx: &ToolCtx, ) -> Option<NetworkApprovalSpec> { let file_system_sandbox_policy = ctx.turn.file_system_sandbox_policy(); let sandbox_permissions = sandbox_permissions_preserving_denied_reads( req.sandbox_permissions, &file_system_sandbox_policy, ); let network = managed_network_for_sandbox_permissions(req.network.as_ref(), sandbox_permissions)?; Some(NetworkApprovalSpec { network: Some(network.clone()), mode: NetworkApprovalMode::Immediate, trigger: GuardianNetworkAccessTrigger { call_id: ctx.call_id.clone(), tool_name: flat_tool_name(&ctx.tool_name).into_owned(), command: req.command.clone(), cwd: req.cwd.clone(), sandbox_permissions: req.sandbox_permissions, additional_permissions: req.additional_permissions.clone(), justification: req.justification.clone(), tty: None, }, command: req.hook_command.clone(), }) } async fn run( &mut self, req: &ShellRequest, attempt: &SandboxAttempt<'_>, ctx: &ToolCtx, ) -> Result<ExecToolCallOutput, ToolError> { let session_shell = ctx.session.user_shell(); let shell = req .turn_environment .shell .as_ref() .unwrap_or(session_shell.as_ref()); let shell_snapshot_location = req.turn_environment.shell_snapshot(&req.cwd); let (file_system_sandbox_policy, _) = attempt.permissions.to_runtime_permissions(); let sandbox_permissions = sandbox_permissions_preserving_denied_reads( req.sandbox_permissions, &file_system_sandbox_policy, ); let managed_network = managed_network_for_sandbox_permissions(req.network.as_ref(), sandbox_permissions); let env = exec_env_for_sandbox_permissions(&req.env, sandbox_permissions); let explicit_env_overrides = req.explicit_env_overrides.clone(); #[cfg(unix)] let (env, runtime_path_prepends) = { let mut env = env; let mut runtime_path_prepends = RuntimePathPrepends::default(); crate::tools::runtimes::apply_package_path_prepend( &mut env, &mut runtime_path_prepends, ); if self.backend == ShellRuntimeBackend::ShellCommandZshFork && let Some(shell_zsh_path) = ctx.session.services.shell_zsh_path.as_deref() { apply_zsh_fork_path_prepend(&mut env, &mut runtime_path_prepends, shell_zsh_path); } (env, runtime_path_prepends) }; #[cfg(not(unix))] let runtime_path_prepends = RuntimePathPrepends::default(); let command = maybe_wrap_shell_lc_with_snapshot( &req.command, shell, shell_snapshot_location.as_ref(), &explicit_env_overrides, &env, &runtime_path_prepends, ); let command = disable_powershell_profile_for_elevated_windows_sandbox( &command, req.shell_type.as_ref(), attempt.sandbox, attempt.windows_sandbox_level, ); let command = if matches!(shell.shell_type, ShellType::PowerShell) { prefix_powershell_script_with_utf8(&command) } else { command }; if self.backend == ShellRuntimeBackend::ShellCommandZshFork { match zsh_fork_backend::maybe_run_shell_command(req, attempt, ctx, &command).await? { Some(out) => return Ok(out), None => { tracing::warn!( "ZshFork backend specified, but conditions for using it were not met, falling back to normal execution", ); } } } let command = build_sandbox_command(&command, &req.cwd, &env, req.additional_permissions.clone())?; let mut expiration: crate::exec::ExecExpiration = req.timeout_ms.into(); expiration = expiration.with_cancellation(req.cancellation_token.clone()); if let Some(cancellation) = attempt.network_denial_cancellation_token.clone() { expiration = expiration.with_cancellation(cancellation); } let options = ExecOptions { expiration, capture_policy: ExecCapturePolicy::ShellTool, }; let env = attempt .env_for(command, options, managed_network) .map_err(ToolError::Codex)?; let out = execute_env(env, Self::stdout_stream(ctx)) .await .map_err(ToolError::Codex)?; Ok(out) }}