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.
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
||||
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",
|
||||
];
|
||||
Reference in New Issue
Block a user