mirror of
https://github.com/openai/codex.git
synced 2026-05-03 02:46:39 +00:00
Compare commits
1 Commits
main
...
codex/bugb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88effe3455 |
@@ -24,6 +24,8 @@ use std::process::Command;
|
|||||||
|
|
||||||
use codex_protocol::error::CodexErr;
|
use codex_protocol::error::CodexErr;
|
||||||
use codex_protocol::error::Result;
|
use codex_protocol::error::Result;
|
||||||
|
use codex_protocol::protocol::FileSystemAccessMode;
|
||||||
|
use codex_protocol::protocol::FileSystemPath;
|
||||||
use codex_protocol::protocol::FileSystemSandboxPolicy;
|
use codex_protocol::protocol::FileSystemSandboxPolicy;
|
||||||
use codex_protocol::protocol::WritableRoot;
|
use codex_protocol::protocol::WritableRoot;
|
||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
@@ -101,6 +103,7 @@ impl BwrapNetworkMode {
|
|||||||
pub(crate) struct BwrapArgs {
|
pub(crate) struct BwrapArgs {
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
pub preserved_files: Vec<File>,
|
pub preserved_files: Vec<File>,
|
||||||
|
pub cleanup_mount_points: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrap a command with bubblewrap so the filesystem is read-only by default,
|
/// Wrap a command with bubblewrap so the filesystem is read-only by default,
|
||||||
@@ -126,6 +129,7 @@ pub(crate) fn create_bwrap_command_args(
|
|||||||
Ok(BwrapArgs {
|
Ok(BwrapArgs {
|
||||||
args: command,
|
args: command,
|
||||||
preserved_files: Vec::new(),
|
preserved_files: Vec::new(),
|
||||||
|
cleanup_mount_points: Vec::new(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(create_bwrap_flags_full_filesystem(command, options))
|
Ok(create_bwrap_flags_full_filesystem(command, options))
|
||||||
@@ -165,6 +169,7 @@ fn create_bwrap_flags_full_filesystem(command: Vec<String>, options: BwrapOption
|
|||||||
BwrapArgs {
|
BwrapArgs {
|
||||||
args,
|
args,
|
||||||
preserved_files: Vec::new(),
|
preserved_files: Vec::new(),
|
||||||
|
cleanup_mount_points: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +184,7 @@ fn create_bwrap_flags(
|
|||||||
let BwrapArgs {
|
let BwrapArgs {
|
||||||
args: filesystem_args,
|
args: filesystem_args,
|
||||||
preserved_files,
|
preserved_files,
|
||||||
|
cleanup_mount_points,
|
||||||
} = create_filesystem_args(
|
} = create_filesystem_args(
|
||||||
file_system_sandbox_policy,
|
file_system_sandbox_policy,
|
||||||
sandbox_policy_cwd,
|
sandbox_policy_cwd,
|
||||||
@@ -216,6 +222,7 @@ fn create_bwrap_flags(
|
|||||||
Ok(BwrapArgs {
|
Ok(BwrapArgs {
|
||||||
args,
|
args,
|
||||||
preserved_files,
|
preserved_files,
|
||||||
|
cleanup_mount_points,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +248,8 @@ fn create_filesystem_args(
|
|||||||
glob_scan_max_depth: Option<usize>,
|
glob_scan_max_depth: Option<usize>,
|
||||||
) -> Result<BwrapArgs> {
|
) -> Result<BwrapArgs> {
|
||||||
let unreadable_globs = file_system_sandbox_policy.get_unreadable_globs_with_cwd(cwd);
|
let unreadable_globs = file_system_sandbox_policy.get_unreadable_globs_with_cwd(cwd);
|
||||||
// Bubblewrap requires bind mount targets to exist. Skip missing writable
|
let mut cleanup_mount_points = Vec::new();
|
||||||
|
// Bubblewrap requires bind mount sources to exist. Skip missing writable
|
||||||
// roots so mixed-platform configs can keep harmless paths for other
|
// roots so mixed-platform configs can keep harmless paths for other
|
||||||
// environments without breaking Linux command startup.
|
// environments without breaking Linux command startup.
|
||||||
let mut writable_roots = file_system_sandbox_policy
|
let mut writable_roots = file_system_sandbox_policy
|
||||||
@@ -381,6 +389,7 @@ fn create_filesystem_args(
|
|||||||
append_unreadable_root_args(
|
append_unreadable_root_args(
|
||||||
&mut args,
|
&mut args,
|
||||||
&mut preserved_files,
|
&mut preserved_files,
|
||||||
|
&mut cleanup_mount_points,
|
||||||
unreadable_root,
|
unreadable_root,
|
||||||
&allowed_write_paths,
|
&allowed_write_paths,
|
||||||
)?;
|
)?;
|
||||||
@@ -401,22 +410,36 @@ fn create_filesystem_args(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mount_root = symlink_target.as_deref().unwrap_or(root);
|
let mount_root = symlink_target.as_deref().unwrap_or(root);
|
||||||
args.push("--bind".to_string());
|
|
||||||
args.push(path_to_string(mount_root));
|
|
||||||
args.push(path_to_string(mount_root));
|
|
||||||
|
|
||||||
let mut read_only_subpaths: Vec<PathBuf> = writable_root
|
let mut read_only_subpaths: Vec<PathBuf> = writable_root
|
||||||
.read_only_subpaths
|
.read_only_subpaths
|
||||||
.iter()
|
.iter()
|
||||||
.map(|path| path.as_path().to_path_buf())
|
.map(|path| path.as_path().to_path_buf())
|
||||||
.filter(|path| !unreadable_paths.contains(path))
|
.filter(|path| !unreadable_paths.contains(path))
|
||||||
.collect();
|
.collect();
|
||||||
|
if root == cwd {
|
||||||
|
let top_level_git = root.join(".git");
|
||||||
|
if !read_only_subpaths.iter().any(|path| path == &top_level_git)
|
||||||
|
&& !unreadable_paths.contains(&top_level_git)
|
||||||
|
&& !has_explicit_write_entry_for_path(file_system_sandbox_policy, &top_level_git)
|
||||||
|
{
|
||||||
|
read_only_subpaths.push(top_level_git);
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(target) = &symlink_target {
|
if let Some(target) = &symlink_target {
|
||||||
read_only_subpaths = remap_paths_for_symlink_target(read_only_subpaths, root, target);
|
read_only_subpaths = remap_paths_for_symlink_target(read_only_subpaths, root, target);
|
||||||
}
|
}
|
||||||
|
args.push("--bind".to_string());
|
||||||
|
args.push(path_to_string(mount_root));
|
||||||
|
args.push(path_to_string(mount_root));
|
||||||
|
|
||||||
read_only_subpaths.sort_by_key(|path| path_depth(path));
|
read_only_subpaths.sort_by_key(|path| path_depth(path));
|
||||||
for subpath in read_only_subpaths {
|
for subpath in read_only_subpaths {
|
||||||
append_read_only_subpath_args(&mut args, &subpath, &allowed_write_paths)?;
|
append_read_only_subpath_args(
|
||||||
|
&mut args,
|
||||||
|
&mut cleanup_mount_points,
|
||||||
|
&subpath,
|
||||||
|
&allowed_write_paths,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
let mut nested_unreadable_roots: Vec<PathBuf> = unreadable_roots
|
let mut nested_unreadable_roots: Vec<PathBuf> = unreadable_roots
|
||||||
.iter()
|
.iter()
|
||||||
@@ -432,6 +455,7 @@ fn create_filesystem_args(
|
|||||||
append_unreadable_root_args(
|
append_unreadable_root_args(
|
||||||
&mut args,
|
&mut args,
|
||||||
&mut preserved_files,
|
&mut preserved_files,
|
||||||
|
&mut cleanup_mount_points,
|
||||||
&unreadable_root,
|
&unreadable_root,
|
||||||
&allowed_write_paths,
|
&allowed_write_paths,
|
||||||
)?;
|
)?;
|
||||||
@@ -453,6 +477,7 @@ fn create_filesystem_args(
|
|||||||
append_unreadable_root_args(
|
append_unreadable_root_args(
|
||||||
&mut args,
|
&mut args,
|
||||||
&mut preserved_files,
|
&mut preserved_files,
|
||||||
|
&mut cleanup_mount_points,
|
||||||
&unreadable_root,
|
&unreadable_root,
|
||||||
&allowed_write_paths,
|
&allowed_write_paths,
|
||||||
)?;
|
)?;
|
||||||
@@ -461,6 +486,7 @@ fn create_filesystem_args(
|
|||||||
Ok(BwrapArgs {
|
Ok(BwrapArgs {
|
||||||
args,
|
args,
|
||||||
preserved_files,
|
preserved_files,
|
||||||
|
cleanup_mount_points,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -787,6 +813,7 @@ fn append_mount_target_parent_dir_args(args: &mut Vec<String>, mount_target: &Pa
|
|||||||
|
|
||||||
fn append_read_only_subpath_args(
|
fn append_read_only_subpath_args(
|
||||||
args: &mut Vec<String>,
|
args: &mut Vec<String>,
|
||||||
|
cleanup_mount_points: &mut Vec<PathBuf>,
|
||||||
subpath: &Path,
|
subpath: &Path,
|
||||||
allowed_write_paths: &[PathBuf],
|
allowed_write_paths: &[PathBuf],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -808,9 +835,7 @@ fn append_read_only_subpath_args(
|
|||||||
if let Some(first_missing_component) = find_first_non_existent_component(subpath)
|
if let Some(first_missing_component) = find_first_non_existent_component(subpath)
|
||||||
&& is_within_allowed_write_paths(&first_missing_component, allowed_write_paths)
|
&& is_within_allowed_write_paths(&first_missing_component, allowed_write_paths)
|
||||||
{
|
{
|
||||||
args.push("--ro-bind".to_string());
|
append_missing_path_mask_args(args, cleanup_mount_points, &first_missing_component);
|
||||||
args.push("/dev/null".to_string());
|
|
||||||
args.push(path_to_string(&first_missing_component));
|
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -823,9 +848,61 @@ fn append_read_only_subpath_args(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn track_cleanup_mount_point(cleanup_mount_points: &mut Vec<PathBuf>, mount_point: &Path) {
|
||||||
|
if cleanup_mount_points
|
||||||
|
.iter()
|
||||||
|
.any(|existing| existing == mount_point)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cleanup_mount_points.push(mount_point.to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_explicit_write_entry_for_path(policy: &FileSystemSandboxPolicy, path: &Path) -> bool {
|
||||||
|
let Ok(path) = AbsolutePathBuf::from_absolute_path(path) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
policy.entries.iter().any(|entry| {
|
||||||
|
entry.access == FileSystemAccessMode::Write
|
||||||
|
&& matches!(&entry.path, FileSystemPath::Path { path: existing } if existing == &path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_empty_file_mask_args(
|
||||||
|
args: &mut Vec<String>,
|
||||||
|
preserved_files: &mut Vec<File>,
|
||||||
|
path: &Path,
|
||||||
|
) -> Result<()> {
|
||||||
|
if preserved_files.is_empty() {
|
||||||
|
preserved_files.push(File::open("/dev/null")?);
|
||||||
|
}
|
||||||
|
let null_fd = preserved_files[0].as_raw_fd().to_string();
|
||||||
|
args.push("--perms".to_string());
|
||||||
|
args.push("000".to_string());
|
||||||
|
args.push("--ro-bind-data".to_string());
|
||||||
|
args.push(null_fd);
|
||||||
|
args.push(path_to_string(path));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_missing_path_mask_args(
|
||||||
|
args: &mut Vec<String>,
|
||||||
|
cleanup_mount_points: &mut Vec<PathBuf>,
|
||||||
|
mount_point: &Path,
|
||||||
|
) {
|
||||||
|
args.push("--perms".to_string());
|
||||||
|
args.push("000".to_string());
|
||||||
|
args.push("--tmpfs".to_string());
|
||||||
|
args.push(path_to_string(mount_point));
|
||||||
|
args.push("--remount-ro".to_string());
|
||||||
|
args.push(path_to_string(mount_point));
|
||||||
|
track_cleanup_mount_point(cleanup_mount_points, mount_point);
|
||||||
|
}
|
||||||
|
|
||||||
fn append_unreadable_root_args(
|
fn append_unreadable_root_args(
|
||||||
args: &mut Vec<String>,
|
args: &mut Vec<String>,
|
||||||
preserved_files: &mut Vec<File>,
|
preserved_files: &mut Vec<File>,
|
||||||
|
cleanup_mount_points: &mut Vec<PathBuf>,
|
||||||
unreadable_root: &Path,
|
unreadable_root: &Path,
|
||||||
allowed_write_paths: &[PathBuf],
|
allowed_write_paths: &[PathBuf],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -850,9 +927,7 @@ fn append_unreadable_root_args(
|
|||||||
if let Some(first_missing_component) = find_first_non_existent_component(unreadable_root)
|
if let Some(first_missing_component) = find_first_non_existent_component(unreadable_root)
|
||||||
&& is_within_allowed_write_paths(&first_missing_component, allowed_write_paths)
|
&& is_within_allowed_write_paths(&first_missing_component, allowed_write_paths)
|
||||||
{
|
{
|
||||||
args.push("--ro-bind".to_string());
|
append_missing_path_mask_args(args, cleanup_mount_points, &first_missing_component);
|
||||||
args.push("/dev/null".to_string());
|
|
||||||
args.push(path_to_string(&first_missing_component));
|
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -901,16 +976,7 @@ fn append_existing_unreadable_path_args(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if preserved_files.is_empty() {
|
append_empty_file_mask_args(args, preserved_files, unreadable_root)
|
||||||
preserved_files.push(File::open("/dev/null")?);
|
|
||||||
}
|
|
||||||
let null_fd = preserved_files[0].as_raw_fd().to_string();
|
|
||||||
args.push("--perms".to_string());
|
|
||||||
args.push("000".to_string());
|
|
||||||
args.push("--ro-bind-data".to_string());
|
|
||||||
args.push(null_fd);
|
|
||||||
args.push(path_to_string(unreadable_root));
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true when `path` is under any allowed writable root.
|
/// Returns true when `path` is under any allowed writable root.
|
||||||
@@ -1359,6 +1425,132 @@ mod tests {
|
|||||||
assert!(message.contains(&real_linked_private_str), "{message}");
|
assert!(message.contains(&real_linked_private_str), "{message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_default_metadata_paths_use_tmpfs_mask() {
|
||||||
|
let temp_dir = TempDir::new().expect("temp dir");
|
||||||
|
let workspace = temp_dir.path().join("workspace");
|
||||||
|
std::fs::create_dir_all(&workspace).expect("create workspace");
|
||||||
|
|
||||||
|
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||||
|
path: FileSystemPath::Path {
|
||||||
|
path: AbsolutePathBuf::from_absolute_path(&workspace).expect("absolute workspace"),
|
||||||
|
},
|
||||||
|
access: FileSystemAccessMode::Write,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
let args = create_filesystem_args(&policy, &workspace, NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH)
|
||||||
|
.expect("filesystem args");
|
||||||
|
|
||||||
|
assert_missing_path_masked(&args.args, &workspace.join(".codex"));
|
||||||
|
assert_missing_path_masked(&args.args, &workspace.join(".git"));
|
||||||
|
assert!(args.preserved_files.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
args.cleanup_mount_points,
|
||||||
|
vec![workspace.join(".codex"), workspace.join(".git")]
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!workspace.join(".codex").exists() && !workspace.join(".git").exists(),
|
||||||
|
"tmpfs mask should not materialize host side metadata paths at arg construction time",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_default_git_path_respects_explicit_write_entry() {
|
||||||
|
let temp_dir = TempDir::new().expect("temp dir");
|
||||||
|
let workspace = temp_dir.path().join("workspace");
|
||||||
|
let dot_git = workspace.join(".git");
|
||||||
|
std::fs::create_dir_all(&workspace).expect("create workspace");
|
||||||
|
|
||||||
|
let policy = FileSystemSandboxPolicy::restricted(vec![
|
||||||
|
FileSystemSandboxEntry {
|
||||||
|
path: FileSystemPath::Path {
|
||||||
|
path: AbsolutePathBuf::from_absolute_path(&workspace)
|
||||||
|
.expect("absolute workspace"),
|
||||||
|
},
|
||||||
|
access: FileSystemAccessMode::Write,
|
||||||
|
},
|
||||||
|
FileSystemSandboxEntry {
|
||||||
|
path: FileSystemPath::Path {
|
||||||
|
path: AbsolutePathBuf::from_absolute_path(&dot_git).expect("absolute .git"),
|
||||||
|
},
|
||||||
|
access: FileSystemAccessMode::Write,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
let args = create_filesystem_args(&policy, &workspace, NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH)
|
||||||
|
.expect("filesystem args");
|
||||||
|
|
||||||
|
assert_missing_path_masked(&args.args, &workspace.join(".codex"));
|
||||||
|
assert!(
|
||||||
|
!args.args.iter().any(|arg| arg == &path_to_string(&dot_git)),
|
||||||
|
"explicitly writable missing .git path should not be masked: {:#?}",
|
||||||
|
args.args
|
||||||
|
);
|
||||||
|
assert_eq!(args.cleanup_mount_points, vec![workspace.join(".codex")]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_read_only_subpath_uses_tmpfs_mask() {
|
||||||
|
let temp_dir = TempDir::new().expect("temp dir");
|
||||||
|
let workspace = temp_dir.path().join("workspace");
|
||||||
|
let blocked = workspace.join("blocked");
|
||||||
|
std::fs::create_dir_all(&workspace).expect("create workspace");
|
||||||
|
|
||||||
|
let workspace_root =
|
||||||
|
AbsolutePathBuf::from_absolute_path(&workspace).expect("absolute workspace");
|
||||||
|
let blocked_root = AbsolutePathBuf::from_absolute_path(&blocked).expect("absolute blocked");
|
||||||
|
let policy = FileSystemSandboxPolicy::restricted(vec![
|
||||||
|
FileSystemSandboxEntry {
|
||||||
|
path: FileSystemPath::Path {
|
||||||
|
path: workspace_root,
|
||||||
|
},
|
||||||
|
access: FileSystemAccessMode::Write,
|
||||||
|
},
|
||||||
|
FileSystemSandboxEntry {
|
||||||
|
path: FileSystemPath::Path { path: blocked_root },
|
||||||
|
access: FileSystemAccessMode::Read,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
let args =
|
||||||
|
create_filesystem_args(&policy, temp_dir.path(), NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH)
|
||||||
|
.expect("filesystem args");
|
||||||
|
|
||||||
|
assert_missing_path_masked(&args.args, &blocked);
|
||||||
|
assert_eq!(args.cleanup_mount_points, vec![blocked]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_unreadable_path_uses_tmpfs_mask() {
|
||||||
|
let temp_dir = TempDir::new().expect("temp dir");
|
||||||
|
let workspace = temp_dir.path().join("workspace");
|
||||||
|
let secret = workspace.join("secret");
|
||||||
|
std::fs::create_dir_all(&workspace).expect("create workspace");
|
||||||
|
|
||||||
|
let workspace_root =
|
||||||
|
AbsolutePathBuf::from_absolute_path(&workspace).expect("absolute workspace");
|
||||||
|
let secret_root = AbsolutePathBuf::from_absolute_path(&secret).expect("absolute secret");
|
||||||
|
let policy = FileSystemSandboxPolicy::restricted(vec![
|
||||||
|
FileSystemSandboxEntry {
|
||||||
|
path: FileSystemPath::Path {
|
||||||
|
path: workspace_root,
|
||||||
|
},
|
||||||
|
access: FileSystemAccessMode::Write,
|
||||||
|
},
|
||||||
|
FileSystemSandboxEntry {
|
||||||
|
path: FileSystemPath::Path { path: secret_root },
|
||||||
|
access: FileSystemAccessMode::None,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
let args =
|
||||||
|
create_filesystem_args(&policy, temp_dir.path(), NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH)
|
||||||
|
.expect("filesystem args");
|
||||||
|
|
||||||
|
assert_missing_path_masked(&args.args, &secret);
|
||||||
|
assert_eq!(args.cleanup_mount_points, vec![secret]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ignores_missing_writable_roots() {
|
fn ignores_missing_writable_roots() {
|
||||||
let temp_dir = TempDir::new().expect("temp dir");
|
let temp_dir = TempDir::new().expect("temp dir");
|
||||||
@@ -1393,8 +1585,10 @@ mod tests {
|
|||||||
"existing writable root should be rebound writable",
|
"existing writable root should be rebound writable",
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
!args.args.iter().any(|arg| arg == &missing_root),
|
!args.args.windows(3).any(|window| {
|
||||||
"missing writable root should be skipped",
|
window == ["--bind", missing_root.as_str(), missing_root.as_str()]
|
||||||
|
}),
|
||||||
|
"missing writable root should not be rebound writable",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1414,6 +1608,11 @@ mod tests {
|
|||||||
NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH,
|
NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH,
|
||||||
)
|
)
|
||||||
.expect("bwrap fs args");
|
.expect("bwrap fs args");
|
||||||
|
assert!(args.preserved_files.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
args.cleanup_mount_points,
|
||||||
|
vec![PathBuf::from("/.codex"), PathBuf::from("/.git")]
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
args.args,
|
args.args,
|
||||||
vec![
|
vec![
|
||||||
@@ -1430,10 +1629,19 @@ mod tests {
|
|||||||
"/".to_string(),
|
"/".to_string(),
|
||||||
// Mask the default protected .codex subpath under that writable
|
// Mask the default protected .codex subpath under that writable
|
||||||
// root. Because the root is `/` in this test, the carveout path
|
// root. Because the root is `/` in this test, the carveout path
|
||||||
// appears as `/.codex`.
|
// appears as `/.codex` and `/.git`.
|
||||||
"--ro-bind".to_string(),
|
"--perms".to_string(),
|
||||||
"/dev/null".to_string(),
|
"000".to_string(),
|
||||||
|
"--tmpfs".to_string(),
|
||||||
"/.codex".to_string(),
|
"/.codex".to_string(),
|
||||||
|
"--remount-ro".to_string(),
|
||||||
|
"/.codex".to_string(),
|
||||||
|
"--perms".to_string(),
|
||||||
|
"000".to_string(),
|
||||||
|
"--tmpfs".to_string(),
|
||||||
|
"/.git".to_string(),
|
||||||
|
"--remount-ro".to_string(),
|
||||||
|
"/.git".to_string(),
|
||||||
// Rebind /dev after the root bind so device nodes remain
|
// Rebind /dev after the root bind so device nodes remain
|
||||||
// writable/usable inside the writable root.
|
// writable/usable inside the writable root.
|
||||||
"--bind".to_string(),
|
"--bind".to_string(),
|
||||||
@@ -2018,4 +2226,25 @@ mod tests {
|
|||||||
"expected file mask for {path}: {args:#?}"
|
"expected file mask for {path}: {args:#?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Assert that `path` is masked due to a bwrap arg sequence like:
|
||||||
|
///
|
||||||
|
/// `bwrap ... --perms 000 --tmpfs PATH --remount-ro PATH`
|
||||||
|
fn assert_missing_path_masked(args: &[String], path: &Path) {
|
||||||
|
let path = path_to_string(path);
|
||||||
|
assert!(
|
||||||
|
args.windows(6).any(|window| {
|
||||||
|
window
|
||||||
|
== [
|
||||||
|
"--perms",
|
||||||
|
"000",
|
||||||
|
"--tmpfs",
|
||||||
|
path.as_str(),
|
||||||
|
"--remount-ro",
|
||||||
|
path.as_str(),
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
"expected missing path mask for {path}: {args:#?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -437,7 +437,7 @@ fn run_bwrap_with_proc_fallback(
|
|||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
apply_inner_command_argv0(&mut bwrap_args.args);
|
apply_inner_command_argv0(&mut bwrap_args.args);
|
||||||
exec_bwrap(bwrap_args.args, bwrap_args.preserved_files);
|
run_bwrap_command(bwrap_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bwrap_network_mode(
|
fn bwrap_network_mode(
|
||||||
@@ -474,9 +474,46 @@ fn build_bwrap_argv(
|
|||||||
crate::bwrap::BwrapArgs {
|
crate::bwrap::BwrapArgs {
|
||||||
args: argv,
|
args: argv,
|
||||||
preserved_files: bwrap_args.preserved_files,
|
preserved_files: bwrap_args.preserved_files,
|
||||||
|
cleanup_mount_points: bwrap_args.cleanup_mount_points,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_bwrap_command(bwrap_args: crate::bwrap::BwrapArgs) -> ! {
|
||||||
|
if bwrap_args.cleanup_mount_points.is_empty() {
|
||||||
|
exec_bwrap(bwrap_args.args, bwrap_args.preserved_files);
|
||||||
|
}
|
||||||
|
run_bwrap_in_child_then_cleanup(bwrap_args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_bwrap_in_child_then_cleanup(bwrap_args: crate::bwrap::BwrapArgs) -> ! {
|
||||||
|
let pid = unsafe { libc::fork() };
|
||||||
|
if pid < 0 {
|
||||||
|
let err = std::io::Error::last_os_error();
|
||||||
|
panic!("failed to fork for bubblewrap: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if pid == 0 {
|
||||||
|
exec_bwrap(bwrap_args.args, bwrap_args.preserved_files);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut status: libc::c_int = 0;
|
||||||
|
let wait_res = unsafe { libc::waitpid(pid, &mut status as *mut libc::c_int, 0) };
|
||||||
|
if wait_res < 0 {
|
||||||
|
let err = std::io::Error::last_os_error();
|
||||||
|
panic!("waitpid failed for bubblewrap child: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_bwrap_mount_points(&bwrap_args.cleanup_mount_points);
|
||||||
|
|
||||||
|
if libc::WIFEXITED(status) {
|
||||||
|
unsafe { libc::_exit(libc::WEXITSTATUS(status)) };
|
||||||
|
}
|
||||||
|
if libc::WIFSIGNALED(status) {
|
||||||
|
unsafe { libc::_exit(128 + libc::WTERMSIG(status)) };
|
||||||
|
}
|
||||||
|
unsafe { libc::_exit(1) };
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_inner_command_argv0(argv: &mut Vec<String>) {
|
fn apply_inner_command_argv0(argv: &mut Vec<String>) {
|
||||||
apply_inner_command_argv0_for_launcher(
|
apply_inner_command_argv0_for_launcher(
|
||||||
argv,
|
argv,
|
||||||
@@ -529,8 +566,8 @@ fn preflight_proc_mount_support(
|
|||||||
file_system_sandbox_policy,
|
file_system_sandbox_policy,
|
||||||
network_mode,
|
network_mode,
|
||||||
);
|
);
|
||||||
let stderr = run_bwrap_in_child_capture_stderr(preflight_argv);
|
let output = run_bwrap_in_child_capture_stderr(preflight_argv);
|
||||||
!is_proc_mount_failure(stderr.as_str())
|
!should_disable_proc_mount_after_preflight(output.status, output.stderr.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_preflight_bwrap_argv(
|
fn build_preflight_bwrap_argv(
|
||||||
@@ -573,7 +610,13 @@ fn resolve_true_command() -> String {
|
|||||||
/// - We capture stderr from that preflight to match known mount-failure text.
|
/// - We capture stderr from that preflight to match known mount-failure text.
|
||||||
/// We do not stream it because this is a one-shot probe with a trivial
|
/// We do not stream it because this is a one-shot probe with a trivial
|
||||||
/// command, and reads are bounded to a fixed max size.
|
/// command, and reads are bounded to a fixed max size.
|
||||||
fn run_bwrap_in_child_capture_stderr(bwrap_args: crate::bwrap::BwrapArgs) -> String {
|
#[derive(Debug)]
|
||||||
|
struct BwrapChildOutput {
|
||||||
|
stderr: String,
|
||||||
|
status: libc::c_int,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_bwrap_in_child_capture_stderr(bwrap_args: crate::bwrap::BwrapArgs) -> BwrapChildOutput {
|
||||||
const MAX_PREFLIGHT_STDERR_BYTES: u64 = 64 * 1024;
|
const MAX_PREFLIGHT_STDERR_BYTES: u64 = 64 * 1024;
|
||||||
|
|
||||||
let mut pipe_fds = [0; 2];
|
let mut pipe_fds = [0; 2];
|
||||||
@@ -623,7 +666,42 @@ fn run_bwrap_in_child_capture_stderr(bwrap_args: crate::bwrap::BwrapArgs) -> Str
|
|||||||
panic!("waitpid failed for bubblewrap child: {err}");
|
panic!("waitpid failed for bubblewrap child: {err}");
|
||||||
}
|
}
|
||||||
|
|
||||||
String::from_utf8_lossy(&stderr_bytes).into_owned()
|
cleanup_bwrap_mount_points(&bwrap_args.cleanup_mount_points);
|
||||||
|
|
||||||
|
BwrapChildOutput {
|
||||||
|
stderr: String::from_utf8_lossy(&stderr_bytes).into_owned(),
|
||||||
|
status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_disable_proc_mount_after_preflight(_status: libc::c_int, stderr: &str) -> bool {
|
||||||
|
is_proc_mount_failure(stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_bwrap_mount_points(mount_points: &[PathBuf]) {
|
||||||
|
for mount_point in mount_points {
|
||||||
|
remove_bwrap_mount_point_if_safe(mount_point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_bwrap_mount_point_if_safe(mount_point: &Path) {
|
||||||
|
let Ok(metadata) = std::fs::symlink_metadata(mount_point) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if metadata.file_type().is_file() && metadata.len() == 0 {
|
||||||
|
let _ = std::fs::remove_file(mount_point);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.file_type().is_dir() {
|
||||||
|
let Ok(mut entries) = std::fs::read_dir(mount_point) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if entries.next().is_none() {
|
||||||
|
let _ = std::fs::remove_dir(mount_point);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close an owned file descriptor and panic with context on failure.
|
/// Close an owned file descriptor and panic with context on failure.
|
||||||
|
|||||||
@@ -37,6 +37,52 @@ fn ignores_non_proc_mount_errors() {
|
|||||||
assert!(!is_proc_mount_failure(stderr));
|
assert!(!is_proc_mount_failure(stderr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_proc_preflight_failure_keeps_proc_mount_enabled() {
|
||||||
|
let status = wait_status_for_exit_code(/*exit_code*/ 1);
|
||||||
|
let stderr = "bwrap: Can't bind mount /dev/null: Operation not permitted";
|
||||||
|
|
||||||
|
assert!(!should_disable_proc_mount_after_preflight(status, stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cleanup_removes_empty_synthetic_mount_point() {
|
||||||
|
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||||
|
let git_path = temp_dir.path().join(".git");
|
||||||
|
File::create(&git_path).expect("create empty git file");
|
||||||
|
|
||||||
|
remove_bwrap_mount_point_if_safe(&git_path);
|
||||||
|
|
||||||
|
assert!(!git_path.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cleanup_preserves_real_git_file() {
|
||||||
|
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||||
|
let git_path = temp_dir.path().join(".git");
|
||||||
|
std::fs::write(&git_path, "gitdir: /tmp/worktree\n").expect("write git file");
|
||||||
|
|
||||||
|
remove_bwrap_mount_point_if_safe(&git_path);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read_to_string(&git_path).expect("read git file"),
|
||||||
|
"gitdir: /tmp/worktree\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cleanup_preserves_nonempty_git_directory() {
|
||||||
|
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||||
|
let git_path = temp_dir.path().join(".git");
|
||||||
|
std::fs::create_dir(&git_path).expect("create git dir");
|
||||||
|
std::fs::write(git_path.join("config"), "[core]\n").expect("write git config");
|
||||||
|
|
||||||
|
remove_bwrap_mount_point_if_safe(&git_path);
|
||||||
|
|
||||||
|
assert!(git_path.exists());
|
||||||
|
assert!(git_path.join("config").exists());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn inserts_bwrap_argv0_before_command_separator() {
|
fn inserts_bwrap_argv0_before_command_separator() {
|
||||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||||
@@ -536,3 +582,8 @@ fn valid_inner_stage_modes_do_not_panic() {
|
|||||||
/*apply_seccomp_then_exec*/ true, /*use_legacy_landlock*/ false,
|
/*apply_seccomp_then_exec*/ true, /*use_legacy_landlock*/ false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn wait_status_for_exit_code(exit_code: libc::c_int) -> libc::c_int {
|
||||||
|
exit_code << 8
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,6 +48,19 @@ fn create_env_from_core_vars() -> HashMap<String, String> {
|
|||||||
create_env(&policy, /*thread_id*/ None)
|
create_env(&policy, /*thread_id*/ None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn codex_linux_sandbox_exe() -> PathBuf {
|
||||||
|
let sandbox_program = PathBuf::from(env!("CARGO_BIN_EXE_codex-linux-sandbox"));
|
||||||
|
if sandbox_program.is_absolute() {
|
||||||
|
return sandbox_program;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(current_dir) = std::env::current_dir() {
|
||||||
|
current_dir.join(sandbox_program)
|
||||||
|
} else {
|
||||||
|
sandbox_program
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[expect(clippy::print_stdout)]
|
#[expect(clippy::print_stdout)]
|
||||||
async fn run_cmd(cmd: &[&str], writable_roots: &[PathBuf], timeout_ms: u64) {
|
async fn run_cmd(cmd: &[&str], writable_roots: &[PathBuf], timeout_ms: u64) {
|
||||||
let output = run_cmd_output(cmd, writable_roots, timeout_ms).await;
|
let output = run_cmd_output(cmd, writable_roots, timeout_ms).await;
|
||||||
@@ -75,12 +88,32 @@ async fn run_cmd_output(
|
|||||||
.expect("sandboxed command should execute")
|
.expect("sandboxed command should execute")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::expect_used)]
|
||||||
async fn run_cmd_result_with_writable_roots(
|
async fn run_cmd_result_with_writable_roots(
|
||||||
cmd: &[&str],
|
cmd: &[&str],
|
||||||
writable_roots: &[PathBuf],
|
writable_roots: &[PathBuf],
|
||||||
timeout_ms: u64,
|
timeout_ms: u64,
|
||||||
use_legacy_landlock: bool,
|
use_legacy_landlock: bool,
|
||||||
network_access: bool,
|
network_access: bool,
|
||||||
|
) -> Result<codex_protocol::exec_output::ExecToolCallOutput> {
|
||||||
|
run_cmd_result_with_writable_roots_and_cwd(
|
||||||
|
cmd,
|
||||||
|
writable_roots,
|
||||||
|
&AbsolutePathBuf::current_dir().expect("cwd should exist"),
|
||||||
|
timeout_ms,
|
||||||
|
use_legacy_landlock,
|
||||||
|
network_access,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_cmd_result_with_writable_roots_and_cwd(
|
||||||
|
cmd: &[&str],
|
||||||
|
writable_roots: &[PathBuf],
|
||||||
|
cwd: &AbsolutePathBuf,
|
||||||
|
timeout_ms: u64,
|
||||||
|
use_legacy_landlock: bool,
|
||||||
|
network_access: bool,
|
||||||
) -> Result<codex_protocol::exec_output::ExecToolCallOutput> {
|
) -> Result<codex_protocol::exec_output::ExecToolCallOutput> {
|
||||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||||
writable_roots: writable_roots
|
writable_roots: writable_roots
|
||||||
@@ -102,26 +135,26 @@ async fn run_cmd_result_with_writable_roots(
|
|||||||
sandbox_policy,
|
sandbox_policy,
|
||||||
file_system_sandbox_policy,
|
file_system_sandbox_policy,
|
||||||
network_sandbox_policy,
|
network_sandbox_policy,
|
||||||
|
cwd,
|
||||||
timeout_ms,
|
timeout_ms,
|
||||||
use_legacy_landlock,
|
use_legacy_landlock,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::expect_used)]
|
|
||||||
async fn run_cmd_result_with_policies(
|
async fn run_cmd_result_with_policies(
|
||||||
cmd: &[&str],
|
cmd: &[&str],
|
||||||
sandbox_policy: SandboxPolicy,
|
sandbox_policy: SandboxPolicy,
|
||||||
file_system_sandbox_policy: FileSystemSandboxPolicy,
|
file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||||
network_sandbox_policy: NetworkSandboxPolicy,
|
network_sandbox_policy: NetworkSandboxPolicy,
|
||||||
|
cwd: &AbsolutePathBuf,
|
||||||
timeout_ms: u64,
|
timeout_ms: u64,
|
||||||
use_legacy_landlock: bool,
|
use_legacy_landlock: bool,
|
||||||
) -> Result<codex_protocol::exec_output::ExecToolCallOutput> {
|
) -> Result<codex_protocol::exec_output::ExecToolCallOutput> {
|
||||||
let cwd = AbsolutePathBuf::current_dir().expect("cwd should exist");
|
|
||||||
let sandbox_cwd = cwd.clone();
|
let sandbox_cwd = cwd.clone();
|
||||||
let params = ExecParams {
|
let params = ExecParams {
|
||||||
command: cmd.iter().copied().map(str::to_owned).collect(),
|
command: cmd.iter().copied().map(str::to_owned).collect(),
|
||||||
cwd,
|
cwd: cwd.clone(),
|
||||||
expiration: timeout_ms.into(),
|
expiration: timeout_ms.into(),
|
||||||
capture_policy: ExecCapturePolicy::ShellTool,
|
capture_policy: ExecCapturePolicy::ShellTool,
|
||||||
env: create_env_from_core_vars(),
|
env: create_env_from_core_vars(),
|
||||||
@@ -132,8 +165,7 @@ async fn run_cmd_result_with_policies(
|
|||||||
justification: None,
|
justification: None,
|
||||||
arg0: None,
|
arg0: None,
|
||||||
};
|
};
|
||||||
let sandbox_program = env!("CARGO_BIN_EXE_codex-linux-sandbox");
|
let codex_linux_sandbox_exe = Some(codex_linux_sandbox_exe());
|
||||||
let codex_linux_sandbox_exe = Some(PathBuf::from(sandbox_program));
|
|
||||||
|
|
||||||
process_exec_tool_call(
|
process_exec_tool_call(
|
||||||
params,
|
params,
|
||||||
@@ -394,8 +426,7 @@ async fn assert_network_blocked(cmd: &[&str]) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||||
let sandbox_program = env!("CARGO_BIN_EXE_codex-linux-sandbox");
|
let codex_linux_sandbox_exe: Option<PathBuf> = Some(codex_linux_sandbox_exe());
|
||||||
let codex_linux_sandbox_exe: Option<PathBuf> = Some(PathBuf::from(sandbox_program));
|
|
||||||
let result = process_exec_tool_call(
|
let result = process_exec_tool_call(
|
||||||
params,
|
params,
|
||||||
&sandbox_policy,
|
&sandbox_policy,
|
||||||
@@ -505,6 +536,50 @@ async fn sandbox_blocks_git_and_codex_writes_inside_writable_root() {
|
|||||||
assert_ne!(codex_output.exit_code, 0);
|
assert_ne!(codex_output.exit_code, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn sandbox_blocks_missing_git_creation_without_host_artifact() {
|
||||||
|
if should_skip_bwrap_tests().await {
|
||||||
|
eprintln!("skipping bwrap test: bwrap sandbox prerequisites are unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tmpdir = tempfile::tempdir().expect("tempdir");
|
||||||
|
let sandbox_cwd =
|
||||||
|
AbsolutePathBuf::try_from(tmpdir.path()).expect("tempdir should be an absolute cwd");
|
||||||
|
let allowed_target = tmpdir.path().join("allowed.txt");
|
||||||
|
let git_path = tmpdir.path().join(".git");
|
||||||
|
|
||||||
|
let output = expect_denied(
|
||||||
|
run_cmd_result_with_writable_roots_and_cwd(
|
||||||
|
&[
|
||||||
|
"bash",
|
||||||
|
"-lc",
|
||||||
|
&format!(
|
||||||
|
"printf allowed > {} && git init -q",
|
||||||
|
allowed_target.to_string_lossy(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
&[tmpdir.path().to_path_buf()],
|
||||||
|
&sandbox_cwd,
|
||||||
|
LONG_TIMEOUT_MS,
|
||||||
|
/*use_legacy_landlock*/ false,
|
||||||
|
/*network_access*/ true,
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
"missing .git should stay blocked under bubblewrap",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_ne!(output.exit_code, 0);
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read_to_string(&allowed_target).expect("read allowed write target"),
|
||||||
|
"allowed",
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!git_path.exists(),
|
||||||
|
"sandbox should not materialize host side .git when the path is missing",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn sandbox_blocks_codex_symlink_replacement_attack() {
|
async fn sandbox_blocks_codex_symlink_replacement_attack() {
|
||||||
if should_skip_bwrap_tests().await {
|
if should_skip_bwrap_tests().await {
|
||||||
@@ -554,7 +629,7 @@ async fn sandbox_blocks_explicit_split_policy_carveouts_under_bwrap() {
|
|||||||
let blocked_target = blocked.join("secret.txt");
|
let blocked_target = blocked.join("secret.txt");
|
||||||
// These tests bypass the usual legacy-policy bridge, so explicitly keep
|
// These tests bypass the usual legacy-policy bridge, so explicitly keep
|
||||||
// the sandbox helper binary and minimal runtime paths readable.
|
// the sandbox helper binary and minimal runtime paths readable.
|
||||||
let sandbox_helper_dir = PathBuf::from(env!("CARGO_BIN_EXE_codex-linux-sandbox"))
|
let sandbox_helper_dir = codex_linux_sandbox_exe()
|
||||||
.parent()
|
.parent()
|
||||||
.expect("sandbox helper should have a parent")
|
.expect("sandbox helper should have a parent")
|
||||||
.to_path_buf();
|
.to_path_buf();
|
||||||
@@ -603,6 +678,7 @@ async fn sandbox_blocks_explicit_split_policy_carveouts_under_bwrap() {
|
|||||||
sandbox_policy,
|
sandbox_policy,
|
||||||
file_system_sandbox_policy,
|
file_system_sandbox_policy,
|
||||||
NetworkSandboxPolicy::Enabled,
|
NetworkSandboxPolicy::Enabled,
|
||||||
|
&AbsolutePathBuf::current_dir().expect("cwd should exist"),
|
||||||
LONG_TIMEOUT_MS,
|
LONG_TIMEOUT_MS,
|
||||||
/*use_legacy_landlock*/ false,
|
/*use_legacy_landlock*/ false,
|
||||||
)
|
)
|
||||||
@@ -627,7 +703,7 @@ async fn sandbox_reenables_writable_subpaths_under_unreadable_parents() {
|
|||||||
let allowed_target = allowed.join("note.txt");
|
let allowed_target = allowed.join("note.txt");
|
||||||
// These tests bypass the usual legacy-policy bridge, so explicitly keep
|
// These tests bypass the usual legacy-policy bridge, so explicitly keep
|
||||||
// the sandbox helper binary and minimal runtime paths readable.
|
// the sandbox helper binary and minimal runtime paths readable.
|
||||||
let sandbox_helper_dir = PathBuf::from(env!("CARGO_BIN_EXE_codex-linux-sandbox"))
|
let sandbox_helper_dir = codex_linux_sandbox_exe()
|
||||||
.parent()
|
.parent()
|
||||||
.expect("sandbox helper should have a parent")
|
.expect("sandbox helper should have a parent")
|
||||||
.to_path_buf();
|
.to_path_buf();
|
||||||
@@ -685,6 +761,7 @@ async fn sandbox_reenables_writable_subpaths_under_unreadable_parents() {
|
|||||||
sandbox_policy,
|
sandbox_policy,
|
||||||
file_system_sandbox_policy,
|
file_system_sandbox_policy,
|
||||||
NetworkSandboxPolicy::Enabled,
|
NetworkSandboxPolicy::Enabled,
|
||||||
|
&AbsolutePathBuf::current_dir().expect("cwd should exist"),
|
||||||
LONG_TIMEOUT_MS,
|
LONG_TIMEOUT_MS,
|
||||||
/*use_legacy_landlock*/ false,
|
/*use_legacy_landlock*/ false,
|
||||||
)
|
)
|
||||||
@@ -736,6 +813,7 @@ async fn sandbox_blocks_root_read_carveouts_under_bwrap() {
|
|||||||
sandbox_policy,
|
sandbox_policy,
|
||||||
file_system_sandbox_policy,
|
file_system_sandbox_policy,
|
||||||
NetworkSandboxPolicy::Enabled,
|
NetworkSandboxPolicy::Enabled,
|
||||||
|
&AbsolutePathBuf::current_dir().expect("cwd should exist"),
|
||||||
LONG_TIMEOUT_MS,
|
LONG_TIMEOUT_MS,
|
||||||
/*use_legacy_landlock*/ false,
|
/*use_legacy_landlock*/ false,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user