pub(crate) fn is_dangerous_env_var(key: &str) -> bool {
key.starts_with("LD_")
|| key.starts_with("DYLD_")
|| key == "BASH_ENV"
|| key == "ENV"
|| key == "CDPATH"
|| key == "GLOBIGNORE"
|| key.starts_with("BASH_FUNC_")
|| key == "PROMPT_COMMAND"
|| key == "IFS"
|| key == "PYTHONSTARTUP"
|| key == "PYTHONPATH"
|| key == "NODE_OPTIONS"
|| key == "NODE_PATH"
|| key == "PERL5OPT"
|| key == "PERL5LIB"
|| key == "RUBYOPT"
|| key == "RUBYLIB"
|| key == "GEM_PATH"
|| key == "GEM_HOME"
|| key == "JAVA_TOOL_OPTIONS"
|| key == "_JAVA_OPTIONS"
|| key == "DOTNET_STARTUP_HOOKS"
|| key == "GOFLAGS"
|| key == "OP_SERVICE_ACCOUNT_TOKEN"
|| key == "OP_CONNECT_TOKEN"
|| key == "OP_CONNECT_HOST"
|| key.starts_with("OP_SESSION_")
}
pub(crate) fn is_env_var_allowed(key: &str, allowed_env_vars: &[String]) -> bool {
for pattern in allowed_env_vars {
if let Some(prefix) = pattern.strip_suffix('*') {
if prefix.contains('*') {
continue;
}
if key.starts_with(prefix) {
return true;
}
} else if !pattern.contains('*') && key == *pattern {
return true;
}
}
false
}
pub(crate) fn validate_allow_vars_pattern(allow_vars: &[String]) -> Option<String> {
for pattern in allow_vars {
if pattern.contains('*') && !pattern.ends_with('*') {
return Some(format!(
"Invalid allow_vars pattern '{}': '*' is only valid as a trailing suffix",
pattern
));
}
if pattern.starts_with('*') && pattern.len() > 1 {
return Some(format!(
"Invalid allow_vars pattern '{}': use a bare '*' to match all variables, or a specific prefix like 'AWS_*'",
pattern
));
}
}
None
}
pub(super) fn should_skip_env_var(
key: &str,
config_env_vars: &[(&str, &str)],
blocked_extra: &[&str],
) -> bool {
config_env_vars.iter().any(|(ek, _)| *ek == key)
|| blocked_extra.contains(&key)
|| is_dangerous_env_var(key)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blocks_op_service_account_token() {
assert!(is_dangerous_env_var("OP_SERVICE_ACCOUNT_TOKEN"));
}
#[test]
fn test_blocks_op_connect_token() {
assert!(is_dangerous_env_var("OP_CONNECT_TOKEN"));
}
#[test]
fn test_blocks_op_connect_host() {
assert!(is_dangerous_env_var("OP_CONNECT_HOST"));
}
#[test]
fn test_blocks_op_session_prefix() {
assert!(is_dangerous_env_var("OP_SESSION_my_team"));
assert!(is_dangerous_env_var("OP_SESSION_personal"));
assert!(is_dangerous_env_var("OP_SESSION_"));
}
#[test]
fn test_allows_unrelated_env_vars() {
assert!(!is_dangerous_env_var("OPENAI_API_KEY"));
assert!(!is_dangerous_env_var("OPERATOR_TOKEN"));
assert!(!is_dangerous_env_var("OPTIONS"));
assert!(!is_dangerous_env_var("HOME"));
assert!(!is_dangerous_env_var("PATH"));
}
#[test]
fn test_blocks_linker_injection() {
assert!(is_dangerous_env_var("LD_PRELOAD"));
assert!(is_dangerous_env_var("DYLD_INSERT_LIBRARIES"));
}
#[test]
fn test_blocks_interpreter_injection() {
assert!(is_dangerous_env_var("NODE_OPTIONS"));
assert!(is_dangerous_env_var("PYTHONPATH"));
assert!(is_dangerous_env_var("RUBYOPT"));
}
#[test]
fn test_env_var_allowed_exact_match() {
let allowed: Vec<String> = vec!["PATH".into(), "HOME".into()];
assert!(is_env_var_allowed("PATH", &allowed));
assert!(is_env_var_allowed("HOME", &allowed));
}
#[test]
fn test_env_var_allowed_exact_no_match() {
let allowed: Vec<String> = vec!["PATH".into(), "HOME".into()];
assert!(!is_env_var_allowed("SECRET", &allowed));
}
#[test]
fn test_env_var_allowed_prefix_match() {
let allowed: Vec<String> = vec!["AWS_*".into()];
assert!(is_env_var_allowed("AWS_REGION", &allowed));
assert!(is_env_var_allowed("AWS_SECRET_ACCESS_KEY", &allowed));
}
#[test]
fn test_env_var_allowed_prefix_no_match() {
let allowed: Vec<String> = vec!["AWS_*".into()];
assert!(!is_env_var_allowed("GCP_REGION", &allowed));
}
#[test]
fn test_env_var_allowed_empty_list() {
let allowed: Vec<String> = vec![];
assert!(!is_env_var_allowed("PATH", &allowed));
}
#[test]
fn test_env_var_allowed_bare_star() {
let allowed: Vec<String> = vec!["*".into()];
assert!(is_env_var_allowed("ANYTHING", &allowed));
assert!(is_env_var_allowed("PATH", &allowed));
}
#[test]
fn test_env_var_allowed_prefix_does_not_match_partial() {
let allowed: Vec<String> = vec!["AWS_*".into()];
assert!(!is_env_var_allowed("AWS", &allowed));
}
#[test]
fn test_env_var_allowed_prefix_matches_empty_suffix() {
let allowed: Vec<String> = vec!["AWS_*".into()];
assert!(is_env_var_allowed("AWS_", &allowed));
}
#[test]
fn test_env_var_allowed_mixed_patterns() {
let allowed: Vec<String> = vec!["PATH".into(), "AWS_*".into()];
assert!(is_env_var_allowed("PATH", &allowed));
assert!(is_env_var_allowed("AWS_REGION", &allowed));
assert!(!is_env_var_allowed("HOME", &allowed));
}
#[test]
fn test_env_var_allowed_mid_star_ignored() {
let allowed: Vec<String> = vec!["A*B".into()];
assert!(!is_env_var_allowed("AXB", &allowed));
assert!(!is_env_var_allowed("A*B", &allowed));
}
#[test]
fn test_validate_valid_patterns() {
let patterns: Vec<String> = vec!["PATH".into(), "AWS_*".into(), "*".into()];
assert!(validate_allow_vars_pattern(&patterns).is_none());
}
#[test]
fn test_validate_rejects_mid_star() {
let patterns: Vec<String> = vec!["A*B".into()];
let err = validate_allow_vars_pattern(&patterns);
assert!(err.is_some());
assert!(err.as_ref().is_some_and(|e| e.contains("A*B")));
}
#[test]
fn test_validate_rejects_leading_star_with_suffix() {
let patterns: Vec<String> = vec!["*X".into()];
let err = validate_allow_vars_pattern(&patterns);
assert!(err.is_some());
assert!(err.as_ref().is_some_and(|e| e.contains("*X")));
}
#[test]
fn test_validate_accepts_bare_star() {
let patterns: Vec<String> = vec!["*".into()];
assert!(validate_allow_vars_pattern(&patterns).is_none());
}
#[test]
fn test_validate_exact_name_no_star() {
let patterns: Vec<String> = vec!["PATH".into()];
assert!(validate_allow_vars_pattern(&patterns).is_none());
}
}