#![cfg(target_os = "linux")]
mod common;
use std::path::{Path, PathBuf};
use std::process::Output;
use tempfile::TempDir;
const KERNEL_SKIP_MARKER: &str = "Landlock is not enforced";
fn home_scratch(prefix: &str) -> Option<TempDir> {
let home = PathBuf::from(std::env::var_os("HOME")?);
tempfile::Builder::new()
.prefix(prefix)
.tempdir_in(home)
.ok()
}
fn write_probe_script(project: &Path, denied: &Path) -> PathBuf {
let script_path = project.join("sandbox_probe.lua");
let source = format!(
r#"-- generated by tests/e2e_sandbox.rs
std.fs.write("{inside}", "inside-ok")
print("INSIDE_WRITE_OK")
local r = sh.exec("printf 'x' > '{denied}'")
if r.ok and r.code == 0 then
print("OUTSIDE_WRITE_OK")
else
print("OUTSIDE_WRITE_DENIED")
end
"#,
inside = project.join("inside.txt").display(),
denied = denied.display(),
);
std::fs::write(&script_path, source).expect("write probe script");
script_path
}
fn run_probe(project: &Path, script: &Path, sandbox: bool) -> (Output, TempDir) {
let home = tempfile::tempdir().expect("tempdir for AGENT_BLOCK_HOME");
let mut cmd = common::agent_block_cmd();
cmd.env_remove("AGENT_BLOCK_SANDBOX")
.env_remove("AGENT_BLOCK_SANDBOX_FS_RW")
.env_remove("AGENT_BLOCK_SANDBOX_TCP");
cmd.env("AGENT_BLOCK_HOME", home.path()).args([
"-s",
script.to_str().expect("utf-8 script path"),
"-p",
project.to_str().expect("utf-8 project path"),
]);
if sandbox {
cmd.arg("--sandbox");
}
let output = cmd.output().expect("spawn agent-block");
(output, home)
}
#[test]
fn sandbox_allows_project_writes_and_denies_writes_outside_the_allowlist() {
let Some(scratch) = home_scratch(".agent-block-sandbox-test-") else {
eprintln!("skipping: HOME is unset or not writable");
return;
};
let Some(project) = home_scratch(".agent-block-sandbox-project-") else {
eprintln!("skipping: HOME is unset or not writable");
return;
};
let denied = scratch.path().join("probe.txt");
let script = write_probe_script(project.path(), &denied);
let (output, _home) = run_probe(project.path(), &script, true);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains(KERNEL_SKIP_MARKER) {
eprintln!("skipping: this kernel provides no usable Landlock support");
return;
}
assert!(
output.status.success(),
"sandboxed run failed\nstdout: {stdout}\nstderr: {stderr}"
);
assert!(
stdout.contains("INSIDE_WRITE_OK"),
"expected the in-project write to succeed\nstdout: {stdout}\nstderr: {stderr}"
);
assert!(
project.path().join("inside.txt").exists(),
"in-project file was not created"
);
assert!(
stdout.contains("OUTSIDE_WRITE_DENIED"),
"expected sh.exec to fail writing outside the allowlist\nstdout: {stdout}\nstderr: {stderr}"
);
assert!(
!denied.exists(),
"sandbox did not prevent the write to {}",
denied.display()
);
}
#[test]
fn without_sandbox_writes_outside_the_project_succeed() {
let Some(scratch) = home_scratch(".agent-block-sandbox-test-") else {
eprintln!("skipping: HOME is unset or not writable");
return;
};
let Some(project) = home_scratch(".agent-block-sandbox-project-") else {
eprintln!("skipping: HOME is unset or not writable");
return;
};
let denied = scratch.path().join("probe.txt");
let script = write_probe_script(project.path(), &denied);
let (output, _home) = run_probe(project.path(), &script, false);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"unsandboxed run failed\nstdout: {stdout}\nstderr: {stderr}"
);
assert!(
stdout.contains("OUTSIDE_WRITE_OK"),
"expected the unsandboxed write to succeed\nstdout: {stdout}\nstderr: {stderr}"
);
assert!(
denied.exists(),
"unsandboxed write did not create {}",
denied.display()
);
}
#[test]
fn sandbox_with_unresolvable_project_root_fails_at_startup() {
let home = tempfile::tempdir().expect("tempdir for AGENT_BLOCK_HOME");
let mut cmd = common::agent_block_cmd();
cmd.env_remove("AGENT_BLOCK_SANDBOX")
.env_remove("AGENT_BLOCK_SANDBOX_FS_RW")
.env_remove("AGENT_BLOCK_SANDBOX_TCP");
cmd.env("AGENT_BLOCK_HOME", home.path()).args([
"-s",
"/nonexistent/agent-block-sandbox-test/script.lua",
"-p",
"/nonexistent/agent-block-sandbox-test",
"--sandbox",
]);
let output = cmd.output().expect("spawn agent-block");
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains(KERNEL_SKIP_MARKER) {
eprintln!("skipping: this kernel provides no usable Landlock support");
return;
}
assert!(
!output.status.success(),
"expected startup to fail\nstderr: {stderr}"
);
assert!(
stderr.contains("project root"),
"expected the project-root resolution error\nstderr: {stderr}"
);
}