use crate::product::agent::exec::ExecExpiration;
use crate::product::agent::exec::ExecToolCallOutput;
use crate::product::agent::exec::SandboxType;
use crate::product::agent::exec::StdoutStream;
use crate::product::agent::exec::execute_exec_env;
use crate::product::agent::landlock::create_linux_sandbox_command_args;
use crate::product::agent::protocol::SandboxPolicy;
#[cfg(target_os = "macos")]
use crate::product::agent::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
#[cfg(target_os = "macos")]
use crate::product::agent::seatbelt::create_seatbelt_command_args;
#[cfg(target_os = "macos")]
use crate::product::agent::spawn::LHA_SANDBOX_ENV_VAR;
use crate::product::agent::spawn::LHA_SANDBOX_NETWORK_DISABLED_ENV_VAR;
use crate::product::agent::tools::sandboxing::SandboxablePreference;
use crate::product::protocol::config_types::WindowsSandboxLevel;
pub use crate::product::protocol::models::SandboxPermissions;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
#[derive(Debug)]
pub struct CommandSpec {
pub program: String,
pub args: Vec<String>,
pub cwd: PathBuf,
pub env: HashMap<String, String>,
pub expiration: ExecExpiration,
pub sandbox_permissions: SandboxPermissions,
pub justification: Option<String>,
}
#[derive(Debug)]
pub struct ExecEnv {
pub command: Vec<String>,
pub cwd: PathBuf,
pub env: HashMap<String, String>,
pub expiration: ExecExpiration,
pub sandbox: SandboxType,
pub windows_sandbox_level: WindowsSandboxLevel,
pub sandbox_permissions: SandboxPermissions,
pub justification: Option<String>,
pub arg0: Option<String>,
}
pub enum SandboxPreference {
Auto,
Require,
Forbid,
}
#[derive(Debug, thiserror::Error)]
pub(crate) enum SandboxTransformError {
#[error("missing lha-linux-sandbox executable path")]
MissingLinuxSandboxExecutable,
#[cfg(not(target_os = "macos"))]
#[error("seatbelt sandbox is only available on macOS")]
SeatbeltUnavailable,
}
#[derive(Default)]
pub struct SandboxManager;
impl SandboxManager {
pub fn new() -> Self {
Self
}
pub(crate) fn select_initial(
&self,
policy: &SandboxPolicy,
pref: SandboxablePreference,
windows_sandbox_level: WindowsSandboxLevel,
) -> SandboxType {
match pref {
SandboxablePreference::Forbid => SandboxType::None,
SandboxablePreference::Require => {
crate::product::agent::safety::get_platform_sandbox(
windows_sandbox_level != WindowsSandboxLevel::Disabled,
)
.unwrap_or(SandboxType::None)
}
SandboxablePreference::Auto => match policy {
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => {
SandboxType::None
}
_ => crate::product::agent::safety::get_platform_sandbox(
windows_sandbox_level != WindowsSandboxLevel::Disabled,
)
.unwrap_or(SandboxType::None),
},
}
}
pub(crate) fn transform(
&self,
mut spec: CommandSpec,
policy: &SandboxPolicy,
sandbox: SandboxType,
sandbox_policy_cwd: &Path,
codex_linux_sandbox_exe: Option<&PathBuf>,
windows_sandbox_level: WindowsSandboxLevel,
) -> Result<ExecEnv, SandboxTransformError> {
let mut env = spec.env;
if !policy.has_full_network_access() {
env.insert(
LHA_SANDBOX_NETWORK_DISABLED_ENV_VAR.to_string(),
"1".to_string(),
);
}
let mut command = Vec::with_capacity(1 + spec.args.len());
command.push(spec.program);
command.append(&mut spec.args);
let (command, sandbox_env, arg0_override) = match sandbox {
SandboxType::None => (command, HashMap::new(), None),
#[cfg(target_os = "macos")]
SandboxType::MacosSeatbelt => {
let mut seatbelt_env = HashMap::new();
seatbelt_env.insert(LHA_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string());
let mut args =
create_seatbelt_command_args(command.clone(), policy, sandbox_policy_cwd);
let mut full_command = Vec::with_capacity(1 + args.len());
full_command.push(MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string());
full_command.append(&mut args);
(full_command, seatbelt_env, None)
}
#[cfg(not(target_os = "macos"))]
SandboxType::MacosSeatbelt => return Err(SandboxTransformError::SeatbeltUnavailable),
SandboxType::LinuxSeccomp => {
let exe = codex_linux_sandbox_exe
.ok_or(SandboxTransformError::MissingLinuxSandboxExecutable)?;
let mut args =
create_linux_sandbox_command_args(command.clone(), policy, sandbox_policy_cwd);
let mut full_command = Vec::with_capacity(1 + args.len());
full_command.push(exe.to_string_lossy().to_string());
full_command.append(&mut args);
(
full_command,
HashMap::new(),
Some("lha-linux-sandbox".to_string()),
)
}
#[cfg(target_os = "windows")]
SandboxType::WindowsRestrictedToken => (command, HashMap::new(), None),
#[cfg(not(target_os = "windows"))]
SandboxType::WindowsRestrictedToken => (command, HashMap::new(), None),
};
env.extend(sandbox_env);
Ok(ExecEnv {
command,
cwd: spec.cwd,
env,
expiration: spec.expiration,
sandbox,
windows_sandbox_level,
sandbox_permissions: spec.sandbox_permissions,
justification: spec.justification,
arg0: arg0_override,
})
}
pub fn denied(&self, sandbox: SandboxType, out: &ExecToolCallOutput) -> bool {
crate::product::agent::exec::is_likely_sandbox_denied(sandbox, out)
}
}
pub async fn execute_env(
env: ExecEnv,
policy: &SandboxPolicy,
stdout_stream: Option<StdoutStream>,
) -> crate::product::agent::error::Result<ExecToolCallOutput> {
execute_exec_env(env, policy, stdout_stream).await
}