mod agents; mod blacklist; pub mod cli; pub mod config; mod errors; mod preflight; mod sandbox; pub use errors::SandboxError; use std::env; use std::ffi::OsString; use std::fs; use std::os::unix::process::CommandExt; use std::path::PathBuf; pub enum SandboxMode { Blacklist, Whitelist, } pub struct SandboxConfig { pub mode: SandboxMode, pub hardened: bool, pub no_net: bool, pub extra_rw: Vec, pub extra_ro: Vec, pub mask: Vec, pub bwrap_args: Vec, pub command: PathBuf, pub command_args: Vec, pub chdir: PathBuf, pub dry_run: bool, } pub fn require_home() -> Result { env::var("HOME") .ok() .filter(|h| !h.is_empty()) .ok_or(SandboxError::HomeNotSet) } pub fn require_run_user() -> Result { env::var("XDG_RUNTIME_DIR") .ok() .or_else(resolve_run_user_from_proc) .ok_or(SandboxError::RunUserNotFound) } fn resolve_run_user_from_proc() -> Option { let status = fs::read_to_string("/proc/self/status").ok()?; for line in status.lines() { if let Some(rest) = line.strip_prefix("Uid:") { let uid = rest.split_whitespace().next()?; return Some(format!("/run/user/{uid}")); } } None } pub fn run(config: SandboxConfig) -> Result<(), SandboxError> { preflight::check(&config)?; let mut cmd = sandbox::build_command(&config)?; if config.dry_run { println!("{}", shell_quote_command(&cmd)); return Ok(()); } Err(SandboxError::Io(cmd.exec())) } fn shell_quote_command(cmd: &std::process::Command) -> String { let prog = cmd.get_program().to_string_lossy(); let args = cmd.get_args().map(|a| a.to_string_lossy()); let all: Vec<_> = std::iter::once(prog).chain(args).collect(); shlex::try_join(all.iter().map(|s| s.as_ref())).unwrap() }