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: String = field
.chars()
.map(|character| match character {
'-' | '.' => '_',
other => other.to_ascii_lowercase(),
})
.collect();
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 the_same_secret_is_caught_in_every_separator_convention() {
for name in [
"x-api-key",
"X-Api-Key",
"x-session-id",
"x-private-key",
"x-pin-code",
"x-cache-value",
"http.request.header.authorization",
"oauth.access-token",
] {
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",
"user-agent",
"content-length",
"x-request-id",
"http.route",
] {
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));
}
}