use std::fs; use std::process::Command; use tempfile::TempDir; fn sandbox(extra_args: &[&str]) -> Command { let mut cmd = Command::new(env!("CARGO_BIN_EXE_agent-sandbox")); cmd.args(extra_args); cmd } #[test] fn cwd_is_writable() { let output = sandbox(&[]) .args(["--", "bash", "-c", "touch ./sandbox_canary && echo ok"]) .output() .expect("agent-sandbox binary failed to execute"); let stdout = String::from_utf8_lossy(&output.stdout); assert!( stdout.contains("ok"), "expected 'ok' in stdout, got: {stdout}" ); } #[test] fn host_fs_is_readonly() { let output = sandbox(&[]) .args(["--", "bash", "-c", "touch /etc/pwned 2>&1 || echo readonly"]) .output() .expect("agent-sandbox binary failed to execute"); let stdout = String::from_utf8_lossy(&output.stdout); assert!( stdout.contains("readonly"), "expected 'readonly' in stdout, got: {stdout}" ); assert!(!std::path::Path::new("/etc/pwned").exists()); } #[test] fn ssh_dir_is_hidden() { let output = sandbox(&[]) .args(["--", "bash", "-c", "ls ~/.ssh 2>/dev/null | wc -l"]) .output() .expect("agent-sandbox binary failed to execute"); let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); assert_eq!(stdout, "0", "expected empty ~/.ssh, got {stdout} entries"); } #[test] fn no_net_blocks_network() { let output = sandbox(&["--no-net"]) .args([ "--", "bash", "-c", "curl -s --max-time 2 http://1.1.1.1 2>&1; echo $?", ]) .output() .expect("agent-sandbox binary failed to execute"); let stdout = String::from_utf8_lossy(&output.stdout); assert!( !stdout.trim().ends_with("0"), "expected curl to fail, got: {stdout}" ); } #[test] fn hardened_pid_namespace() { let output = sandbox(&["--hardened"]) .args(["--", "bash", "-c", "ls /proc | grep -cE '^[0-9]+$'"]) .output() .expect("agent-sandbox binary failed to execute"); let count: u32 = String::from_utf8_lossy(&output.stdout) .trim() .parse() .unwrap_or(999); assert!( count < 10, "expected isolated PID namespace with few PIDs, got {count}" ); } #[test] fn whitelist_hides_home_contents() { let output = sandbox(&["--whitelist"]) .args(["--", "bash", "-c", "ls ~/Documents 2>&1 || echo hidden"]) .output() .expect("agent-sandbox binary failed to execute"); let stdout = String::from_utf8_lossy(&output.stdout); assert!( stdout.contains("hidden"), "expected ~/Documents to be hidden, got: {stdout}" ); } #[test] fn extra_ro_mount() { let dir = TempDir::new().expect("failed to create temp dir"); 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]) .args([ "--", "bash", "-c", &format!("cat {dir_str}/hello.txt && touch {dir_str}/new 2>&1 || echo readonly"), ]) .output() .expect("agent-sandbox binary failed to execute"); let stdout = String::from_utf8_lossy(&output.stdout); assert!( stdout.contains("hi"), "expected file content 'hi', got: {stdout}" ); assert!( stdout.contains("readonly"), "expected ro mount to block writes, got: {stdout}" ); } #[test] 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]) .args([ "--", "bash", "-c", &format!("touch {dir_str}/canary && echo ok"), ]) .output() .expect("agent-sandbox binary failed to execute"); let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("ok"), "expected 'ok', got: {stdout}"); assert!( dir.path().join("canary").exists(), "canary file should exist on host after rw mount" ); } #[test] 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]) .args(["--", "bash", "-c", "pwd"]) .output() .expect("agent-sandbox binary failed to execute"); let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); assert_eq!( stdout, dir_str, "expected cwd to be {dir_str}, got: {stdout}" ); } #[test] 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]) .args(["--", "bash", "-c", "pwd && touch ./ok && echo done"]) .output() .expect("agent-sandbox binary failed to execute"); let stdout = String::from_utf8_lossy(&output.stdout); assert!( stdout.contains("done"), "expected chdir under /tmp to work with --hardened, got: {stdout}" ); } #[test] fn dry_run_prints_and_exits() { let output = sandbox(&["--dry-run"]) .args(["--", "bash", "-c", "exit 42"]) .output() .expect("agent-sandbox binary failed to execute"); let stdout = String::from_utf8_lossy(&output.stdout); assert!( stdout.contains("bwrap"), "expected bwrap command in dry-run output, got: {stdout}" ); assert!( output.status.success(), "dry-run should exit 0, not 42 from the inner command" ); } #[test] fn rw_missing_path_errors() { let output = sandbox(&["--rw", "/nonexistent/xyz"]) .args(["--", "true"]) .output() .expect("agent-sandbox binary failed to execute"); assert!( !output.status.success(), "expected non-zero exit for missing --rw path" ); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("/nonexistent/xyz"), "expected path in error message, got: {stderr}" ); }