mod command;
mod error;
mod platform;
mod sandbox;
pub use command::Command;
pub use error::SandboxError;
pub use sandbox::{ProtectHome, ProtectSystem, Sandbox, SandboxPolicy};
#[cfg(target_os = "linux")]
pub use platform::landlock_available;
pub fn apply_sandbox(policy: &SandboxPolicy) -> Result<(), SandboxError> {
platform::apply_sandbox(policy)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
use std::process::Stdio;
#[test]
fn test_command_without_sandbox() {
let output = Command::new("echo")
.arg("hello")
.stdout(Stdio::piped())
.output()
.expect("failed to execute echo");
assert!(output.status.success());
assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "hello");
}
#[test]
fn test_sandbox_builder_fluent_api() {
let sandbox = Sandbox::new()
.protect_system(ProtectSystem::Strict)
.protect_home(ProtectHome::Yes)
.read_write_paths([Path::new("/tmp")])
.private_network(true)
.no_new_privileges(true)
.limit_nofile(256)
.limit_nproc(10)
.limit_core(0)
.build()
.expect("failed to build sandbox");
let _ = sandbox;
}
#[test]
#[cfg(target_os = "macos")]
fn test_protect_home_blocks_home_access() {
let sandbox = Sandbox::new()
.protect_home(ProtectHome::Yes)
.build()
.expect("failed to build sandbox");
let home = std::env::var("HOME").unwrap_or_else(|_| "/Users".to_string());
let output = Command::new("ls")
.arg(&home)
.sandbox(sandbox)
.stderr(Stdio::piped())
.output()
.expect("failed to execute ls");
assert!(!output.status.success());
}
#[test]
#[cfg(target_os = "macos")]
fn test_private_network_blocks_network() {
let sandbox = Sandbox::new()
.private_network(true)
.build()
.expect("failed to build sandbox");
let output = Command::new("curl")
.arg("--connect-timeout")
.arg("1")
.arg("http://127.0.0.1:1")
.sandbox(sandbox)
.stderr(Stdio::piped())
.output()
.expect("failed to execute curl");
assert!(!output.status.success());
}
#[test]
#[cfg(target_os = "macos")]
fn test_inaccessible_paths() {
let temp_dir = std::env::temp_dir();
let test_file = temp_dir.join("sandbox_test_inaccessible.txt");
std::fs::write(&test_file, "secret content").expect("failed to write test file");
let sandbox = Sandbox::new()
.inaccessible_paths([&test_file])
.build()
.expect("failed to build sandbox");
let output = Command::new("cat")
.arg(&test_file)
.sandbox(sandbox)
.stderr(Stdio::piped())
.output()
.expect("failed to execute cat");
let _ = std::fs::remove_file(&test_file);
assert!(!output.status.success());
}
#[test]
#[cfg(target_os = "macos")]
fn test_read_only_paths() {
let temp_dir = std::env::temp_dir();
let test_file = temp_dir.join("sandbox_test_readonly.txt");
std::fs::write(&test_file, "initial content").expect("failed to write test file");
let sandbox = Sandbox::new()
.read_only_paths([&temp_dir])
.build()
.expect("failed to build sandbox");
let output = Command::new("sh")
.arg("-c")
.arg(format!("echo 'new content' > {}", test_file.display()))
.sandbox(sandbox)
.stderr(Stdio::piped())
.output()
.expect("failed to execute sh");
let _ = std::fs::remove_file(&test_file);
assert!(!output.status.success());
}
#[test]
#[cfg(target_os = "linux")]
fn test_protect_system_strict() {
let sandbox = Sandbox::new()
.protect_system(ProtectSystem::Strict)
.read_write_paths([Path::new("/tmp")])
.build()
.expect("failed to build sandbox");
let output = Command::new("sh")
.arg("-c")
.arg("echo test > /tmp/sandbox_test_strict.txt && cat /tmp/sandbox_test_strict.txt")
.sandbox(sandbox)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("failed to execute sh");
let _ = std::fs::remove_file("/tmp/sandbox_test_strict.txt");
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
}