exec tests: launch sandbox cases from permission profiles

This commit is contained in:
Michael Bolin
2026-04-30 02:36:30 -07:00
parent 200c83f7d7
commit 550adee585

View File

@@ -1,6 +1,7 @@
#![cfg(unix)]
use codex_core::spawn::StdioPolicy;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::test_support::PathBufExt;
use std::collections::HashMap;
@@ -14,7 +15,7 @@ use tokio::process::Child;
async fn spawn_command_under_sandbox(
command: Vec<String>,
command_cwd: AbsolutePathBuf,
sandbox_policy: &SandboxPolicy,
permission_profile: &PermissionProfile,
sandbox_cwd: &AbsolutePathBuf,
stdio_policy: StdioPolicy,
env: HashMap<String, String>,
@@ -24,7 +25,6 @@ async fn spawn_command_under_sandbox(
use codex_core::exec::build_exec_request;
use codex_core::sandboxing::SandboxPermissions;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use std::process::Stdio;
let codex_linux_sandbox_exe = None;
@@ -42,7 +42,7 @@ async fn spawn_command_under_sandbox(
justification: None,
arg0: None,
},
&PermissionProfile::from_legacy_sandbox_policy(sandbox_policy),
permission_profile,
sandbox_cwd,
&codex_linux_sandbox_exe,
/*use_legacy_landlock*/ false,
@@ -83,22 +83,20 @@ async fn spawn_command_under_sandbox(
async fn spawn_command_under_sandbox(
command: Vec<String>,
command_cwd: AbsolutePathBuf,
sandbox_policy: &SandboxPolicy,
permission_profile: &PermissionProfile,
sandbox_cwd: &AbsolutePathBuf,
stdio_policy: StdioPolicy,
env: HashMap<String, String>,
) -> std::io::Result<Child> {
use codex_core::spawn_command_under_linux_sandbox;
use codex_protocol::models::PermissionProfile;
let codex_linux_sandbox_exe = core_test_support::find_codex_linux_sandbox_exe()
.map_err(|err| io::Error::new(io::ErrorKind::NotFound, err))?;
let permission_profile = PermissionProfile::from_legacy_sandbox_policy(sandbox_policy);
spawn_command_under_linux_sandbox(
codex_linux_sandbox_exe,
command,
command_cwd,
&permission_profile,
permission_profile,
sandbox_cwd,
/*use_legacy_landlock*/ false,
stdio_policy,
@@ -118,9 +116,16 @@ async fn spawn_command_under_sandbox(
async fn linux_sandbox_test_env() -> Option<HashMap<String, String>> {
let command_cwd = AbsolutePathBuf::current_dir().ok()?;
let sandbox_cwd = command_cwd.clone();
let policy = SandboxPolicy::new_read_only_policy();
let permission_profile = PermissionProfile::read_only();
if can_apply_linux_sandbox_policy(&policy, &command_cwd, &sandbox_cwd, HashMap::new()).await {
if can_apply_linux_sandbox_policy(
&permission_profile,
&command_cwd,
&sandbox_cwd,
HashMap::new(),
)
.await
{
return Some(HashMap::new());
}
@@ -135,7 +140,7 @@ async fn linux_sandbox_test_env() -> Option<HashMap<String, String>> {
/// This is used as a capability probe so sandbox behavior tests only run when
/// Landlock enforcement is actually active.
async fn can_apply_linux_sandbox_policy(
policy: &SandboxPolicy,
permission_profile: &PermissionProfile,
command_cwd: &AbsolutePathBuf,
sandbox_cwd: &AbsolutePathBuf,
env: HashMap<String, String>,
@@ -143,7 +148,7 @@ async fn can_apply_linux_sandbox_policy(
let spawn_result = spawn_command_under_sandbox(
vec!["/usr/bin/true".to_string()],
command_cwd.clone(),
policy,
permission_profile,
sandbox_cwd,
StdioPolicy::RedirectForShellTool,
env,
@@ -180,12 +185,12 @@ async fn python_multiprocessing_lock_works_under_sandbox() {
#[cfg(target_os = "linux")]
let writable_roots: Vec<AbsolutePathBuf> = vec!["/dev/shm".try_into().unwrap()];
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots,
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
};
let permission_profile = PermissionProfile::workspace_write_with(
&writable_roots,
NetworkSandboxPolicy::Restricted,
/*exclude_tmpdir_env_var*/ false,
/*exclude_slash_tmp*/ false,
);
let python_code = r#"import multiprocessing
from multiprocessing import Lock, Process
@@ -210,7 +215,7 @@ if __name__ == '__main__':
python_code.to_string(),
],
command_cwd,
&policy,
&permission_profile,
&sandbox_cwd,
StdioPolicy::Inherit,
sandbox_env,
@@ -242,7 +247,7 @@ async fn python_getpwuid_works_under_sandbox() {
return;
}
let policy = SandboxPolicy::new_read_only_policy();
let permission_profile = PermissionProfile::read_only();
let command_cwd = AbsolutePathBuf::current_dir().expect("should be able to get current dir");
let sandbox_cwd = command_cwd.clone();
@@ -253,7 +258,7 @@ async fn python_getpwuid_works_under_sandbox() {
"import pwd, os; print(pwd.getpwuid(os.getuid()))".to_string(),
],
command_cwd,
&policy,
&permission_profile,
&sandbox_cwd,
StdioPolicy::RedirectForShellTool,
sandbox_env,
@@ -294,12 +299,12 @@ async fn sandbox_distinguishes_command_and_policy_cwds() {
// Note writable_roots is empty: verify that `canonical_allowed_path` is
// writable only because it is under the sandbox policy cwd, not because it
// is under a writable root.
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
};
let permission_profile = PermissionProfile::workspace_write_with(
&[],
NetworkSandboxPolicy::Restricted,
/*exclude_tmpdir_env_var*/ true,
/*exclude_slash_tmp*/ true,
);
// Attempt to write inside the command cwd, which is outside of the sandbox policy cwd.
let mut child = spawn_command_under_sandbox(
@@ -309,7 +314,7 @@ async fn sandbox_distinguishes_command_and_policy_cwds() {
"echo forbidden > forbidden.txt".to_string(),
],
command_root.clone(),
&policy,
&permission_profile,
&canonical_sandbox_root,
StdioPolicy::Inherit,
sandbox_env.clone(),
@@ -340,7 +345,7 @@ async fn sandbox_distinguishes_command_and_policy_cwds() {
canonical_allowed_path.to_string_lossy().into_owned(),
],
command_root,
&policy,
&permission_profile,
&canonical_sandbox_root,
StdioPolicy::Inherit,
sandbox_env,
@@ -375,12 +380,12 @@ async fn sandbox_blocks_first_time_dot_codex_creation() {
create_dir_all(&repo_root).await.expect("mkdir repo");
let dot_codex = repo_root.join(".codex");
let config_toml = dot_codex.join("config.toml");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
};
let permission_profile = PermissionProfile::workspace_write_with(
&[],
NetworkSandboxPolicy::Restricted,
/*exclude_tmpdir_env_var*/ true,
/*exclude_slash_tmp*/ true,
);
let mut child = spawn_command_under_sandbox(
vec![
@@ -390,7 +395,7 @@ async fn sandbox_blocks_first_time_dot_codex_creation() {
.to_string(),
],
repo_root.clone(),
&policy,
&permission_profile,
&repo_root,
StdioPolicy::RedirectForShellTool,
sandbox_env,
@@ -507,7 +512,7 @@ fn unix_sock_body() {
async fn allow_unix_socketpair_recvfrom() {
run_code_under_sandbox(
"allow_unix_socketpair_recvfrom",
&SandboxPolicy::new_read_only_policy(),
&PermissionProfile::read_only(),
|| async { unix_sock_body() },
)
.await
@@ -519,7 +524,7 @@ const IN_SANDBOX_ENV_VAR: &str = "IN_SANDBOX";
#[expect(clippy::expect_used)]
pub async fn run_code_under_sandbox<F, Fut>(
test_selector: &str,
policy: &SandboxPolicy,
permission_profile: &PermissionProfile,
child_body: F,
) -> io::Result<Option<ExitStatus>>
where
@@ -544,7 +549,7 @@ where
let mut child = spawn_command_under_sandbox(
cmds,
command_cwd,
policy,
permission_profile,
&sandbox_cwd,
stdio_policy,
HashMap::from([("IN_SANDBOX".into(), "1".into())]),