diff --git a/README.md b/README.md index e5c50fa..49768fb 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,25 @@ The threat model is prompt injection and accidental damage, not a determined att **Not protected in blacklist mode:** arbitrary readable files outside the sensitive paths list, and D-Bus method calls (access control is daemon-side). +## Configuration file + +Settings can be stored in a TOML config file at `$XDG_CONFIG_HOME/agent-sandbox/config.toml` (or pass `--config `). Use `--no-config` to skip loading it. The config file accepts the same options as the corresponding CLI flags. + +Top-level keys set defaults; `[profile.]` sections define named presets selectable with `--profile `. CLI flags always take highest precedence, followed by the active profile, then top-level defaults. + +```toml +# Global defaults +whitelist = true +no-net = true +ro = ["~/.aws"] + +# Named profile +[profile.docker] +blacklist = true +rw = ["/var/run/docker.sock"] +command = ["claude", "--dangerously-skip-permissions"] +``` + ## Escape hatches When the agent needs access to something the sandbox blocks, use `--rw` or `--ro`: diff --git a/src/cli.rs b/src/cli.rs index 52cfb5e..a99ab7e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -47,7 +47,7 @@ pub struct Args { pub profile: Option, /// Path to config file (default: $XDG_CONFIG_HOME/agent-sandbox/config.toml) - #[arg(long = "config", value_name = "PATH")] + #[arg(long = "config", value_name = "PATH", conflicts_with = "no_config")] pub config_path: Option, /// Skip loading the config file entirely diff --git a/tests/integration.rs b/tests/integration.rs index 36b1d62..4f96099 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3,12 +3,18 @@ use std::process::Command; use tempfile::TempDir; -fn sandbox(extra_args: &[&str]) -> Command { +fn sandbox_withconfig(extra_args: &[&str]) -> Command { let mut cmd = Command::new(env!("CARGO_BIN_EXE_agent-sandbox")); cmd.args(extra_args); cmd } +fn sandbox(extra_args: &[&str]) -> Command { + let mut cmd = sandbox_withconfig(&["--no-config"]); + cmd.args(extra_args); + cmd +} + fn write_config(dir: &TempDir, content: &str) -> String { let path = dir.path().join("config.toml"); fs::write(&path, content).expect("failed to write config"); @@ -547,7 +553,7 @@ fn rw_missing_path_errors() { #[test] fn config_missing_file_errors() { - let output = sandbox(&["--config", "/nonexistent/config.toml"]) + let output = sandbox_withconfig(&["--config", "/nonexistent/config.toml"]) .args(["--", "true"]) .output() .expect("failed to execute"); @@ -565,7 +571,7 @@ fn config_invalid_toml_errors() { let dir = TempDir::new().unwrap(); let cfg = write_config(&dir, "not valid {{{{ toml"); - let output = sandbox(&["--config", &cfg]) + let output = sandbox_withconfig(&["--config", &cfg]) .args(["--", "true"]) .output() .expect("failed to execute"); @@ -583,7 +589,7 @@ fn config_unknown_key_errors() { let dir = TempDir::new().unwrap(); let cfg = write_config(&dir, "hardened = true\nbogus = \"nope\"\n"); - let output = sandbox(&["--config", &cfg]) + let output = sandbox_withconfig(&["--config", &cfg]) .args(["--", "true"]) .output() .expect("failed to execute");