Codex Handbook
core/src/tools/runtimes/shell/unix_escalation.rs 1090 lines
use super::ShellRequest;use crate::exec::ExecCapturePolicy;use crate::exec::ExecExpiration;use crate::exec::cancel_when_either;use crate::exec::is_likely_sandbox_denied;use crate::guardian::GuardianApprovalRequest;use crate::guardian::guardian_rejection_message;use crate::guardian::guardian_timeout_message;use crate::guardian::new_guardian_review_id;use crate::guardian::review_approval_request;use crate::guardian::routes_approval_to_guardian;use crate::hook_runtime::run_permission_request_hooks;use crate::sandboxing::ExecOptions;use crate::sandboxing::ExecRequest;use crate::sandboxing::SandboxPermissions;use crate::shell::ShellType;use crate::tools::runtimes::build_sandbox_command;use crate::tools::runtimes::exec_env_for_sandbox_permissions;use crate::tools::runtimes::prepend_zsh_fork_bin_to_path;use crate::tools::sandboxing::PermissionRequestPayload;use crate::tools::sandboxing::SandboxAttempt;use crate::tools::sandboxing::ToolCtx;use crate::tools::sandboxing::ToolError;use crate::tools::sandboxing::managed_network_for_sandbox_permissions;use crate::tools::sandboxing::sandbox_permissions_preserving_denied_reads;use crate::tools::sandboxing::unsandboxed_execution_allowed;use codex_execpolicy::Decision;use codex_execpolicy::Evaluation;use codex_execpolicy::MatchOptions;use codex_execpolicy::Policy;use codex_execpolicy::RuleMatch;use codex_features::Feature;use codex_hooks::PermissionRequestDecision;use codex_protocol::config_types::WindowsSandboxLevel;use codex_protocol::error::CodexErr;use codex_protocol::error::SandboxErr;use codex_protocol::exec_output::ExecToolCallOutput;use codex_protocol::exec_output::StreamOutput;use codex_protocol::models::AdditionalPermissionProfile;use codex_protocol::models::PermissionProfile;use codex_protocol::permissions::FileSystemSandboxPolicy;use codex_protocol::permissions::NetworkSandboxPolicy;use codex_protocol::protocol::AskForApproval;use codex_protocol::protocol::GuardianCommandSource;use codex_protocol::protocol::NetworkPolicyRuleAction;use codex_protocol::protocol::ReviewDecision;use codex_sandboxing::SandboxCommand;use codex_sandboxing::SandboxManager;use codex_sandboxing::SandboxTransformRequest;use codex_sandboxing::SandboxType;use codex_sandboxing::SandboxablePreference;use codex_shell_command::bash::parse_shell_lc_plain_commands;use codex_shell_command::bash::parse_shell_lc_single_command_prefix;use codex_shell_escalation::EscalateServer;use codex_shell_escalation::EscalationDecision;use codex_shell_escalation::EscalationExecution;use codex_shell_escalation::EscalationPermissions;use codex_shell_escalation::EscalationPolicy;use codex_shell_escalation::EscalationPolicyFuture;use codex_shell_escalation::EscalationSession;use codex_shell_escalation::ExecParams;use codex_shell_escalation::ExecResult;use codex_shell_escalation::PreparedExec;use codex_shell_escalation::ResolvedPermissionProfile;use codex_shell_escalation::ShellCommandExecutor;use codex_shell_escalation::ShellCommandExecutorFuture;use codex_shell_escalation::Stopwatch;use codex_utils_absolute_path::AbsolutePathBuf;use codex_utils_path_uri::PathUri;use std::collections::HashMap;use std::path::PathBuf;use std::sync::Arc;use std::time::Duration;use tokio::sync::RwLock;use tokio_util::sync::CancellationToken;use uuid::Uuid;pub(crate) struct PreparedUnifiedExecZshFork {    pub(crate) exec_request: ExecRequest,    pub(crate) escalation_session: EscalationSession,}const PROMPT_CONFLICT_REASON: &str =    "approval required by policy, but AskForApproval is set to Never";const REJECT_SANDBOX_APPROVAL_REASON: &str =    "approval required by policy, but AskForApproval::Granular.sandbox_approval is false";const REJECT_RULES_APPROVAL_REASON: &str =    "approval required by policy rule, but AskForApproval::Granular.rules is false";fn approval_sandbox_permissions(    sandbox_permissions: SandboxPermissions,    additional_permissions_preapproved: bool,) -> SandboxPermissions {    if additional_permissions_preapproved        && matches!(            sandbox_permissions,            SandboxPermissions::WithAdditionalPermissions        )    {        SandboxPermissions::UseDefault    } else {        sandbox_permissions    }}pub(super) async fn try_run_zsh_fork(    req: &ShellRequest,    attempt: &SandboxAttempt<'_>,    ctx: &ToolCtx,    command: &[String],) -> Result<Option<ExecToolCallOutput>, ToolError> {    let Some(shell_zsh_path) = ctx.session.services.shell_zsh_path.as_ref() else {        tracing::warn!("ZshFork backend specified, but shell_zsh_path is not configured.");        return Ok(None);    };    if !ctx.session.features().enabled(Feature::ShellZshFork) {        tracing::warn!("ZshFork backend specified, but ShellZshFork feature is not enabled.");        return Ok(None);    }    if !matches!(ctx.session.user_shell().shell_type, ShellType::Zsh) {        tracing::warn!("ZshFork backend specified, but user shell is not Zsh.");        return Ok(None);    }    let (attempt_file_system_sandbox_policy, _) = attempt.permissions.to_runtime_permissions();    let sandbox_permissions = sandbox_permissions_preserving_denied_reads(        req.sandbox_permissions,        &attempt_file_system_sandbox_policy,    );    let req = &ShellRequest {        sandbox_permissions,        ..req.clone()    };    let mut env = exec_env_for_sandbox_permissions(&req.env, req.sandbox_permissions);    prepend_zsh_fork_bin_to_path(&mut env, shell_zsh_path);    let command =        build_sandbox_command(command, &req.cwd, &env, req.additional_permissions.clone())?;    let options = ExecOptions {        expiration: req.timeout_ms.into(),        capture_policy: ExecCapturePolicy::ShellTool,    };    let sandbox_exec_request = attempt        .env_for(            command,            options,            managed_network_for_sandbox_permissions(req.network.as_ref(), req.sandbox_permissions),        )        .map_err(ToolError::Codex)?;    let crate::sandboxing::ExecRequest {        command,        cwd: sandbox_cwd,        env: sandbox_env,        exec_server_env_config: _,        network: sandbox_network,        expiration: _sandbox_expiration,        capture_policy: _capture_policy,        sandbox,        windows_sandbox_policy_cwd: sandbox_policy_cwd,        windows_sandbox_workspace_roots,        windows_sandbox_level,        windows_sandbox_private_desktop: _windows_sandbox_private_desktop,        permission_profile,        file_system_sandbox_policy,        network_sandbox_policy,        windows_sandbox_filesystem_overrides: _windows_sandbox_filesystem_overrides,        arg0,    } = sandbox_exec_request;    let ParsedShellCommand { script, login, .. } = extract_shell_script(&command)?;    let effective_timeout = Duration::from_millis(        req.timeout_ms            .unwrap_or(crate::exec::DEFAULT_EXEC_COMMAND_TIMEOUT_MS),    );    let exec_policy = Arc::new(RwLock::new(        ctx.session.services.exec_policy.current().as_ref().clone(),    ));    let command_executor = CoreShellCommandExecutor {        command,        cwd: sandbox_cwd,        permission_profile,        file_system_sandbox_policy,        network_sandbox_policy,        sandbox,        env: sandbox_env,        network: sandbox_network,        windows_sandbox_level,        arg0,        sandbox_policy_cwd,        windows_sandbox_workspace_roots,        codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(),        use_legacy_landlock: ctx.turn.features.use_legacy_landlock(),    };    let main_execve_wrapper_exe = ctx        .session        .services        .main_execve_wrapper_exe        .clone()        .ok_or_else(|| {            ToolError::Rejected(                "zsh fork feature enabled, but execve wrapper is not configured".to_string(),            )        })?;    let exec_params = ExecParams {        command: script,        workdir: req.cwd.to_string_lossy().to_string(),        timeout_ms: Some(effective_timeout.as_millis() as u64),        login: Some(login),    };    // Note that Stopwatch starts immediately upon creation, so currently we try    // to minimize the time between creating the Stopwatch and starting the    // escalation server.    let stopwatch = Stopwatch::new(effective_timeout);    let mut cancel_token = stopwatch.cancellation_token();    if let Some(cancellation) = attempt.network_denial_cancellation_token.clone() {        cancel_token = cancel_when_either(cancel_token, cancellation);    }    let approval_sandbox_permissions = approval_sandbox_permissions(        req.sandbox_permissions,        req.additional_permissions_preapproved,    );    let escalation_policy = CoreShellActionProvider {        policy: Arc::clone(&exec_policy),        session: Arc::clone(&ctx.session),        turn: Arc::clone(&ctx.turn),        call_id: ctx.call_id.clone(),        tool_name: GuardianCommandSource::Shell,        approval_policy: ctx.turn.approval_policy.value(),        permission_profile: command_executor.permission_profile.clone(),        file_system_sandbox_policy: command_executor.file_system_sandbox_policy.clone(),        sandbox_permissions: req.sandbox_permissions,        approval_sandbox_permissions,        prompt_permissions: req.additional_permissions.clone(),        stopwatch: stopwatch.clone(),    };    let escalate_server = EscalateServer::new(        shell_zsh_path.clone(),        main_execve_wrapper_exe,        escalation_policy,    );    let exec_result = escalate_server        .exec(exec_params, cancel_token, Arc::new(command_executor))        .await        .map_err(|err| ToolError::Rejected(err.to_string()))?;    map_exec_result(attempt.sandbox, exec_result).map(Some)}pub(crate) async fn prepare_unified_exec_zsh_fork(    req: &crate::tools::runtimes::unified_exec::UnifiedExecRequest,    _attempt: &SandboxAttempt<'_>,    ctx: &ToolCtx,    exec_request: ExecRequest,    shell_zsh_path: &std::path::Path,    main_execve_wrapper_exe: &std::path::Path,) -> Result<Option<PreparedUnifiedExecZshFork>, ToolError> {    let parsed = match extract_shell_script(&exec_request.command) {        Ok(parsed) => parsed,        Err(err) => {            tracing::warn!("ZshFork unified exec fallback: {err:?}");            return Ok(None);        }    };    if parsed.program != shell_zsh_path.to_string_lossy() {        tracing::warn!(            "ZshFork backend specified, but unified exec command targets `{}` instead of `{}`.",            parsed.program,            shell_zsh_path.display(),        );        return Ok(None);    }    let exec_policy = Arc::new(RwLock::new(        ctx.session.services.exec_policy.current().as_ref().clone(),    ));    let command_executor = CoreShellCommandExecutor {        command: exec_request.command.clone(),        cwd: exec_request.cwd.clone(),        permission_profile: exec_request.permission_profile.clone(),        file_system_sandbox_policy: exec_request.file_system_sandbox_policy.clone(),        network_sandbox_policy: exec_request.network_sandbox_policy,        sandbox: exec_request.sandbox,        env: exec_request.env.clone(),        network: exec_request.network.clone(),        windows_sandbox_level: exec_request.windows_sandbox_level,        arg0: exec_request.arg0.clone(),        sandbox_policy_cwd: exec_request.windows_sandbox_policy_cwd.clone(),        windows_sandbox_workspace_roots: exec_request.windows_sandbox_workspace_roots.clone(),        codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(),        use_legacy_landlock: ctx.turn.features.use_legacy_landlock(),    };    let escalation_policy = CoreShellActionProvider {        policy: Arc::clone(&exec_policy),        session: Arc::clone(&ctx.session),        turn: Arc::clone(&ctx.turn),        call_id: ctx.call_id.clone(),        tool_name: GuardianCommandSource::UnifiedExec,        approval_policy: ctx.turn.approval_policy.value(),        permission_profile: exec_request.permission_profile.clone(),        file_system_sandbox_policy: exec_request.file_system_sandbox_policy.clone(),        sandbox_permissions: req.sandbox_permissions,        approval_sandbox_permissions: approval_sandbox_permissions(            req.sandbox_permissions,            req.additional_permissions_preapproved,        ),        prompt_permissions: req.additional_permissions.clone(),        stopwatch: Stopwatch::unlimited(),    };    let escalate_server = EscalateServer::new(        shell_zsh_path.to_path_buf(),        main_execve_wrapper_exe.to_path_buf(),        escalation_policy,    );    let escalation_session = escalate_server        .start_session(CancellationToken::new(), Arc::new(command_executor))        .map_err(|err| ToolError::Rejected(err.to_string()))?;    let mut exec_request = exec_request;    exec_request.env.extend(escalation_session.env().clone());    Ok(Some(PreparedUnifiedExecZshFork {        exec_request,        escalation_session,    }))}struct CoreShellActionProvider {    policy: Arc<RwLock<Policy>>,    session: Arc<crate::session::session::Session>,    turn: Arc<crate::session::turn_context::TurnContext>,    call_id: String,    tool_name: GuardianCommandSource,    approval_policy: AskForApproval,    permission_profile: PermissionProfile,    file_system_sandbox_policy: FileSystemSandboxPolicy,    sandbox_permissions: SandboxPermissions,    approval_sandbox_permissions: SandboxPermissions,    prompt_permissions: Option<AdditionalPermissionProfile>,    stopwatch: Stopwatch,}#[allow(clippy::large_enum_variant)]enum DecisionSource {    PrefixRule,    /// Often, this is `is_safe_command()`.    UnmatchedCommandFallback,}struct PromptDecision {    decision: ReviewDecision,    guardian_review_id: Option<String>,    rejection_message: Option<String>,}fn execve_prompt_is_rejected_by_policy(    approval_policy: AskForApproval,    decision_source: &DecisionSource,) -> Option<&'static str> {    match (approval_policy, decision_source) {        (AskForApproval::Never, _) => Some(PROMPT_CONFLICT_REASON),        (AskForApproval::Granular(granular_config), DecisionSource::PrefixRule)            if !granular_config.allows_rules_approval() =>        {            Some(REJECT_RULES_APPROVAL_REASON)        }        (AskForApproval::Granular(granular_config), DecisionSource::UnmatchedCommandFallback)            if !granular_config.allows_sandbox_approval() =>        {            Some(REJECT_SANDBOX_APPROVAL_REASON)        }        _ => None,    }}impl CoreShellActionProvider {    fn decision_driven_by_policy(matched_rules: &[RuleMatch], decision: Decision) -> bool {        matched_rules.iter().any(|rule_match| {            !matches!(rule_match, RuleMatch::HeuristicsRuleMatch { .. })                && rule_match.decision() == decision        })    }    fn shell_request_escalation_execution(        sandbox_permissions: SandboxPermissions,        permission_profile: &PermissionProfile,        file_system_sandbox_policy: &FileSystemSandboxPolicy,        additional_permissions: Option<&AdditionalPermissionProfile>,    ) -> EscalationExecution {        match sandbox_permissions {            SandboxPermissions::UseDefault => EscalationExecution::TurnDefault,            SandboxPermissions::RequireEscalated => {                if unsandboxed_execution_allowed(file_system_sandbox_policy) {                    EscalationExecution::Unsandboxed                } else {                    EscalationExecution::TurnDefault                }            }            SandboxPermissions::WithAdditionalPermissions => additional_permissions                .map(|_| {                    // Shell request additional permissions were already normalized and                    // merged into the first-attempt sandbox policy.                    EscalationExecution::Permissions(                        EscalationPermissions::ResolvedPermissionProfile(                            ResolvedPermissionProfile {                                permission_profile: permission_profile.clone(),                            },                        ),                    )                })                .unwrap_or(EscalationExecution::TurnDefault),        }    }    async fn prompt(        &self,        program: &AbsolutePathBuf,        argv: &[String],        workdir: &AbsolutePathBuf,        stopwatch: &Stopwatch,        additional_permissions: Option<AdditionalPermissionProfile>,    ) -> anyhow::Result<PromptDecision> {        let command = join_program_and_argv(program, argv);        let workdir = workdir.clone();        let session = self.session.clone();        let turn = self.turn.clone();        let call_id = self.call_id.clone();        let approval_id = Some(Uuid::new_v4().to_string());        let source = self.tool_name;        let guardian_review_id = routes_approval_to_guardian(&turn).then(new_guardian_review_id);        Ok(stopwatch            .pause_for(async move {                // 1) Run PermissionRequest hooks                let permission_request = PermissionRequestPayload::bash(                    codex_shell_command::parse_command::shlex_join(&command),                    /*description*/ None,                );                let effective_approval_id = approval_id.clone().unwrap_or_else(|| call_id.clone());                match run_permission_request_hooks(                    &session,                    &turn,                    &effective_approval_id,                    permission_request,                )                .await                {                    Some(PermissionRequestDecision::Allow) => {                        return PromptDecision {                            decision: ReviewDecision::Approved,                            guardian_review_id: None,                            rejection_message: None,                        };                    }                    Some(PermissionRequestDecision::Deny { message }) => {                        return PromptDecision {                            decision: ReviewDecision::Denied,                            guardian_review_id: None,                            rejection_message: Some(message),                        };                    }                    None => {}                }                // 2) Route to Guardian if configured                if let Some(review_id) = guardian_review_id.clone() {                    let decision = review_approval_request(                        &session,                        &turn,                        review_id.clone(),                        GuardianApprovalRequest::Execve {                            id: call_id.clone(),                            source,                            program: program.to_string_lossy().into_owned(),                            argv: argv.to_vec(),                            cwd: workdir.clone(),                            additional_permissions,                        },                        /*retry_reason*/ None,                    )                    .await;                    return PromptDecision {                        decision,                        guardian_review_id,                        rejection_message: None,                    };                }                // 3) Fall back to regular user prompt                let decision = session                    .request_command_approval(                        &turn,                        call_id,                        approval_id,                        command,                        workdir.clone(),                        /*reason*/ None,                        /*network_approval_context*/ None,                        /*proposed_execpolicy_amendment*/ None,                        additional_permissions,                        Some(vec![ReviewDecision::Approved, ReviewDecision::Abort]),                    )                    .await;                PromptDecision {                    decision,                    guardian_review_id: None,                    rejection_message: None,                }            })            .await)    }    #[allow(clippy::too_many_arguments)]    async fn process_decision(        &self,        decision: Decision,        needs_escalation: bool,        program: &AbsolutePathBuf,        argv: &[String],        workdir: &AbsolutePathBuf,        prompt_permissions: Option<AdditionalPermissionProfile>,        escalation_execution: EscalationExecution,        decision_source: DecisionSource,    ) -> anyhow::Result<EscalationDecision> {        let action = match decision {            Decision::Forbidden => {                EscalationDecision::deny(Some("Execution forbidden by policy".to_string()))            }            Decision::Prompt => {                if execve_prompt_is_rejected_by_policy(self.approval_policy, &decision_source)                    .is_some()                {                    EscalationDecision::deny(Some("Execution forbidden by policy".to_string()))                } else {                    let prompt_decision = self                        .prompt(program, argv, workdir, &self.stopwatch, prompt_permissions)                        .await?;                    match prompt_decision.decision {                        ReviewDecision::Approved                        | ReviewDecision::ApprovedForSession                        | ReviewDecision::ApprovedExecpolicyAmendment { .. } => {                            if needs_escalation {                                EscalationDecision::escalate(escalation_execution.clone())                            } else {                                EscalationDecision::run()                            }                        }                        ReviewDecision::NetworkPolicyAmendment {                            network_policy_amendment,                        } => match network_policy_amendment.action {                            NetworkPolicyRuleAction::Allow => {                                if needs_escalation {                                    EscalationDecision::escalate(escalation_execution.clone())                                } else {                                    EscalationDecision::run()                                }                            }                            NetworkPolicyRuleAction::Deny => {                                EscalationDecision::deny(Some("User denied execution".to_string()))                            }                        },                        ReviewDecision::Denied => {                            let message = if let Some(message) =                                prompt_decision.rejection_message.clone()                            {                                message                            } else if let Some(review_id) =                                prompt_decision.guardian_review_id.as_deref()                            {                                guardian_rejection_message(self.session.as_ref(), review_id).await                            } else {                                "User denied execution".to_string()                            };                            EscalationDecision::deny(Some(message))                        }                        ReviewDecision::TimedOut => {                            EscalationDecision::deny(Some(guardian_timeout_message()))                        }                        ReviewDecision::Abort => {                            EscalationDecision::deny(Some("User cancelled execution".to_string()))                        }                    }                }            }            Decision::Allow => {                if needs_escalation {                    EscalationDecision::escalate(escalation_execution)                } else {                    EscalationDecision::run()                }            }        };        tracing::debug!(            "Policy decision for command {program:?} is {decision:?}, leading to escalation action {action:?}",        );        Ok(action)    }}// Shell-wrapper parsing is weaker than direct exec interception because it can// only see the script text, not the final resolved executable path. Keep it// disabled by default so path-sensitive rules rely on the later authoritative// execve interception.const ENABLE_INTERCEPTED_EXEC_POLICY_SHELL_WRAPPER_PARSING: bool = false;impl CoreShellActionProvider {    async fn determine_action(        &self,        program: &AbsolutePathBuf,        argv: &[String],        workdir: &AbsolutePathBuf,    ) -> anyhow::Result<EscalationDecision> {        tracing::debug!(            "Determining escalation action for command {program:?} with args {argv:?} in {workdir:?}"        );        let evaluation = {            let policy = self.policy.read().await;            evaluate_intercepted_exec_policy(                &policy,                program,                argv,                InterceptedExecPolicyContext {                    approval_policy: self.approval_policy,                    permission_profile: self.permission_profile.clone(),                    windows_sandbox_level: self.turn.windows_sandbox_level,                    sandbox_permissions: self.approval_sandbox_permissions,                    enable_shell_wrapper_parsing:                        ENABLE_INTERCEPTED_EXEC_POLICY_SHELL_WRAPPER_PARSING,                },            )        };        // When true, means the Evaluation was due to *.rules, not the        // fallback function.        let decision_driven_by_policy =            Self::decision_driven_by_policy(&evaluation.matched_rules, evaluation.decision);        let unsandboxed_allowed = unsandboxed_execution_allowed(&self.file_system_sandbox_policy);        let needs_escalation = match self.sandbox_permissions {            SandboxPermissions::UseDefault => unsandboxed_allowed && decision_driven_by_policy,            SandboxPermissions::RequireEscalated => unsandboxed_allowed,            SandboxPermissions::WithAdditionalPermissions => true,        };        let decision_source = if decision_driven_by_policy {            DecisionSource::PrefixRule        } else {            DecisionSource::UnmatchedCommandFallback        };        let escalation_execution = match decision_source {            DecisionSource::PrefixRule if unsandboxed_allowed => EscalationExecution::Unsandboxed,            DecisionSource::PrefixRule => EscalationExecution::TurnDefault,            DecisionSource::UnmatchedCommandFallback => Self::shell_request_escalation_execution(                self.sandbox_permissions,                &self.permission_profile,                &self.file_system_sandbox_policy,                self.prompt_permissions.as_ref(),            ),        };        self.process_decision(            evaluation.decision,            needs_escalation,            program,            argv,            workdir,            self.prompt_permissions.clone(),            escalation_execution,            decision_source,        )        .await    }}impl EscalationPolicy for CoreShellActionProvider {    fn determine_action<'a>(        &'a self,        program: &'a AbsolutePathBuf,        argv: &'a [String],        workdir: &'a AbsolutePathBuf,    ) -> EscalationPolicyFuture<'a> {        Box::pin(CoreShellActionProvider::determine_action(            self, program, argv, workdir,        ))    }}fn evaluate_intercepted_exec_policy(    policy: &Policy,    program: &AbsolutePathBuf,    argv: &[String],    context: InterceptedExecPolicyContext,) -> Evaluation {    let InterceptedExecPolicyContext {        approval_policy,        permission_profile,        windows_sandbox_level,        sandbox_permissions,        enable_shell_wrapper_parsing,    } = context;    let CandidateCommands {        commands,        used_complex_parsing,    } = if enable_shell_wrapper_parsing {        // In this codepath, the first argument in `commands` could be a bare        // name like `find` instead of an absolute path like `/usr/bin/find`.        // It could also be a shell built-in like `echo`.        commands_for_intercepted_exec_policy(program, argv)    } else {        // In this codepath, `commands` has a single entry where the program        // is always an absolute path.        CandidateCommands {            commands: vec![join_program_and_argv(program, argv)],            used_complex_parsing: false,        }    };    let fallback = |cmd: &[String]| {        crate::exec_policy::render_decision_for_unmatched_command(            cmd,            crate::exec_policy::UnmatchedCommandContext {                approval_policy,                permission_profile: &permission_profile,                windows_sandbox_level,                sandbox_permissions,                used_complex_parsing,                command_origin: crate::exec_policy::ExecPolicyCommandOrigin::Generic,            },        )    };    policy.check_multiple_with_options(        commands.iter(),        &fallback,        &MatchOptions {            resolve_host_executables: true,        },    )}#[derive(Clone)]struct InterceptedExecPolicyContext {    approval_policy: AskForApproval,    permission_profile: PermissionProfile,    windows_sandbox_level: WindowsSandboxLevel,    sandbox_permissions: SandboxPermissions,    enable_shell_wrapper_parsing: bool,}struct CandidateCommands {    commands: Vec<Vec<String>>,    used_complex_parsing: bool,}fn commands_for_intercepted_exec_policy(    program: &AbsolutePathBuf,    argv: &[String],) -> CandidateCommands {    if let [_, flag, script] = argv {        let shell_command = [            program.to_string_lossy().to_string(),            flag.clone(),            script.clone(),        ];        if let Some(commands) = parse_shell_lc_plain_commands(&shell_command) {            return CandidateCommands {                commands,                used_complex_parsing: false,            };        }        if let Some(single_command) = parse_shell_lc_single_command_prefix(&shell_command) {            return CandidateCommands {                commands: vec![single_command],                used_complex_parsing: true,            };        }    }    CandidateCommands {        commands: vec![join_program_and_argv(program, argv)],        used_complex_parsing: false,    }}struct CoreShellCommandExecutor {    command: Vec<String>,    cwd: AbsolutePathBuf,    permission_profile: PermissionProfile,    file_system_sandbox_policy: FileSystemSandboxPolicy,    network_sandbox_policy: NetworkSandboxPolicy,    sandbox: SandboxType,    env: HashMap<String, String>,    network: Option<codex_network_proxy::NetworkProxy>,    windows_sandbox_level: WindowsSandboxLevel,    arg0: Option<String>,    sandbox_policy_cwd: AbsolutePathBuf,    windows_sandbox_workspace_roots: Vec<AbsolutePathBuf>,    codex_linux_sandbox_exe: Option<PathBuf>,    use_legacy_landlock: bool,}struct PrepareSandboxedExecParams<'a> {    command: Vec<String>,    workdir: &'a AbsolutePathBuf,    env: HashMap<String, String>,    permission_profile: &'a PermissionProfile,    additional_permissions: Option<AdditionalPermissionProfile>,}impl ShellCommandExecutor for CoreShellCommandExecutor {    fn run(        &self,        _command: Vec<String>,        _cwd: PathBuf,        env_overlay: HashMap<String, String>,        cancel_rx: CancellationToken,        after_spawn: Option<Box<dyn FnOnce() + Send>>,    ) -> ShellCommandExecutorFuture<'_, ExecResult> {        Box::pin(CoreShellCommandExecutor::run(            self,            env_overlay,            cancel_rx,            after_spawn,        ))    }    fn prepare_escalated_exec<'a>(        &'a self,        program: &'a AbsolutePathBuf,        argv: &'a [String],        workdir: &'a AbsolutePathBuf,        env: HashMap<String, String>,        execution: EscalationExecution,    ) -> ShellCommandExecutorFuture<'a, PreparedExec> {        Box::pin(CoreShellCommandExecutor::prepare_escalated_exec(            self, program, argv, workdir, env, execution,        ))    }}impl CoreShellCommandExecutor {    async fn run(        &self,        env_overlay: HashMap<String, String>,        cancel_rx: CancellationToken,        after_spawn: Option<Box<dyn FnOnce() + Send>>,    ) -> anyhow::Result<ExecResult> {        let mut exec_env = self.env.clone();        // `env_overlay` comes from `EscalationSession::env()`, so merge only the        // wrapper/socket variables into the base shell environment.        for var in ["CODEX_ESCALATE_SOCKET", "EXEC_WRAPPER"] {            if let Some(value) = env_overlay.get(var) {                exec_env.insert(var.to_string(), value.clone());            }        }        let result = crate::sandboxing::execute_exec_request_with_after_spawn(            crate::sandboxing::ExecRequest {                command: self.command.clone(),                cwd: self.cwd.clone(),                env: exec_env,                exec_server_env_config: None,                network: self.network.clone(),                expiration: ExecExpiration::Cancellation(cancel_rx),                capture_policy: ExecCapturePolicy::ShellTool,                sandbox: self.sandbox,                windows_sandbox_policy_cwd: self.sandbox_policy_cwd.clone(),                windows_sandbox_workspace_roots: self.windows_sandbox_workspace_roots.clone(),                windows_sandbox_level: self.windows_sandbox_level,                windows_sandbox_private_desktop: false,                permission_profile: self.permission_profile.clone(),                file_system_sandbox_policy: self.file_system_sandbox_policy.clone(),                network_sandbox_policy: self.network_sandbox_policy,                windows_sandbox_filesystem_overrides: None,                arg0: self.arg0.clone(),            },            /*stdout_stream*/ None,            after_spawn,        )        .await?;        Ok(ExecResult {            exit_code: result.exit_code,            stdout: result.stdout.text,            stderr: result.stderr.text,            output: result.aggregated_output.text,            duration: result.duration,            timed_out: result.timed_out,        })    }    async fn prepare_escalated_exec(        &self,        program: &AbsolutePathBuf,        argv: &[String],        workdir: &AbsolutePathBuf,        env: HashMap<String, String>,        execution: EscalationExecution,    ) -> anyhow::Result<PreparedExec> {        let command = join_program_and_argv(program, argv);        let Some(first_arg) = argv.first() else {            return Err(anyhow::anyhow!(                "intercepted exec request must contain argv[0]"            ));        };        let prepared = match execution {            EscalationExecution::Unsandboxed => PreparedExec {                command,                cwd: workdir.to_path_buf(),                env: exec_env_for_sandbox_permissions(&env, SandboxPermissions::RequireEscalated),                arg0: Some(first_arg.clone()),            },            EscalationExecution::TurnDefault => {                self.prepare_sandboxed_exec(PrepareSandboxedExecParams {                    command,                    workdir,                    env,                    permission_profile: &self.permission_profile,                    additional_permissions: None,                })?            }            EscalationExecution::Permissions(                EscalationPermissions::AdditionalPermissionProfile(permission_profile),            ) => {                // Merge additive permissions into the existing turn/request sandbox policy.                self.prepare_sandboxed_exec(PrepareSandboxedExecParams {                    command,                    workdir,                    env,                    permission_profile: &self.permission_profile,                    additional_permissions: Some(permission_profile),                })?            }            EscalationExecution::Permissions(EscalationPermissions::ResolvedPermissionProfile(                permissions,            )) => {                // Use a fully specified permission profile instead of merging into the turn policy.                self.prepare_sandboxed_exec(PrepareSandboxedExecParams {                    command,                    workdir,                    env,                    permission_profile: &permissions.permission_profile,                    additional_permissions: None,                })?            }        };        Ok(prepared)    }    #[allow(clippy::too_many_arguments)]    fn prepare_sandboxed_exec(        &self,        params: PrepareSandboxedExecParams<'_>,    ) -> anyhow::Result<PreparedExec> {        let PrepareSandboxedExecParams {            command,            workdir,            env,            permission_profile,            additional_permissions,        } = params;        let (file_system_sandbox_policy, network_sandbox_policy) =            permission_profile.to_runtime_permissions();        let (program, args) = command            .split_first()            .ok_or_else(|| anyhow::anyhow!("prepared command must not be empty"))?;        let sandbox_manager = SandboxManager::new();        let sandbox = sandbox_manager.select_initial(            &file_system_sandbox_policy,            network_sandbox_policy,            SandboxablePreference::Auto,            self.windows_sandbox_level,            self.network.is_some(),        );        let cwd = PathUri::from_abs_path(workdir);        let sandbox_policy_cwd = PathUri::from_abs_path(&self.sandbox_policy_cwd);        let command = SandboxCommand {            program: program.clone().into(),            args: args.to_vec(),            cwd,            env,            additional_permissions,        };        let options = ExecOptions {            expiration: ExecExpiration::DefaultTimeout,            capture_policy: ExecCapturePolicy::ShellTool,        };        let exec_request = sandbox_manager.transform(SandboxTransformRequest {            command,            permissions: permission_profile,            sandbox,            enforce_managed_network: self.network.is_some(),            network: self.network.as_ref(),            sandbox_policy_cwd: &sandbox_policy_cwd,            codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.as_deref(),            use_legacy_landlock: self.use_legacy_landlock,            windows_sandbox_level: self.windows_sandbox_level,            windows_sandbox_private_desktop: false,        })?;        let mut exec_request = crate::sandboxing::ExecRequest::from_sandbox_exec_request(            exec_request,            options,            self.windows_sandbox_workspace_roots.clone(),        );        if let Some(network) = exec_request.network.as_ref() {            network.apply_to_env(&mut exec_request.env);        }        Ok(PreparedExec {            command: exec_request.command,            cwd: exec_request.cwd.to_path_buf(),            env: exec_request.env,            arg0: exec_request.arg0,        })    }}#[derive(Debug, Eq, PartialEq)]struct ParsedShellCommand {    program: String,    script: String,    login: bool,}fn extract_shell_script(command: &[String]) -> Result<ParsedShellCommand, ToolError> {    // Commands reaching zsh-fork can be wrapped by environment/sandbox helpers, so    // we search for the first `-c`/`-lc` triple anywhere in the argv rather    // than assuming it is the first positional form.    if let Some((program, script, login)) = command.windows(3).find_map(|parts| match parts {        [program, flag, script] if flag == "-c" => {            Some((program.to_owned(), script.to_owned(), false))        }        [program, flag, script] if flag == "-lc" => {            Some((program.to_owned(), script.to_owned(), true))        }        _ => None,    }) {        return Ok(ParsedShellCommand {            program,            script,            login,        });    }    Err(ToolError::Rejected(        "unexpected shell command format for zsh-fork execution".to_string(),    ))}fn map_exec_result(    sandbox: SandboxType,    result: ExecResult,) -> Result<ExecToolCallOutput, ToolError> {    let output = ExecToolCallOutput {        exit_code: result.exit_code,        stdout: StreamOutput::new(result.stdout.clone()),        stderr: StreamOutput::new(result.stderr.clone()),        aggregated_output: StreamOutput::new(result.output.clone()),        duration: result.duration,        timed_out: result.timed_out,    };    if result.timed_out {        return Err(ToolError::Codex(CodexErr::Sandbox(SandboxErr::Timeout {            output: Box::new(output),        })));    }    if is_likely_sandbox_denied(sandbox, &output) {        return Err(ToolError::Codex(CodexErr::Sandbox(SandboxErr::Denied {            output: Box::new(output),            network_policy_decision: None,        })));    }    Ok(output)}/// Convert an intercepted exec `(program, argv)` into a command vector suitable/// for display and policy parsing.////// The intercepted `argv` includes `argv[0]`, but once we have normalized the/// executable path in `program`, we should replace the original `argv[0]`/// rather than duplicating it as an apparent user argument.fn join_program_and_argv(program: &AbsolutePathBuf, argv: &[String]) -> Vec<String> {    std::iter::once(program.to_string_lossy().to_string())        .chain(argv.iter().skip(1).cloned())        .collect::<Vec<_>>()}#[cfg(test)]#[path = "unix_escalation_tests.rs"]mod tests;