Files
agent-sandbox/tests/e2e/common.rs
T
mrtoth 6e81866226 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.
2026-04-27 08:18:41 +02:00

110 lines
2.8 KiB
Rust

use std::fs;
use std::ops::{Deref, DerefMut};
use std::process::Command;
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use tempfile::TempDir;
// 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()
}
}
static LOCK: RwLock<()> = RwLock::new(());
#[allow(dead_code)]
enum HostGlobsGuard {
Scan(RwLockReadGuard<'static, ()>),
Mutation(RwLockWriteGuard<'static, ()>),
}
pub struct Sandbox {
cmd: Command,
_guard: Option<HostGlobsGuard>,
}
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<HostGlobsGuard>) -> 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<HostGlobsGuard> {
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 {
_dir: TempDir,
path: String,
}
impl ConfigFile {
pub fn new(content: &str) -> Self {
let dir = TempDir::new().unwrap();
let path = dir.path().join("config.toml");
fs::write(&path, content).expect("failed to write config");
Self {
_dir: dir,
path: path.to_str().unwrap().to_string(),
}
}
}
impl std::ops::Deref for ConfigFile {
type Target = str;
fn deref(&self) -> &str {
&self.path
}
}