/* Module: runtimes Concrete ToolRuntime implementations for specific tools. Each runtime stays small and focused and reuses the orchestrator for approvals + sandbox + retry. */ use crate::exec_env::CODEX_PERMISSION_PROFILE_ENV_VAR; use crate::exec_env::CODEX_SESSION_ID_ENV_VAR; use crate::exec_env::CODEX_THREAD_ID_ENV_VAR; use crate::sandboxing::SandboxPermissions; use crate::shell::Shell; use crate::shell::ShellType; use codex_apply_patch::CODEX_APPLY_PATCH_PRESERVE_LINE_ENDINGS_ENV_VAR; use codex_core_plugins::PLUGIN_METRICS_OUTPUT_ENV_VAR; #[cfg(unix)] use codex_install_context::InstallContext; #[cfg(target_os = "macos")] use codex_network_proxy::CODEX_PROXY_GIT_SSH_COMMAND_MARKER; use codex_network_proxy::CUSTOM_CA_ENV_KEYS; use codex_network_proxy::PROXY_ACTIVE_ENV_KEY; use codex_network_proxy::PROXY_ENV_KEYS; #[cfg(target_os = "macos")] use codex_network_proxy::PROXY_GIT_SSH_COMMAND_ENV_KEY; pub(crate) use codex_network_proxy::is_managed_proxy_env_var; pub(crate) use codex_network_proxy::strip_managed_proxy_env; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::shell_environment::is_non_inheritable_env_var; use codex_utils_absolute_path::AbsolutePathBuf; use std::collections::HashMap; #[cfg(unix)] use std::path::Path; pub(crate) mod apply_patch; pub(crate) mod unified_exec; pub(crate) mod zsh_fork; pub(crate) fn exec_env_for_sandbox_permissions( env: &HashMap, sandbox_permissions: SandboxPermissions, ) -> HashMap { let mut env = env.clone(); // Escalated commands intentionally use the original, unbrokered environment. This branch is // defensive cleanup for a caller that passes an environment already prepared by Codex. if sandbox_permissions.requires_escalated_permissions() && env.contains_key(PROXY_ACTIVE_ENV_KEY) { strip_managed_proxy_env(&mut env); } env } /// Prepends `path_entry` to `PATH`, removing duplicate and empty existing /// entries. /// /// Returns the updated `PATH` value when `env` was changed. Returns `None` when /// `path_entry` is empty, leaving `env` untouched so an empty entry does not add /// the current working directory to command lookup. #[cfg(unix)] fn prepend_path_entry(env: &mut HashMap, path_entry: &str) -> Option { if path_entry.is_empty() { None } else { let updated_path = match env.get("PATH") { Some(path) if !path.is_empty() => std::iter::once(path_entry) .chain( path.split(':') .filter(|entry| !entry.is_empty() && *entry != path_entry), ) .collect::>() .join(":"), _ => path_entry.to_string(), }; env.insert("PATH".to_string(), updated_path.clone()); Some(updated_path) } } /// PATH entries owned by Codex runtime setup. /// /// These are applied to the live exec environment immediately and replayed after /// restoring a shell snapshot, unless the user explicitly overrides `PATH`. #[derive(Debug, Default, Eq, PartialEq)] pub(crate) struct RuntimePathPrepends { entries: Vec, } impl RuntimePathPrepends { #[cfg(unix)] pub(crate) fn prepend(&mut self, env: &mut HashMap, path_entry: &Path) { let path_entry = path_entry.to_string_lossy().to_string(); if prepend_path_entry(env, &path_entry).is_some() { self.entries.retain(|entry| entry != &path_entry); self.entries.push(path_entry); } } fn shell_exports_after_snapshot( &self, explicit_env_overrides: &HashMap, ) -> String { if explicit_env_overrides.contains_key("PATH") { return String::new(); } self.entries .iter() .filter(|entry| !entry.is_empty()) .map(|entry| { let entry = shell_single_quote(entry); format!( "if [ -n \"${{PATH:-}}\" ]; then export PATH='{entry}':\"$PATH\"; else export PATH='{entry}'; fi" ) }) .collect::>() .join("\n") } } #[cfg(unix)] pub(crate) fn apply_package_path_prepend( env: &mut HashMap, runtime_path_prepends: &mut RuntimePathPrepends, ) { let Some(path_dir) = InstallContext::current() .package_layout .as_ref() .and_then(|package_layout| package_layout.path_dir.as_ref()) else { return; }; runtime_path_prepends.prepend(env, path_dir.as_path()); } #[cfg(unix)] pub(crate) fn apply_zsh_fork_path_prepend( env: &mut HashMap, runtime_path_prepends: &mut RuntimePathPrepends, shell_zsh_path: &Path, ) { let Some(zsh_bin_dir) = shell_zsh_path.parent() else { return; }; runtime_path_prepends.prepend(env, zsh_bin_dir); } pub(crate) fn disable_powershell_profile_for_elevated_windows_sandbox( command: &[String], shell_type: Option<&ShellType>, sandbox_requested: bool, windows_sandbox_level: WindowsSandboxLevel, ) -> Vec { if shell_type != Some(&ShellType::PowerShell) || !sandbox_requested || windows_sandbox_level != WindowsSandboxLevel::Elevated || command.is_empty() { return command.to_vec(); } if command[1..] .iter() .any(|arg| arg.eq_ignore_ascii_case("-NoProfile")) { return command.to_vec(); } // The elevated Windows sandbox runs as a dedicated sandbox account while // HOME/USERPROFILE may still point at the real user profile. Loading // PowerShell profiles in that mixed context is not a valid login shell. let mut command = command.to_vec(); command.insert(1, "-NoProfile".to_string()); command } /// POSIX-only helper: for commands produced by `Shell::derive_exec_args` /// for Bash/Zsh/sh of the form `[shell_path, "-lc", "