pub const SENSITIVE_PATTERNS: &[&str] = &[
"password",
"passwd",
"pwd",
"secret",
"token",
"api_key",
"apikey",
"access_key",
"private_key",
"ssn",
"social_security",
"tax_id",
"national_id",
"credit_card",
"card_number",
"cvv",
"cvc",
"bank_account",
"iban",
"routing_number",
"pin",
"otp",
"mfa",
"totp",
];
pub fn is_sensitive_column(column_name: &str) -> bool {
let lower = column_name.to_lowercase();
SENSITIVE_PATTERNS.iter().any(|p| lower.contains(p))
}
fn matches_qualified(table: &str, column: &str, list: &[String]) -> bool {
let qualified = format!("{table}.{column}");
list.iter().any(|c| c == &qualified)
}
pub fn is_excluded(table: &str, column: &str, exclude_columns: &[String]) -> bool {
matches_qualified(table, column, exclude_columns)
}
pub fn is_included(table: &str, column: &str, include_columns: &[String]) -> bool {
matches_qualified(table, column, include_columns)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sensitive_detects_password_variants() {
assert!(is_sensitive_column("password"));
assert!(is_sensitive_column("password_hash"));
assert!(is_sensitive_column("user_password"));
assert!(is_sensitive_column("passwd"));
assert!(is_sensitive_column("PWD")); }
#[test]
fn test_sensitive_detects_financial_and_pii() {
assert!(is_sensitive_column("ssn"));
assert!(is_sensitive_column("social_security"));
assert!(is_sensitive_column("tax_id"));
assert!(is_sensitive_column("credit_card"));
assert!(is_sensitive_column("card_number"));
assert!(is_sensitive_column("cvv"));
assert!(is_sensitive_column("iban"));
assert!(is_sensitive_column("api_key"));
assert!(is_sensitive_column("reset_token"));
}
#[test]
fn test_sensitive_does_not_flag_normal_columns() {
assert!(!is_sensitive_column("status"));
assert!(!is_sensitive_column("email"));
assert!(!is_sensitive_column("price"));
assert!(!is_sensitive_column("created_at"));
assert!(!is_sensitive_column("role"));
assert!(!is_sensitive_column("name"));
assert!(!is_sensitive_column("total_amount"));
assert!(!is_sensitive_column("passport_number")); }
#[test]
fn test_is_excluded_matches_qualified_name() {
let exclude = vec![
"users.internal_notes".to_string(),
"orders.admin_comment".to_string(),
];
assert!(is_excluded("users", "internal_notes", &exclude));
assert!(is_excluded("orders", "admin_comment", &exclude));
assert!(!is_excluded("users", "email", &exclude));
assert!(!is_excluded("posts", "internal_notes", &exclude));
assert!(!is_excluded("users", "internal_notes", &[]));
}
#[test]
fn test_is_included_matches_qualified_name() {
let include = vec!["orders.token_type".to_string()];
assert!(is_included("orders", "token_type", &include));
assert!(!is_included("users", "token_type", &include));
assert!(!is_included("orders", "token_type", &[]));
}
}