From 6e81866226256d3e45621ab95da52273872fccc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20T=C3=B3th?= Date: Mon, 27 Apr 2026 08:18:41 +0200 Subject: [PATCH] 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. --- .cargo/config.toml | 2 - AGENTS.md | 2 +- src/cli.rs | 4 +- src/config.rs | 2 +- tests/e2e/cli.rs | 28 +++++++------- tests/e2e/common.rs | 85 +++++++++++++++++++++++++++++++++++++---- tests/e2e/env.rs | 24 ++++++++---- tests/e2e/modes.rs | 20 +++++----- tests/e2e/mounts.rs | 38 +++++++++--------- tests/e2e/namespaces.rs | 16 ++++---- tests/e2e/seccomp.rs | 16 ++++---- tests/unit/config.rs | 2 +- 12 files changed, 158 insertions(+), 81 deletions(-) delete mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 8af59dd..0000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[env] -RUST_TEST_THREADS = "1" diff --git a/AGENTS.md b/AGENTS.md index 2cbedd6..96d1f44 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ The config file may set `extra-config = ""` to layer a second fil ## Build and test - `cargo fmt` and `cargo clippy` must pass before every commit. -- `cargo test` runs all integration tests. Tests run serially (configured in `.cargo/config.toml`) because they spawn real bwrap sandboxes that share host paths like `/tmp`. +- `cargo test` runs all test cases. - Never add Co-Authored-By lines to commits. ## Things that will bite you diff --git a/src/cli.rs b/src/cli.rs index 45f6c5b..39bbdcf 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -10,11 +10,11 @@ use clap::Parser; about = "Sandbox agentic coding assistants with bubblewrap" )] pub struct Args { - /// Blacklist mode: bind / read-only, overlay sensitive paths (default) + /// Blacklist mode: bind / read-only, overlay sensitive paths #[arg(long, conflicts_with = "whitelist")] pub blacklist: bool, - /// Whitelist mode: only explicitly listed minimal paths visible + /// Whitelist mode: only explicitly listed minimal paths visible (default) #[arg(long)] pub whitelist: bool, diff --git a/src/config.rs b/src/config.rs index 8ea7f3e..2c50d64 100644 --- a/src/config.rs +++ b/src/config.rs @@ -93,7 +93,7 @@ fn merge_mode( } resolve_mode(profile) .or_else(|| resolve_mode(globals)) - .unwrap_or(SandboxMode::Blacklist) + .unwrap_or(SandboxMode::Whitelist) } fn resolve_mode(opts: &Options) -> Option { diff --git a/tests/e2e/cli.rs b/tests/e2e/cli.rs index c3a789d..dc56a4b 100644 --- a/tests/e2e/cli.rs +++ b/tests/e2e/cli.rs @@ -2,7 +2,7 @@ use crate::common::*; #[test] fn dry_run_prints_and_exits() { - let output = sandbox(&["--dry-run"]) + let output = Sandbox::new(&["--dry-run"]) .args(["--", "bash", "-c", "exit 42"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -20,7 +20,7 @@ fn dry_run_prints_and_exits() { #[test] fn dry_run_output_is_copy_pasteable_shell() { - let dry = sandbox(&["--dry-run"]) + let dry = Sandbox::new(&["--dry-run"]) .args(["--", "bash", "-c", "echo $HOME"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -34,7 +34,7 @@ fn dry_run_output_is_copy_pasteable_shell() { #[test] fn empty_home_rejected() { - let output = sandbox(&[]) + let output = Sandbox::new(&[]) .env("HOME", "") .args(["--", "true"]) .output() @@ -53,7 +53,7 @@ fn empty_home_rejected() { #[test] fn config_missing_file_errors() { - let output = sandbox_withconfig(&["--config", "/nonexistent/config.toml"]) + let output = Sandbox::new_with_config(&["--config", "/nonexistent/config.toml"]) .args(["--", "true"]) .output() .expect("failed to execute"); @@ -70,7 +70,7 @@ fn config_missing_file_errors() { fn config_invalid_toml_errors() { let cfg = ConfigFile::new("not valid {{{{ toml"); - let output = sandbox_withconfig(&["--config", &cfg]) + let output = Sandbox::new_with_config(&["--config", &cfg]) .args(["--", "true"]) .output() .expect("failed to execute"); @@ -87,7 +87,7 @@ fn config_invalid_toml_errors() { fn config_unknown_key_errors() { let cfg = ConfigFile::new("hardened = true\nbogus = \"nope\"\n"); - let output = sandbox_withconfig(&["--config", &cfg]) + let output = Sandbox::new_with_config(&["--config", &cfg]) .args(["--", "true"]) .output() .expect("failed to execute"); @@ -102,7 +102,7 @@ fn config_unknown_key_errors() { #[test] fn bwrap_arg_setenv_passes_through() { - let output = sandbox(&["--bwrap-arg", "--setenv MYVAR hello"]) + let output = Sandbox::new(&["--bwrap-arg", "--setenv MYVAR hello"]) .args(["--", "bash", "-c", "echo $MYVAR"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -123,7 +123,7 @@ fn config_entrypoint_appends_passthrough_args() { "#, ); - let output = sandbox_withconfig(&["--config", &cfg, "--profile", "test"]) + let output = Sandbox::new_with_config(&["--config", &cfg, "--profile", "test"]) .args(["--", "echo entrypoint-works"]) .output() .expect("failed to execute"); @@ -145,7 +145,7 @@ fn config_entrypoint_falls_back_to_command_defaults() { "#, ); - let output = sandbox_withconfig(&["--config", &cfg, "--profile", "test"]) + let output = Sandbox::new_with_config(&["--config", &cfg, "--profile", "test"]) .output() .expect("failed to execute"); @@ -165,7 +165,7 @@ fn config_entrypoint_alone_without_command_or_passthrough() { "#, ); - let output = sandbox_withconfig(&["--config", &cfg, "--profile", "test"]) + let output = Sandbox::new_with_config(&["--config", &cfg, "--profile", "test"]) .output() .expect("failed to execute"); @@ -178,7 +178,7 @@ fn config_entrypoint_alone_without_command_or_passthrough() { #[test] fn cli_entrypoint_appends_passthrough_args() { - let output = sandbox(&["--entrypoint", "bash"]) + let output = Sandbox::new(&["--entrypoint", "bash"]) .args(["--", "-c", "echo cli-entrypoint-works"]) .output() .expect("failed to execute"); @@ -198,7 +198,7 @@ fn cli_entrypoint_overrides_config_entrypoint() { "#, ); - let output = sandbox_withconfig(&["--config", &cfg, "--entrypoint", "bash"]) + let output = Sandbox::new_with_config(&["--config", &cfg, "--entrypoint", "bash"]) .args(["--", "-c", "echo override-works"]) .output() .expect("failed to execute"); @@ -219,7 +219,7 @@ fn config_command_alone_without_passthrough() { "#, ); - let output = sandbox_withconfig(&["--config", &cfg, "--profile", "test"]) + let output = Sandbox::new_with_config(&["--config", &cfg, "--profile", "test"]) .output() .expect("failed to execute"); @@ -239,7 +239,7 @@ fn config_command_replaced_by_passthrough() { "#, ); - let output = sandbox_withconfig(&["--config", &cfg, "--profile", "test"]) + let output = Sandbox::new_with_config(&["--config", &cfg, "--profile", "test"]) .args(["--", "bash", "-c", "echo replaced"]) .output() .expect("failed to execute"); diff --git a/tests/e2e/common.rs b/tests/e2e/common.rs index fc8b223..397aef6 100644 --- a/tests/e2e/common.rs +++ b/tests/e2e/common.rs @@ -1,18 +1,87 @@ use std::fs; +use std::ops::{Deref, DerefMut}; use std::process::Command; +use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tempfile::TempDir; -pub fn sandbox_withconfig(extra_args: &[&str]) -> Command { - let mut cmd = Command::new(env!("CARGO_BIN_EXE_agent-sandbox")); - cmd.args(extra_args); - cmd +// Blacklist mode globs the host filesystem at sandbox startup to find +// sensitive paths to mask. A matching host file that vanishes mid-startup +// makes bwrap fail in any concurrent blacklist sandbox. +pub struct HostGlobsLock; + +impl HostGlobsLock { + /// Acquire shared access. Hold while running a blacklist sandbox. + pub fn for_scan() -> RwLockReadGuard<'static, ()> { + LOCK.read().unwrap() + } + + /// Acquire exclusive access. Hold while creating or deleting a host + /// path that may match a blacklist glob (e.g. `/tmp/ssh-*`). + pub fn for_mutation() -> RwLockWriteGuard<'static, ()> { + LOCK.write().unwrap() + } } -pub fn sandbox(extra_args: &[&str]) -> Command { - let mut cmd = sandbox_withconfig(&["--no-config"]); - cmd.args(extra_args); - cmd +static LOCK: RwLock<()> = RwLock::new(()); + +#[allow(dead_code)] +enum HostGlobsGuard { + Scan(RwLockReadGuard<'static, ()>), + Mutation(RwLockWriteGuard<'static, ()>), +} + +pub struct Sandbox { + cmd: Command, + _guard: Option, +} + +impl Sandbox { + pub fn new(extra_args: &[&str]) -> Self { + Self::build(&["--no-config"], extra_args, scan_guard_for(extra_args)) + } + + pub fn new_with_config(extra_args: &[&str]) -> Self { + Self::build(&[], extra_args, scan_guard_for(extra_args)) + } + + pub fn new_for_host_mutation(extra_args: &[&str]) -> Self { + debug_assert!( + extra_args.contains(&"--blacklist"), + "new_for_host_mutation is only meaningful for blacklist sandboxes" + ); + Self::build( + &["--no-config"], + extra_args, + Some(HostGlobsGuard::Mutation(HostGlobsLock::for_mutation())), + ) + } + + fn build(prefix: &[&str], extra_args: &[&str], guard: Option) -> Self { + let mut cmd = Command::new(env!("CARGO_BIN_EXE_agent-sandbox")); + cmd.args(prefix); + cmd.args(extra_args); + Self { cmd, _guard: guard } + } +} + +fn scan_guard_for(extra_args: &[&str]) -> Option { + extra_args + .contains(&"--blacklist") + .then(|| HostGlobsGuard::Scan(HostGlobsLock::for_scan())) +} + +impl Deref for Sandbox { + type Target = Command; + fn deref(&self) -> &Command { + &self.cmd + } +} + +impl DerefMut for Sandbox { + fn deref_mut(&mut self) -> &mut Command { + &mut self.cmd + } } pub struct ConfigFile { diff --git a/tests/e2e/env.rs b/tests/e2e/env.rs index 8599246..73e3701 100644 --- a/tests/e2e/env.rs +++ b/tests/e2e/env.rs @@ -6,7 +6,7 @@ fn printenv_inside(args: &[&str], vars: &[(&str, &str)], query: &[&str]) -> Stri .map(|v| format!("printenv {v} || echo MISSING:{v}")) .collect::>() .join("; "); - let mut cmd = sandbox(args); + let mut cmd = Sandbox::new(args); for (k, v) in vars { cmd.env(k, v); } @@ -219,7 +219,7 @@ fn whitelist_unsetenv_overrides_kept_var() { #[test] fn blacklist_drops_token_and_secret_vars() { let stdout = printenv_inside( - &[], + &["--blacklist"], &[ ("GH_TOKEN", "gh-secret"), ("AWS_SECRET_ACCESS_KEY", "aws-secret"), @@ -252,7 +252,7 @@ fn blacklist_drops_token_and_secret_vars() { #[test] fn blacklist_carves_out_vendor_api_keys() { let stdout = printenv_inside( - &[], + &["--blacklist"], &[ ("ANTHROPIC_API_KEY", "anthropic-key"), ("OPENAI_API_KEY", "openai-key"), @@ -272,7 +272,7 @@ fn blacklist_carves_out_vendor_api_keys() { #[test] fn blacklist_suffix_match_does_not_catch_substring() { let stdout = printenv_inside( - &[], + &["--blacklist"], &[ ("TOKENIZER_PATH", "/opt/tok"), ("MY_TOKEN_HOLDER", "holder"), @@ -291,14 +291,18 @@ fn blacklist_suffix_match_does_not_catch_substring() { #[test] fn blacklist_keeps_unrelated_host_var() { - let stdout = printenv_inside(&[], &[("MY_NICE_VAR", "hello")], &["MY_NICE_VAR"]); + let stdout = printenv_inside( + &["--blacklist"], + &[("MY_NICE_VAR", "hello")], + &["MY_NICE_VAR"], + ); assert!(stdout.contains("hello"), "MY_NICE_VAR stripped: {stdout}"); } #[test] fn blacklist_keeps_dbus_vars() { let stdout = printenv_inside( - &[], + &["--blacklist"], &[ ("DBUS_SESSION_BUS_ADDRESS", "unix:path=/tmp/fake"), ("DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/tmp/fake-system"), @@ -324,7 +328,11 @@ fn no_env_filter_whitelist_keeps_arbitrary_host_var() { #[test] fn no_env_filter_blacklist_keeps_secrets() { - let stdout = printenv_inside(&["--no-env-filter"], &[("GH_TOKEN", "kept")], &["GH_TOKEN"]); + let stdout = printenv_inside( + &["--blacklist", "--no-env-filter"], + &[("GH_TOKEN", "kept")], + &["GH_TOKEN"], + ); assert!( stdout.contains("kept"), "expected --no-env-filter to pass secrets through, got: {stdout}" @@ -347,7 +355,7 @@ fn no_env_filter_still_honors_user_env() { #[test] fn blacklist_env_overrides_builtin_deny() { let stdout = printenv_inside( - &["--env", "GH_TOKEN=overridden"], + &["--blacklist", "--env", "GH_TOKEN=overridden"], &[("GH_TOKEN", "original")], &["GH_TOKEN"], ); diff --git a/tests/e2e/modes.rs b/tests/e2e/modes.rs index 53f8427..6095fa5 100644 --- a/tests/e2e/modes.rs +++ b/tests/e2e/modes.rs @@ -2,7 +2,7 @@ use crate::common::*; #[test] fn whitelist_hides_home_contents() { - let output = sandbox(&["--whitelist"]) + let output = Sandbox::new(&["--whitelist"]) .args(["--", "bash", "-c", "ls ~/Documents 2>&1 || echo hidden"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -16,7 +16,7 @@ fn whitelist_hides_home_contents() { #[test] fn whitelist_sys_is_readable() { - let output = sandbox(&["--whitelist"]) + let output = Sandbox::new(&["--whitelist"]) .args(["--", "bash", "-c", "cat /sys/class/net/lo/address"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -30,7 +30,7 @@ fn whitelist_sys_is_readable() { #[test] fn blacklist_run_is_tmpfs() { - let output = sandbox(&[]) + let output = Sandbox::new(&["--blacklist"]) .args([ "--", "bash", @@ -49,7 +49,7 @@ fn blacklist_run_is_tmpfs() { #[test] fn blacklist_run_dbus_socket_accessible() { - let output = sandbox(&[]) + let output = Sandbox::new(&["--blacklist"]) .args([ "--", "bash", @@ -71,7 +71,7 @@ fn blacklist_runuser_is_tmpfs() { let run_user = agent_sandbox::require_run_user().expect("failed to determine XDG_RUNTIME_DIR"); let script = format!("ls -A {} | grep -v '^bus$'", run_user); - let output = sandbox(&[]) + let output = Sandbox::new(&["--blacklist"]) .args(["--", "bash", "-c", &script]) .output() .expect("agent-sandbox binary failed to execute"); @@ -86,7 +86,7 @@ fn blacklist_runuser_is_tmpfs() { #[test] fn blacklist_dev_input_hidden() { - let output = sandbox(&[]) + let output = Sandbox::new(&["--blacklist"]) .args(["--", "bash", "-c", "ls /dev/input/ 2>/dev/null | wc -l"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -100,7 +100,7 @@ fn blacklist_dev_input_hidden() { #[test] fn blacklist_root_is_readonly() { - let output = sandbox(&[]) + let output = Sandbox::new(&["--blacklist"]) .args([ "--", "bash", @@ -124,7 +124,7 @@ fn blacklist_root_is_readonly() { #[test] fn whitelist_root_is_readonly() { - let output = sandbox(&["--whitelist"]) + let output = Sandbox::new(&["--whitelist"]) .args([ "--", "bash", @@ -148,7 +148,7 @@ fn whitelist_root_is_readonly() { #[test] fn whitelist_mountpoint_parents_are_readonly() { - let output = sandbox(&["--whitelist"]) + let output = Sandbox::new(&["--whitelist"]) .args([ "--", "bash", @@ -177,7 +177,7 @@ fn whitelist_mountpoint_parents_are_readonly() { #[test] fn whitelist_tmp_still_writable() { - let output = sandbox(&["--whitelist"]) + let output = Sandbox::new(&["--whitelist"]) .args([ "--", "bash", diff --git a/tests/e2e/mounts.rs b/tests/e2e/mounts.rs index 7da1f1b..e74a82d 100644 --- a/tests/e2e/mounts.rs +++ b/tests/e2e/mounts.rs @@ -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 { - let output = sandbox(&["--dry-run"]) + let output = Sandbox::new(&["--dry-run"]) .args(sandbox_args) .output() .expect("agent-sandbox binary failed to execute"); diff --git a/tests/e2e/namespaces.rs b/tests/e2e/namespaces.rs index a84a30b..1fe882f 100644 --- a/tests/e2e/namespaces.rs +++ b/tests/e2e/namespaces.rs @@ -13,7 +13,7 @@ fn read_sid_from_stat(stat: &str) -> u32 { } fn read_sid_inside_sandbox(extra_args: &[&str]) -> u32 { - let output = sandbox(extra_args) + let output = Sandbox::new(extra_args) .args(["--", "bash", "-c", "cat /proc/self/stat"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -26,7 +26,7 @@ fn read_sid_current_process() -> u32 { } #[test] fn unshare_net_blocks_network() { - let output = sandbox(&["--unshare-net"]) + let output = Sandbox::new(&["--unshare-net"]) .args([ "--", "bash", @@ -45,7 +45,7 @@ fn unshare_net_blocks_network() { #[test] fn hardened_pid_namespace() { - let output = sandbox(&["--hardened"]) + let output = Sandbox::new(&["--hardened"]) .args(["--", "bash", "-c", "ls /proc | grep -cE '^[0-9]+$'"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -65,7 +65,7 @@ fn chdir_override() { let dir = TempDir::new().expect("failed to create temp dir"); let dir_str = dir.path().to_str().unwrap(); - let output = sandbox(&["--chdir", dir_str]) + let output = Sandbox::new(&["--chdir", dir_str]) .args(["--", "bash", "-c", "pwd"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -82,7 +82,7 @@ fn chdir_under_hardened_tmp() { let dir = TempDir::new().expect("failed to create temp dir"); let dir_str = dir.path().to_str().unwrap(); - let output = sandbox(&["--hardened", "--chdir", dir_str]) + let output = Sandbox::new(&["--hardened", "--chdir", dir_str]) .args(["--", "bash", "-c", "pwd && touch ./ok && echo done"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -106,12 +106,12 @@ fn hardened_isolates_sid() { } #[test] -fn default_mode_shares_session() { - let inner_sid = read_sid_inside_sandbox(&[]); +fn blacklist_mode_shares_session() { + let inner_sid = read_sid_inside_sandbox(&["--blacklist"]); let outer_sid = read_sid_current_process(); assert_eq!( inner_sid, outer_sid, - "default-mode sandbox should share the session ID (got {inner_sid} != {outer_sid})" + "blacklist-mode sandbox should share the session ID (got {inner_sid} != {outer_sid})" ); } diff --git a/tests/e2e/seccomp.rs b/tests/e2e/seccomp.rs index 436491a..22d8318 100644 --- a/tests/e2e/seccomp.rs +++ b/tests/e2e/seccomp.rs @@ -2,7 +2,7 @@ use crate::common::*; #[test] fn seccomp_on_by_default_blocks_unshare() { - let output = sandbox(&[]) + let output = Sandbox::new(&[]) .args(["--", "unshare", "--user", "--map-root-user", "/bin/true"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -15,7 +15,7 @@ fn seccomp_on_by_default_blocks_unshare() { #[test] fn seccomp_off_allows_blocked_syscall() { - let output = sandbox(&["--no-seccomp"]) + let output = Sandbox::new(&["--no-seccomp"]) .args(["--", "unshare", "--user", "--map-root-user", "/bin/true"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -29,7 +29,7 @@ fn seccomp_off_allows_blocked_syscall() { #[test] fn seccomp_dry_run_emits_seccomp_arg() { - let output = sandbox(&["--dry-run"]) + let output = Sandbox::new(&["--dry-run"]) .args(["--", "/bin/true"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -43,7 +43,7 @@ fn seccomp_dry_run_emits_seccomp_arg() { #[test] fn seccomp_dry_run_no_seccomp_omits_arg() { - let output = sandbox(&["--dry-run", "--no-seccomp"]) + let output = Sandbox::new(&["--dry-run", "--no-seccomp"]) .args(["--", "/bin/true"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -57,7 +57,7 @@ fn seccomp_dry_run_no_seccomp_omits_arg() { #[test] fn seccomp_normal_workload_succeeds() { - let output = sandbox(&[]) + let output = Sandbox::new(&[]) .args(["--", "bash", "-c", "ls /etc > /dev/null && date"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -72,7 +72,7 @@ fn seccomp_normal_workload_succeeds() { fn seccomp_bash_pthread_fallback_works() { // Verifies the ENOSYS-not-EPERM choice for clone3 doesn't break libc's // clone3 -> clone fallback path that bash uses internally. - let output = sandbox(&[]) + let output = Sandbox::new(&[]) .args(["--", "bash", "-c", "true"]) .output() .expect("agent-sandbox binary failed to execute"); @@ -92,7 +92,7 @@ fn seccomp_blocks_tiocsti() { // On kernels >= 6.2 with CONFIG_LEGACY_TIOCSTI=n, the kernel blocks TIOCSTI // before seccomp sees it. We test with --no-seccomp first to detect that and // skip, so the test only asserts our filter's behaviour. - let baseline = sandbox(&["--no-seccomp"]) + let baseline = Sandbox::new(&["--no-seccomp"]) .args([ "--", "python3", @@ -107,7 +107,7 @@ fn seccomp_blocks_tiocsti() { return; } - let output = sandbox(&[]) + let output = Sandbox::new(&[]) .args([ "--", "python3", diff --git a/tests/unit/config.rs b/tests/unit/config.rs index f791587..493cddb 100644 --- a/tests/unit/config.rs +++ b/tests/unit/config.rs @@ -405,7 +405,7 @@ fn build_cli_command_overrides_config() { #[test] fn build_no_file_config() { let config = build(args_with_command(), None).unwrap(); - assert!(matches!(config.mode, SandboxMode::Blacklist)); + assert!(matches!(config.mode, SandboxMode::Whitelist)); assert!(!config.hardened); }