Files
agent-sandbox/src/env.rs
T
mrtoth 25f0037aab Filter environment variables in both sandbox modes
Whitelist mode now clears the parent env and re-adds a small allowlist
(identity, terminal, locale, proxy, non-GUI XDG, vendor prefixes).
Blacklist mode strips cloud credentials, backup passphrases, dangling
socket pointers, and anything matching *_TOKEN, *_SECRET, *_PASSWORD,
*_PASSPHRASE, *_API_KEY, *_PRIVATE_KEY, *_CLIENT_SECRET; vendor prefix
carve-outs keep ANTHROPIC_API_KEY and friends.

Users can override via --setenv KEY=VALUE and --unsetenv KEY (and the
corresponding TOML keys), or opt out of the built-in policy entirely
with --no-env-filter.
2026-04-08 09:22:11 +02:00

137 lines
2.9 KiB
Rust

pub fn whitelist_env_args(parent_env: &[(String, String)]) -> Vec<String> {
let mut args = vec!["--clearenv".to_string()];
for (key, value) in parent_env {
if whitelist_keeps(key) {
args.push("--setenv".to_string());
args.push(key.clone());
args.push(value.clone());
}
}
args
}
fn whitelist_keeps(key: &str) -> bool {
WHITELIST_KEEP_EXACT.contains(&key)
|| WHITELIST_KEEP_PREFIXES
.iter()
.any(|prefix| key.starts_with(prefix))
}
const WHITELIST_KEEP_EXACT: &[&str] = &[
// identity / shell
"HOME",
"USER",
"LOGNAME",
"PATH",
"SHELL",
// terminal
"TERM",
"COLORTERM",
"NO_COLOR",
"FORCE_COLOR",
"CLICOLOR",
// locale
"LANG",
"TZ",
// editor
"EDITOR",
"VISUAL",
"PAGER",
// tmp
"TMPDIR",
// proxy
"HTTP_PROXY",
"HTTPS_PROXY",
"NO_PROXY",
"ALL_PROXY",
"http_proxy",
"https_proxy",
"no_proxy",
"all_proxy",
// non-GUI XDG base dirs
"XDG_CONFIG_HOME",
"XDG_DATA_HOME",
"XDG_CACHE_HOME",
"XDG_STATE_HOME",
"XDG_CONFIG_DIRS",
"XDG_DATA_DIRS",
];
const WHITELIST_KEEP_PREFIXES: &[&str] = &[
"LC_",
"ANTHROPIC_",
"CLAUDE_",
"CLAUDECODE",
"OPENAI_",
"CODEX_",
"GEMINI_",
"OTEL_",
];
pub fn blacklist_env_args(parent_env: &[(String, String)]) -> Vec<String> {
let mut args = Vec::new();
for (key, _) in parent_env {
if blacklist_drops(key) {
args.push("--unsetenv".to_string());
args.push(key.clone());
}
}
args
}
fn blacklist_drops(key: &str) -> bool {
if BLACKLIST_KEEP_PREFIXES
.iter()
.any(|prefix| key.starts_with(prefix))
{
return false;
}
if BLACKLIST_DROP_EXACT.contains(&key) {
return true;
}
BLACKLIST_DROP_SUFFIXES
.iter()
.any(|suffix| key.ends_with(suffix))
}
const BLACKLIST_KEEP_PREFIXES: &[&str] = &["ANTHROPIC_", "CLAUDE_", "OPENAI_", "CODEX_", "GEMINI_"];
const BLACKLIST_DROP_EXACT: &[&str] = &[
// dangling sockets after path overlays
"DISPLAY",
"WAYLAND_DISPLAY",
"XAUTHORITY",
"ICEAUTHORITY",
"SESSION_MANAGER",
"SSH_AUTH_SOCK",
"SSH_AGENT_PID",
"GPG_AGENT_INFO",
"GPG_TTY",
"GNUPGHOME",
// cloud creds (don't fit suffix patterns)
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
"AWS_SESSION_TOKEN",
"AWS_SECURITY_TOKEN",
"AWS_PROFILE",
"GOOGLE_APPLICATION_CREDENTIALS",
"AZURE_CLIENT_ID",
"AZURE_TENANT_ID",
// backups
"RESTIC_PASSWORD_COMMAND",
"RESTIC_PASSWORD_FILE",
"RESTIC_KEY_HINT",
"BORG_PASSPHRASE",
"BORG_PASSCOMMAND",
];
const BLACKLIST_DROP_SUFFIXES: &[&str] = &[
"_TOKEN",
"_SECRET",
"_PASSWORD",
"_PASSPHRASE",
"_API_KEY",
"_PRIVATE_KEY",
"_CLIENT_SECRET",
];