25f0037aab
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.
137 lines
2.9 KiB
Rust
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",
|
|
];
|