pub const REDACTED: &str = "[redacted]";
pub const DENY_LIST: &[&str] = &[
"access_token",
"api_key",
"apikey",
"auth",
"bearer",
"bind",
"body",
"cache_value",
"card",
"cookie",
"credential",
"csrf",
"cvv",
"id_token",
"otp",
"passphrase",
"passwd",
"password",
"payload",
"pin_code",
"private_key",
"pwd",
"refresh_token",
"secret",
"session_id",
"signature",
"sql_args",
"token",
"verifier",
];
#[must_use]
pub fn is_sensitive(field: &str) -> bool {
let lowered = field.to_ascii_lowercase();
DENY_LIST.iter().any(|needle| lowered.contains(needle))
}
#[must_use]
pub fn apply<'a>(field: &str, value: &'a str) -> &'a str {
if is_sensitive(field) { REDACTED } else { value }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_obvious_credential_names_are_all_caught() {
for name in [
"password",
"Password",
"user_password",
"db.password",
"api_key",
"authorization",
"Cookie",
"access_token",
"refresh_token",
"client_secret",
"code_verifier",
"sql_args",
"request_body",
"job_payload",
"cache_value",
] {
assert!(is_sensitive(name), "{name} should be redacted");
}
}
#[test]
fn ordinary_field_names_pass_through() {
for name in ["method", "path", "status", "duration_ms", "request_id"] {
assert!(!is_sensitive(name), "{name} should not be redacted");
}
}
#[test]
fn apply_swaps_only_the_sensitive_value() {
assert_eq!(apply("password", "hunter2"), REDACTED);
assert_eq!(apply("path", "/login"), "/login");
}
#[test]
fn the_deny_list_is_sorted_and_lowercase() {
let mut sorted = DENY_LIST.to_vec();
sorted.sort_unstable();
assert_eq!(DENY_LIST, sorted.as_slice());
assert!(DENY_LIST.iter().all(|n| n.to_ascii_lowercase() == *n));
}
}