const BLOCKED_ENV_VARS: &[&str] = &[
"AWS_SECRET_ACCESS_KEY",
"AWS_SESSION_TOKEN",
"GOOGLE_APPLICATION_CREDENTIALS",
"AZURE_CLIENT_SECRET",
"GITHUB_TOKEN",
"GH_TOKEN",
"GITLAB_TOKEN",
"NPM_TOKEN",
"DOCKER_PASSWORD",
"REGISTRY_PASSWORD",
"HOME",
"USER",
"LOGNAME",
"SHELL",
"HISTFILE",
"DATABASE_URL",
"REDIS_URL",
"MONGODB_URI",
"POSTGRES_PASSWORD",
"MYSQL_ROOT_PASSWORD",
"JWT_SECRET",
"SESSION_SECRET",
"ENCRYPTION_KEY",
"MASTER_KEY",
"PRIVATE_KEY",
"SECRET_KEY",
];
const BLOCKED_ENV_PREFIXES: &[&str] =
&["AWS_", "AZURE_", "GCP_", "GOOGLE_", "GITHUB_", "GITLAB_", "SSH_", "GPG_"];
const BLOCKED_KEYWORDS: &[&str] =
&["PASSWORD", "SECRET", "TOKEN", "KEY", "CREDENTIAL", "AUTH", "PRIVATE"];
#[must_use]
pub fn is_env_var_safe(name: &str) -> bool {
!is_blocked(name)
}
#[must_use]
pub fn load_safe_env_vars() -> std::collections::HashMap<String, String> {
std::env::vars().filter(|(name, _)| !is_blocked(name)).collect()
}
#[must_use]
pub fn load_env_var_safe(name: &str) -> Option<String> {
if is_blocked(name) {
return None;
}
std::env::var(name).ok()
}
fn is_blocked(name: &str) -> bool {
if BLOCKED_ENV_VARS.iter().any(|blocked| blocked.eq_ignore_ascii_case(name)) {
return true;
}
let upper = name.to_ascii_uppercase();
if BLOCKED_ENV_PREFIXES.iter().any(|prefix| upper.starts_with(prefix)) {
return true;
}
if BLOCKED_KEYWORDS.iter().any(|keyword| upper.contains(keyword)) {
return true;
}
false
}
#[cfg(test)]
#[allow(unsafe_code)]
mod tests {
use super::*;
#[test]
fn blocks_known_sensitive_vars() {
assert!(!is_env_var_safe("AWS_SECRET_ACCESS_KEY"));
assert!(!is_env_var_safe("GITHUB_TOKEN"));
assert!(!is_env_var_safe("DATABASE_URL"));
assert!(!is_env_var_safe("HOME"));
assert!(!is_env_var_safe("SSH_AUTH_SOCK"));
}
#[test]
fn blocks_prefix_match() {
assert!(!is_env_var_safe("AWS_REGION"));
assert!(!is_env_var_safe("AZURE_SUBSCRIPTION_ID"));
assert!(!is_env_var_safe("GCP_PROJECT"));
assert!(!is_env_var_safe("GOOGLE_CLOUD_KEY"));
}
#[test]
fn blocks_keyword_match() {
assert!(!is_env_var_safe("MY_PASSWORD"));
assert!(!is_env_var_safe("API_SECRET"));
assert!(!is_env_var_safe("ACCESS_TOKEN"));
assert!(!is_env_var_safe("ENCRYPTION_KEY"));
assert!(!is_env_var_safe("CUSTOM_CREDENTIAL"));
}
#[test]
fn allows_safe_vars() {
assert!(is_env_var_safe("RUST_LOG"));
assert!(is_env_var_safe("CARGO_HOME"));
assert!(is_env_var_safe("LANG"));
assert!(is_env_var_safe("TERM"));
assert!(is_env_var_safe("BOB_MODEL"));
}
#[test]
fn case_insensitive() {
assert!(!is_env_var_safe("github_token"));
assert!(!is_env_var_safe("Database_Url"));
assert!(!is_env_var_safe("aws_secret_access_key"));
}
#[test]
fn load_safe_env_vars_filters() {
unsafe {
std::env::set_var("BOB_TEST_SAFE_VAR", "safe_value");
std::env::set_var("BOB_TEST_PASSWORD", "secret_value");
}
let vars = load_safe_env_vars();
assert!(vars.contains_key("BOB_TEST_SAFE_VAR"));
assert!(!vars.contains_key("BOB_TEST_PASSWORD"));
unsafe {
std::env::remove_var("BOB_TEST_SAFE_VAR");
std::env::remove_var("BOB_TEST_PASSWORD");
}
}
#[test]
fn load_env_var_safe_blocks_sensitive() {
unsafe { std::env::set_var("MY_API_TOKEN", "secret") };
assert!(load_env_var_safe("MY_API_TOKEN").is_none());
unsafe { std::env::remove_var("MY_API_TOKEN") };
}
#[test]
fn load_env_var_safe_returns_safe() {
unsafe { std::env::set_var("BOB_TEST_LOADED", "value") };
assert_eq!(load_env_var_safe("BOB_TEST_LOADED"), Some("value".to_string()));
unsafe { std::env::remove_var("BOB_TEST_LOADED") };
}
}