mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
This updates `ExecParams` so that instead of taking `timeout_ms: Option<u64>`, it now takes a more general cancellation mechanism, `ExecExpiration`, which is an enum that includes a `Cancellation(tokio_util::sync::CancellationToken)` variant. If the cancellation token is fired, then `process_exec_tool_call()` returns in the same way as if a timeout was exceeded. This is necessary so that in #6973, we can manage the timeout logic external to the `process_exec_tool_call()` because we want to "suspend" the timeout when an elicitation from a human user is pending. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/6972). * #7005 * #6973 * __->__ #6972
202 lines
6.7 KiB
Rust
202 lines
6.7 KiB
Rust
/*
|
||
Module: sandboxing
|
||
|
||
Build platform wrappers and produce ExecEnv for execution. Owns low‑level
|
||
sandbox placement and transformation of portable CommandSpec into a
|
||
ready‑to‑spawn environment.
|
||
*/
|
||
|
||
pub mod assessment;
|
||
|
||
use crate::exec::ExecExpiration;
|
||
use crate::exec::ExecToolCallOutput;
|
||
use crate::exec::SandboxType;
|
||
use crate::exec::StdoutStream;
|
||
use crate::exec::execute_exec_env;
|
||
use crate::landlock::create_linux_sandbox_command_args;
|
||
use crate::protocol::SandboxPolicy;
|
||
#[cfg(target_os = "macos")]
|
||
use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
|
||
#[cfg(target_os = "macos")]
|
||
use crate::seatbelt::create_seatbelt_command_args;
|
||
#[cfg(target_os = "macos")]
|
||
use crate::spawn::CODEX_SANDBOX_ENV_VAR;
|
||
use crate::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
|
||
use crate::tools::sandboxing::SandboxablePreference;
|
||
use std::collections::HashMap;
|
||
use std::path::Path;
|
||
use std::path::PathBuf;
|
||
|
||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||
pub enum SandboxPermissions {
|
||
UseDefault,
|
||
RequireEscalated,
|
||
}
|
||
|
||
impl SandboxPermissions {
|
||
pub fn requires_escalated_permissions(self) -> bool {
|
||
matches!(self, SandboxPermissions::RequireEscalated)
|
||
}
|
||
}
|
||
|
||
impl From<bool> for SandboxPermissions {
|
||
fn from(with_escalated_permissions: bool) -> Self {
|
||
if with_escalated_permissions {
|
||
SandboxPermissions::RequireEscalated
|
||
} else {
|
||
SandboxPermissions::UseDefault
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
pub struct CommandSpec {
|
||
pub program: String,
|
||
pub args: Vec<String>,
|
||
pub cwd: PathBuf,
|
||
pub env: HashMap<String, String>,
|
||
pub expiration: ExecExpiration,
|
||
pub with_escalated_permissions: Option<bool>,
|
||
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 with_escalated_permissions: Option<bool>,
|
||
pub justification: Option<String>,
|
||
pub arg0: Option<String>,
|
||
}
|
||
|
||
pub enum SandboxPreference {
|
||
Auto,
|
||
Require,
|
||
Forbid,
|
||
}
|
||
|
||
#[derive(Debug, thiserror::Error)]
|
||
pub(crate) enum SandboxTransformError {
|
||
#[error("missing codex-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,
|
||
) -> SandboxType {
|
||
match pref {
|
||
SandboxablePreference::Forbid => SandboxType::None,
|
||
SandboxablePreference::Require => {
|
||
// Require a platform sandbox when available; on Windows this
|
||
// respects the enable_experimental_windows_sandbox feature.
|
||
crate::safety::get_platform_sandbox().unwrap_or(SandboxType::None)
|
||
}
|
||
SandboxablePreference::Auto => match policy {
|
||
SandboxPolicy::DangerFullAccess => SandboxType::None,
|
||
_ => crate::safety::get_platform_sandbox().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>,
|
||
) -> Result<ExecEnv, SandboxTransformError> {
|
||
let mut env = spec.env;
|
||
if !policy.has_full_network_access() {
|
||
env.insert(
|
||
CODEX_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(CODEX_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("codex-linux-sandbox".to_string()),
|
||
)
|
||
}
|
||
// On Windows, the restricted token sandbox executes in-process via the
|
||
// codex-windows-sandbox crate. We leave the command unchanged here and
|
||
// branch during execution based on the sandbox type.
|
||
#[cfg(target_os = "windows")]
|
||
SandboxType::WindowsRestrictedToken => (command, HashMap::new(), None),
|
||
// When building for non-Windows targets, this variant is never constructed.
|
||
#[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,
|
||
with_escalated_permissions: spec.with_escalated_permissions,
|
||
justification: spec.justification,
|
||
arg0: arg0_override,
|
||
})
|
||
}
|
||
|
||
pub fn denied(&self, sandbox: SandboxType, out: &ExecToolCallOutput) -> bool {
|
||
crate::exec::is_likely_sandbox_denied(sandbox, out)
|
||
}
|
||
}
|
||
|
||
pub async fn execute_env(
|
||
env: ExecEnv,
|
||
policy: &SandboxPolicy,
|
||
stdout_stream: Option<StdoutStream>,
|
||
) -> crate::error::Result<ExecToolCallOutput> {
|
||
execute_exec_env(env, policy, stdout_stream).await
|
||
}
|