Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion codex-rs/app-server/src/codex_message_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1178,7 +1178,6 @@ impl CodexMessageProcessor {
.sandbox_policy
.map(|policy| policy.to_core())
.unwrap_or_else(|| self.config.sandbox_policy.clone());

let codex_linux_sandbox_exe = self.config.codex_linux_sandbox_exe.clone();
let outgoing = self.outgoing.clone();
let req_id = request_id;
Expand Down
215 changes: 102 additions & 113 deletions codex-rs/cli/src/debug_sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ use codex_common::CliConfigOverrides;
use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
use codex_core::exec_env::create_env;
use codex_core::landlock::spawn_command_under_linux_sandbox;
#[cfg(target_os = "macos")]
use codex_core::seatbelt::spawn_command_under_seatbelt;
use codex_core::spawn::StdioPolicy;
use codex_protocol::config_types::SandboxMode;

#[cfg(target_os = "linux")]
use codex_core::landlock::spawn_command_under_linux_sandbox;

use crate::LandlockCommand;
use crate::SeatbeltCommand;
use crate::WindowsCommand;
Expand All @@ -39,7 +41,6 @@ pub async fn run_command_under_seatbelt(
command,
config_overrides,
codex_linux_sandbox_exe,
SandboxType::Seatbelt,
log_denials,
)
.await
Expand All @@ -53,6 +54,7 @@ pub async fn run_command_under_seatbelt(
anyhow::bail!("Seatbelt sandbox is only available on macOS");
}

#[cfg(target_os = "linux")]
pub async fn run_command_under_landlock(
command: LandlockCommand,
codex_linux_sandbox_exe: Option<PathBuf>,
Expand All @@ -67,12 +69,19 @@ pub async fn run_command_under_landlock(
command,
config_overrides,
codex_linux_sandbox_exe,
SandboxType::Landlock,
false,
)
.await
}

#[cfg(not(target_os = "linux"))]
pub async fn run_command_under_landlock(
_command: LandlockCommand,
_codex_linux_sandbox_exe: Option<PathBuf>,
) -> anyhow::Result<()> {
anyhow::bail!("Landlock sandbox is only available on Linux");
}

pub async fn run_command_under_windows(
command: WindowsCommand,
codex_linux_sandbox_exe: Option<PathBuf>,
Expand All @@ -87,27 +96,21 @@ pub async fn run_command_under_windows(
command,
config_overrides,
codex_linux_sandbox_exe,
SandboxType::Windows,
false,
)
.await
}

enum SandboxType {
#[cfg(target_os = "macos")]
Seatbelt,
Landlock,
Windows,
}

async fn run_command_under_sandbox(
full_auto: bool,
command: Vec<String>,
config_overrides: CliConfigOverrides,
codex_linux_sandbox_exe: Option<PathBuf>,
sandbox_type: SandboxType,
log_denials: bool,
) -> anyhow::Result<()> {
#[cfg(not(target_os = "macos"))]
let _ = log_denials;

let sandbox_mode = create_sandbox_mode(full_auto);
let config = Config::load_with_cli_overrides(
config_overrides
Expand All @@ -133,120 +136,106 @@ async fn run_command_under_sandbox(
let env = create_env(&config.shell_environment_policy);

// Special-case Windows sandbox: execute and exit the process to emulate inherited stdio.
if let SandboxType::Windows = sandbox_type {
#[cfg(target_os = "windows")]
{
use codex_windows_sandbox::run_windows_sandbox_capture;

let policy_str = serde_json::to_string(&config.sandbox_policy)?;

let sandbox_cwd = sandbox_policy_cwd.clone();
let cwd_clone = cwd.clone();
let env_map = env.clone();
let command_vec = command.clone();
let base_dir = config.codex_home.clone();

// Preflight audit is invoked elsewhere at the appropriate times.
let res = tokio::task::spawn_blocking(move || {
run_windows_sandbox_capture(
policy_str.as_str(),
&sandbox_cwd,
base_dir.as_path(),
command_vec,
&cwd_clone,
env_map,
None,
)
})
.await;

let capture = match res {
Ok(Ok(v)) => v,
Ok(Err(err)) => {
eprintln!("windows sandbox failed: {err}");
std::process::exit(1);
}
Err(join_err) => {
eprintln!("windows sandbox join error: {join_err}");
std::process::exit(1);
}
};

if !capture.stdout.is_empty() {
use std::io::Write;
let _ = std::io::stdout().write_all(&capture.stdout);
#[cfg(target_os = "windows")]
{
use codex_windows_sandbox::run_windows_sandbox_capture;

let policy_str = serde_json::to_string(&config.sandbox_policy)?;

let sandbox_cwd = sandbox_policy_cwd.clone();
let cwd_clone = cwd.clone();
let env_map = env.clone();
let command_vec = command.clone();
let base_dir = config.codex_home.clone();

// Preflight audit is invoked elsewhere at the appropriate times.
let res = tokio::task::spawn_blocking(move || {
run_windows_sandbox_capture(
policy_str.as_str(),
&sandbox_cwd,
base_dir.as_path(),
command_vec,
&cwd_clone,
env_map,
None,
)
})
.await;

let capture = match res {
Ok(Ok(v)) => v,
Ok(Err(err)) => {
eprintln!("windows sandbox failed: {err}");
std::process::exit(1);
}
if !capture.stderr.is_empty() {
use std::io::Write;
let _ = std::io::stderr().write_all(&capture.stderr);
Err(join_err) => {
eprintln!("windows sandbox join error: {join_err}");
std::process::exit(1);
}
};

std::process::exit(capture.exit_code);
if !capture.stdout.is_empty() {
use std::io::Write;
let _ = std::io::stdout().write_all(&capture.stdout);
}
#[cfg(not(target_os = "windows"))]
{
anyhow::bail!("Windows sandbox is only available on Windows");
if !capture.stderr.is_empty() {
use std::io::Write;
let _ = std::io::stderr().write_all(&capture.stderr);
}

std::process::exit(capture.exit_code);
}

#[cfg(target_os = "macos")]
let mut denial_logger = log_denials.then(DenialLogger::new).flatten();
#[cfg(not(target_os = "macos"))]
let _ = log_denials;

let mut child = match sandbox_type {
#[cfg(target_os = "macos")]
SandboxType::Seatbelt => {
spawn_command_under_seatbelt(
command,
cwd,
&config.sandbox_policy,
sandbox_policy_cwd.as_path(),
stdio_policy,
env,
)
.await?
}
SandboxType::Landlock => {
#[expect(clippy::expect_used)]
let codex_linux_sandbox_exe = config
.codex_linux_sandbox_exe
.expect("codex-linux-sandbox executable not found");
spawn_command_under_linux_sandbox(
codex_linux_sandbox_exe,
command,
cwd,
&config.sandbox_policy,
sandbox_policy_cwd.as_path(),
stdio_policy,
env,
)
.await?
let status = {
let mut denial_logger = log_denials.then(DenialLogger::new).flatten();
let mut child = spawn_command_under_seatbelt(
command,
cwd,
&config.sandbox_policy,
sandbox_policy_cwd.as_path(),
stdio_policy,
env,
)
.await?;
if let Some(denial_logger) = &mut denial_logger {
denial_logger.on_child_spawn(&child);
}
SandboxType::Windows => {
unreachable!("Windows sandbox should have been handled above");
}
};

#[cfg(target_os = "macos")]
if let Some(denial_logger) = &mut denial_logger {
denial_logger.on_child_spawn(&child);
}

let status = child.wait().await?;
let status = child.wait().await?;

#[cfg(target_os = "macos")]
if let Some(denial_logger) = denial_logger {
let denials = denial_logger.finish().await;
eprintln!("\n=== Sandbox denials ===");
if denials.is_empty() {
eprintln!("None found.");
} else {
for seatbelt::SandboxDenial { name, capability } in denials {
eprintln!("({name}) {capability}");
if let Some(denial_logger) = denial_logger {
let denials = denial_logger.finish().await;
eprintln!("\n=== Sandbox denials ===");
if denials.is_empty() {
eprintln!("None found.");
} else {
for seatbelt::SandboxDenial { name, capability } in denials {
eprintln!("({name}) {capability}");
}
}
}
}

status
};
#[cfg(target_os = "linux")]
let status = {
#[expect(clippy::expect_used)]
let codex_linux_sandbox_exe = config
.codex_linux_sandbox_exe
.expect("codex-linux-sandbox executable not found");
let mut child = spawn_command_under_linux_sandbox(
codex_linux_sandbox_exe,
command,
cwd,
&config.sandbox_policy,
sandbox_policy_cwd.as_path(),
stdio_policy,
env,
)
.await?;
child.wait().await?
};

handle_exit_status(status);
}
Expand Down
9 changes: 4 additions & 5 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,9 @@ pub struct Config {
/// output will be hyperlinked using the specified URI scheme.
pub file_opener: UriBasedFileOpener,

/// Path to the `codex-linux-sandbox` executable. This must be set if
/// [`crate::exec::SandboxType::LinuxSeccomp`] is used. Note that this
/// cannot be set in the config file: it must be set in code via
/// [`ConfigOverrides`].
/// Path to the `codex-linux-sandbox` executable. This must be set when the
/// Linux sandbox is used. Note that this cannot be set in the config file:
/// it must be set in code via [`ConfigOverrides`].
///
/// When this program is invoked, arg0 will be set to `codex-linux-sandbox`.
pub codex_linux_sandbox_exe: Option<PathBuf>,
Expand Down Expand Up @@ -834,7 +833,7 @@ impl ConfigToml {
if cfg!(target_os = "windows")
&& matches!(resolved_sandbox_mode, SandboxMode::WorkspaceWrite)
// If the experimental Windows sandbox is enabled, do not force a downgrade.
&& crate::safety::get_platform_sandbox().is_none()
&& !crate::safety::get_platform_has_sandbox()
{
sandbox_policy = SandboxPolicy::new_read_only_policy();
forced_auto_mode_downgraded_on_windows = true;
Expand Down
Loading