Default to whitelist mode and parallelize tests

Flips the default sandbox mode from blacklist to whitelist and
replaces the global RUST_TEST_THREADS=1 with a targeted RwLock
that only serializes blacklist sandboxes against tests mutating
glob-matching host paths. A new Sandbox newtype acquires the
guard automatically when --blacklist is in args.
This commit is contained in:
2026-04-27 08:18:41 +02:00
parent c77dbc10c3
commit 6e81866226
12 changed files with 158 additions and 81 deletions
+20 -18
View File
@@ -16,7 +16,7 @@ impl Drop for CleanupFile {
fn cwd_is_writable() {
let _cleanup = CleanupFile("./sandbox_canary");
let output = sandbox(&[])
let output = Sandbox::new(&[])
.args(["--", "bash", "-c", "touch ./sandbox_canary && echo ok"])
.output()
.expect("agent-sandbox binary failed to execute");
@@ -30,7 +30,7 @@ fn cwd_is_writable() {
#[test]
fn host_fs_is_readonly() {
let output = sandbox(&[])
let output = Sandbox::new(&[])
.args(["--", "bash", "-c", "touch /etc/pwned 2>&1 || echo readonly"])
.output()
.expect("agent-sandbox binary failed to execute");
@@ -45,7 +45,7 @@ fn host_fs_is_readonly() {
#[test]
fn ssh_dir_is_hidden() {
let output = sandbox(&[])
let output = Sandbox::new(&[])
.args(["--", "bash", "-c", "ls ~/.ssh 2>/dev/null | wc -l"])
.output()
.expect("agent-sandbox binary failed to execute");
@@ -60,7 +60,7 @@ fn extra_ro_mount() {
fs::write(dir.path().join("hello.txt"), "hi").expect("failed to write test file");
let dir_str = dir.path().to_str().unwrap();
let output = sandbox(&["--ro", dir_str])
let output = Sandbox::new(&["--ro", dir_str])
.args([
"--",
"bash",
@@ -86,7 +86,7 @@ fn extra_rw_mount() {
let dir = TempDir::new().expect("failed to create temp dir");
let dir_str = dir.path().to_str().unwrap();
let output = sandbox(&["--rw", dir_str])
let output = Sandbox::new(&["--rw", dir_str])
.args([
"--",
"bash",
@@ -111,7 +111,7 @@ fn ro_mount_with_remapped_target() {
let src_str = dir.path().to_str().unwrap();
let target = "/tmp/agent-sandbox-remap-test";
let output = sandbox(&["--ro", &format!("{src_str}:{target}")])
let output = Sandbox::new(&["--ro", &format!("{src_str}:{target}")])
.args([
"--",
"bash",
@@ -138,7 +138,7 @@ fn rw_refines_ro_parent() {
let parent_str = parent.path().to_str().unwrap();
let child_str = child.to_str().unwrap();
let output = sandbox(&["--ro", parent_str, "--rw", child_str])
let output = Sandbox::new(&["--ro", parent_str, "--rw", child_str])
.args([
"--",
"bash",
@@ -164,10 +164,11 @@ fn rw_refines_ro_parent() {
#[test]
fn blacklist_overlays_survive_tmp_bind() {
let mut sandbox = Sandbox::new_for_host_mutation(&["--blacklist"]);
fs::write("/tmp/ssh-sandbox-test", "secret").expect("failed to write sentinel");
let _cleanup = CleanupFile("/tmp/ssh-sandbox-test");
let output = sandbox(&[])
let output = sandbox
.args([
"--",
"bash",
@@ -190,7 +191,7 @@ fn blacklist_overlays_survive_tmp_bind() {
#[test]
fn relative_chdir_works() {
let output = sandbox(&["--chdir", "src"])
let output = Sandbox::new(&["--chdir", "src"])
.args(["--", "bash", "-c", "pwd"])
.output()
.expect("agent-sandbox binary failed to execute");
@@ -209,7 +210,7 @@ fn relative_chdir_works() {
#[test]
fn relative_rw_path_works() {
let output = sandbox(&["--rw", "src"])
let output = Sandbox::new(&["--rw", "src"])
.args(["--", "bash", "-c", "echo ok"])
.output()
.expect("agent-sandbox binary failed to execute");
@@ -223,7 +224,7 @@ fn relative_rw_path_works() {
#[test]
fn relative_ro_path_works() {
let output = sandbox(&["--ro", "src"])
let output = Sandbox::new(&["--ro", "src"])
.args(["--", "bash", "-c", "echo ok"])
.output()
.expect("agent-sandbox binary failed to execute");
@@ -237,7 +238,7 @@ fn relative_ro_path_works() {
#[test]
fn rw_missing_path_errors() {
let output = sandbox(&["--rw", "/nonexistent/xyz"])
let output = Sandbox::new(&["--rw", "/nonexistent/xyz"])
.args(["--", "true"])
.output()
.expect("agent-sandbox binary failed to execute");
@@ -259,7 +260,7 @@ fn mask_hides_directory() {
fs::write(dir.path().join("secret.txt"), "sensitive").expect("failed to write");
let dir_str = dir.path().canonicalize().unwrap();
let output = sandbox(&["--mask", dir_str.to_str().unwrap()])
let output = Sandbox::new(&["--mask", dir_str.to_str().unwrap()])
.args([
"--",
"bash",
@@ -283,7 +284,7 @@ fn mask_hides_file() {
fs::write(&file, "sensitive").expect("failed to write");
let file_str = file.canonicalize().unwrap();
let output = sandbox(&["--mask", file_str.to_str().unwrap()])
let output = Sandbox::new(&["--mask", file_str.to_str().unwrap()])
.args([
"--",
"bash",
@@ -309,7 +310,7 @@ fn whitelist_ro_symlink_visible_at_link_path() {
std::os::unix::fs::symlink(&target, &link).expect("failed to create symlink");
let link_str = link.to_str().unwrap();
let output = sandbox(&["--whitelist", "--ro", link_str])
let output = Sandbox::new(&["--whitelist", "--ro", link_str])
.args(["--", "cat", link_str])
.output()
.expect("agent-sandbox binary failed to execute");
@@ -328,7 +329,7 @@ fn mask_nonexistent_path_becomes_tmpfs() {
let fake = dir.path().join("does_not_exist");
let fake_str = fake.to_str().unwrap();
let output = sandbox(&["--mask", fake_str])
let output = Sandbox::new(&["--mask", fake_str])
.args([
"--",
"bash",
@@ -357,7 +358,8 @@ fn blacklist_overlays_survive_absolute_var_run_symlink() {
// like --tmpfs /var/run/dbus trip bwrap's re-rooted symlink resolution.
// Arch ships /var/run -> ../run (relative) so we synthesize the absolute
// layout inside the sandbox to reproduce on any host.
let mut bwrap_args = build_bwrap_command(&["--no-seccomp", "--", "true"]);
let _guard = HostGlobsLock::for_scan();
let mut bwrap_args = build_bwrap_command(&["--blacklist", "--no-seccomp", "--", "true"]);
inject_absolute_var_run_symlink(&mut bwrap_args);
let output = Command::new(&bwrap_args[0])
@@ -373,7 +375,7 @@ fn blacklist_overlays_survive_absolute_var_run_symlink() {
);
}
fn build_bwrap_command(sandbox_args: &[&str]) -> Vec<String> {
let output = sandbox(&["--dry-run"])
let output = Sandbox::new(&["--dry-run"])
.args(sandbox_args)
.output()
.expect("agent-sandbox binary failed to execute");