Initial commit
This commit is contained in:
131
src/main.rs
Normal file
131
src/main.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use agent_sandbox::{SandboxConfig, SandboxMode};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
name = "agent-sandbox",
|
||||
version,
|
||||
about = "Sandbox agentic coding assistants with bubblewrap"
|
||||
)]
|
||||
struct Args {
|
||||
/// Blacklist mode: bind / read-only, overlay sensitive paths (default)
|
||||
#[arg(long, conflicts_with = "whitelist")]
|
||||
blacklist: bool,
|
||||
|
||||
/// Whitelist mode: only explicitly listed minimal paths visible
|
||||
#[arg(long)]
|
||||
whitelist: bool,
|
||||
|
||||
/// Harden: unshare IPC, PID, UTS; private /tmp, /dev, /run
|
||||
#[arg(long)]
|
||||
hardened: bool,
|
||||
|
||||
/// Unshare the network namespace
|
||||
#[arg(long)]
|
||||
no_net: bool,
|
||||
|
||||
/// Bind an extra path read-write (repeatable)
|
||||
#[arg(long = "rw", value_name = "PATH", action = clap::ArgAction::Append)]
|
||||
extra_rw: Vec<PathBuf>,
|
||||
|
||||
/// Bind an extra path read-only (repeatable)
|
||||
#[arg(long = "ro", value_name = "PATH", action = clap::ArgAction::Append)]
|
||||
extra_ro: Vec<PathBuf>,
|
||||
|
||||
/// Print the bwrap command without executing
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
|
||||
/// Working directory inside the sandbox (default: current directory)
|
||||
#[arg(long, value_name = "PATH")]
|
||||
chdir: Option<PathBuf>,
|
||||
|
||||
/// Command and arguments to run inside the sandbox
|
||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
||||
command_and_args: Vec<OsString>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
let (command, command_args) = resolve_command(args.command_and_args);
|
||||
let command = assert_binary_exists(&command);
|
||||
let chdir = assert_chdir(args.chdir);
|
||||
|
||||
let mode = if args.whitelist {
|
||||
SandboxMode::Whitelist
|
||||
} else {
|
||||
SandboxMode::Blacklist
|
||||
};
|
||||
|
||||
let config = SandboxConfig {
|
||||
mode,
|
||||
hardened: args.hardened,
|
||||
no_net: args.no_net,
|
||||
extra_rw: args.extra_rw,
|
||||
extra_ro: args.extra_ro,
|
||||
command,
|
||||
command_args,
|
||||
chdir,
|
||||
dry_run: args.dry_run,
|
||||
};
|
||||
|
||||
if let Err(e) = agent_sandbox::run(config) {
|
||||
eprintln!("error: {e}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_command(mut positional: Vec<OsString>) -> (OsString, Vec<OsString>) {
|
||||
if !positional.is_empty() {
|
||||
let cmd = positional.remove(0);
|
||||
return (cmd, positional);
|
||||
}
|
||||
if let Ok(cmd) = std::env::var("SANDBOX_CMD") {
|
||||
return (OsString::from(cmd), vec![]);
|
||||
}
|
||||
(
|
||||
OsString::from("claude"),
|
||||
vec![OsString::from("--dangerously-skip-permissions")],
|
||||
)
|
||||
}
|
||||
|
||||
fn assert_binary_exists(name: &OsStr) -> PathBuf {
|
||||
resolve_binary(name).unwrap_or_else(|| {
|
||||
eprintln!("error: command not found: {}", name.to_string_lossy());
|
||||
process::exit(1);
|
||||
})
|
||||
}
|
||||
|
||||
fn assert_chdir(explicit: Option<PathBuf>) -> PathBuf {
|
||||
if let Some(p) = explicit {
|
||||
return p;
|
||||
}
|
||||
match std::env::current_dir() {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"error: {}",
|
||||
agent_sandbox::SandboxError::CurrentDirUnavailable(e)
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_binary(name: &OsStr) -> Option<PathBuf> {
|
||||
let path = PathBuf::from(name);
|
||||
if path.is_absolute() || path.components().count() > 1 {
|
||||
return path.is_file().then_some(path);
|
||||
}
|
||||
std::env::var_os("PATH").and_then(|path_var| {
|
||||
std::env::split_paths(&path_var)
|
||||
.map(|dir| dir.join(name))
|
||||
.find(|p| p.is_file())
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user