Skip to content
Open
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
14 changes: 5 additions & 9 deletions codex-rs/app-server/src/codex_message_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ use codex_core::exec::ExecParams;
use codex_core::exec_env::create_env;
use codex_core::features::Feature;
use codex_core::find_conversation_path_by_id_str;
use codex_core::get_platform_sandbox;
use codex_core::git_info::git_diff_to_remote;
use codex_core::parse_cursor;
use codex_core::protocol::EventMsg;
Expand Down Expand Up @@ -1182,13 +1181,10 @@ impl CodexMessageProcessor {
.sandbox_policy
.unwrap_or_else(|| self.config.sandbox_policy.clone());

let sandbox_type = match &effective_policy {
codex_core::protocol::SandboxPolicy::DangerFullAccess => {
codex_core::exec::SandboxType::None
}
_ => get_platform_sandbox().unwrap_or(codex_core::exec::SandboxType::None),
};
tracing::debug!("Sandbox type: {sandbox_type:?}");
let sandboxed = !matches!(
&effective_policy,
codex_core::protocol::SandboxPolicy::DangerFullAccess
);
Comment on lines +1184 to +1187
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Badge Respect platform sandbox availability

This new sandboxed flag ignores whether the current platform actually has a sandbox. In production we use get_platform_has_sandbox() (see core/src/safety.rs) to track e.g. the experimental Windows sandbox, and previously this code called get_platform_sandbox() to fall back to SandboxType::None when it was disabled. Now we always pass sandboxed = true for any policy that isn’t DangerFullAccess, so on Windows without the sandbox enabled we will still route through exec_windows_sandbox (see core/src/exec.rs around the new if sandboxed { return exec_windows_sandbox(...) }). That configuration is common, and those calls will start failing instead of running unsandboxed. Please gate this bool on get_platform_has_sandbox() the same way we did before.

Useful? React with 👍 / 👎.

let codex_linux_sandbox_exe = self.config.codex_linux_sandbox_exe.clone();
let outgoing = self.outgoing.clone();
let req_id = request_id;
Expand All @@ -1197,7 +1193,7 @@ impl CodexMessageProcessor {
tokio::spawn(async move {
match codex_core::exec::process_exec_tool_call(
exec_params,
sandbox_type,
sandboxed,
&effective_policy,
sandbox_cwd.as_path(),
&codex_linux_sandbox_exe,
Expand Down
212 changes: 99 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,25 +96,16 @@ 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<()> {
let sandbox_mode = create_sandbox_mode(full_auto);
Expand Down Expand Up @@ -133,120 +133,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?
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::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?
}
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 @@ -206,10 +206,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 @@ -820,7 +819,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