mirror of
https://github.com/openai/codex.git
synced 2026-09-16 12:13:30 +00:00
## What changed - Add explicit MXC backend selection and carry its identity through exec-server process reporting and sandbox violation classification. - Launch MXC through the Codex executable with the effective permission profile and command environment. - Reject exec-server MXC requests when native MXC is unavailable or when they request a TTY, an `arg0` override, or managed networking. Reject private desktop isolation during MXC preparation. - Allow an explicitly empty child environment and avoid exposing request payload values in launcher decode errors. ## Testing Add coverage for sandbox selection and unsupported-request rejection, plus Windows RPC tests for stdin writes and temporary-directory permissions derived from the command environment. Native MXC tests skip when MXC is unavailable. GitOrigin-RevId: 3626ff0f9ad7f9b812ce09b68c31ea9a5a9c72b1
177 lines
5.0 KiB
Rust
177 lines
5.0 KiB
Rust
#![cfg(target_os = "macos")]
|
|
|
|
use codex_core::exec::ExecCapturePolicy;
|
|
use codex_core::exec::ExecParams;
|
|
use codex_core::exec::process_exec_tool_call;
|
|
use codex_core::sandboxing::SandboxPermissions;
|
|
use codex_core::spawn::CODEX_SANDBOX_ENV_VAR;
|
|
use codex_protocol::config_types::WindowsSandboxLevel;
|
|
use codex_protocol::error::Result;
|
|
use codex_protocol::exec_output::ExecToolCallOutput;
|
|
use codex_protocol::models::PermissionProfile;
|
|
use codex_sandboxing::SandboxType;
|
|
use codex_sandboxing::get_platform_sandbox;
|
|
use core_test_support::PathExt;
|
|
use std::collections::HashMap;
|
|
use tempfile::TempDir;
|
|
|
|
fn skip_test() -> bool {
|
|
if std::env::var(CODEX_SANDBOX_ENV_VAR) == Ok("seatbelt".to_string()) {
|
|
eprintln!("{CODEX_SANDBOX_ENV_VAR} is set to 'seatbelt', skipping test.");
|
|
return true;
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
async fn run_test_cmd<I, S>(tmp: TempDir, command: I) -> Result<ExecToolCallOutput>
|
|
where
|
|
I: IntoIterator<Item = S>,
|
|
S: Into<String>,
|
|
{
|
|
let sandbox_type = get_platform_sandbox(/*windows_sandbox_enabled*/ false)
|
|
.expect("should be able to get sandbox type");
|
|
assert_eq!(sandbox_type, SandboxType::MacosSeatbelt);
|
|
let cwd = tmp.path().abs();
|
|
|
|
let params = ExecParams {
|
|
command: command.into_iter().map(Into::into).collect(),
|
|
cwd: cwd.clone(),
|
|
expiration: 1000.into(),
|
|
capture_policy: ExecCapturePolicy::ShellTool,
|
|
env: HashMap::new(),
|
|
network: None,
|
|
network_environment_id: None,
|
|
sandbox_permissions: SandboxPermissions::UseDefault,
|
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
|
windows_sandbox_private_desktop: false,
|
|
justification: None,
|
|
arg0: None,
|
|
};
|
|
|
|
process_exec_tool_call(
|
|
params,
|
|
&PermissionProfile::read_only(),
|
|
&cwd,
|
|
std::slice::from_ref(&cwd),
|
|
&None,
|
|
/*codex_self_exe*/ &None,
|
|
/*use_legacy_landlock*/ false,
|
|
/*stdout_stream*/ None,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Command succeeds with exit code 0 normally
|
|
#[tokio::test]
|
|
async fn exit_code_0_succeeds() {
|
|
if skip_test() {
|
|
return;
|
|
}
|
|
|
|
let tmp = TempDir::new().expect("should be able to create temp dir");
|
|
let cmd = vec!["echo", "hello"];
|
|
|
|
let output = run_test_cmd(tmp, cmd).await.unwrap();
|
|
assert_eq!(output.stdout.text, "hello\n");
|
|
assert_eq!(output.stderr.text, "");
|
|
assert_eq!(output.stdout.truncated_after_lines, None);
|
|
}
|
|
|
|
/// Command succeeds with exit code 0 normally
|
|
#[tokio::test]
|
|
async fn truncates_output_lines() {
|
|
if skip_test() {
|
|
return;
|
|
}
|
|
|
|
let tmp = TempDir::new().expect("should be able to create temp dir");
|
|
let cmd = vec!["seq", "300"];
|
|
|
|
let output = run_test_cmd(tmp, cmd).await.unwrap();
|
|
|
|
let expected_output = (1..=300)
|
|
.map(|i| format!("{i}\n"))
|
|
.collect::<Vec<_>>()
|
|
.join("");
|
|
assert_eq!(output.stdout.text, expected_output);
|
|
assert_eq!(output.stdout.truncated_after_lines, None);
|
|
}
|
|
|
|
/// Command succeeds with exit code 0 normally
|
|
#[tokio::test]
|
|
async fn truncates_output_bytes() {
|
|
if skip_test() {
|
|
return;
|
|
}
|
|
|
|
let tmp = TempDir::new().expect("should be able to create temp dir");
|
|
// each line is 1000 bytes
|
|
let cmd = vec!["bash", "-lc", "seq 15 | awk '{printf \"%-1000s\\n\", $0}'"];
|
|
|
|
let output = run_test_cmd(tmp, cmd).await.unwrap();
|
|
|
|
assert!(output.stdout.text.len() >= 15000);
|
|
assert_eq!(output.stdout.truncated_after_lines, None);
|
|
}
|
|
|
|
/// Command not found returns exit code 127, this is not considered a sandbox error
|
|
#[tokio::test]
|
|
async fn exit_command_not_found_is_ok() {
|
|
if skip_test() {
|
|
return;
|
|
}
|
|
|
|
let tmp = TempDir::new().expect("should be able to create temp dir");
|
|
let cmd = vec!["/bin/bash", "-c", "nonexistent_command_12345"];
|
|
run_test_cmd(tmp, cmd).await.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn openpty_works_under_real_exec_seatbelt_path() {
|
|
if skip_test() {
|
|
return;
|
|
}
|
|
|
|
let python = match which::which("python3") {
|
|
Ok(path) => path,
|
|
Err(_) => {
|
|
eprintln!("python3 not found in PATH, skipping test.");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let tmp = TempDir::new().expect("should be able to create temp dir");
|
|
let cmd = vec![
|
|
python.to_string_lossy().into_owned(),
|
|
"-c".to_string(),
|
|
r#"import os
|
|
|
|
master, slave = os.openpty()
|
|
os.write(slave, b"ping")
|
|
assert os.read(master, 4) == b"ping""#
|
|
.to_string(),
|
|
];
|
|
|
|
let output = run_test_cmd(tmp, cmd).await.unwrap();
|
|
assert_eq!(output.stdout.text, "");
|
|
assert_eq!(output.stderr.text, "");
|
|
}
|
|
|
|
/// Writing a file fails and should be considered a sandbox error
|
|
#[tokio::test]
|
|
async fn write_file_fails_as_sandbox_error() {
|
|
if skip_test() {
|
|
return;
|
|
}
|
|
|
|
let tmp = TempDir::new().expect("should be able to create temp dir");
|
|
let path = tmp.path().join("test.txt");
|
|
let cmd = vec![
|
|
"/usr/bin/touch",
|
|
path.to_str().expect("should be able to get path"),
|
|
];
|
|
|
|
assert!(run_test_cmd(tmp, cmd).await.is_err());
|
|
}
|