use serde_json::Value;
const SENSITIVE_KEY_SUBSTRINGS: &[&str] = &[
"password",
"token",
"secret",
"authorization",
"apikey",
"api_key",
"api-key",
"credential",
];
pub fn is_sensitive_key(key: &str) -> bool {
let lower = key.to_lowercase();
SENSITIVE_KEY_SUBSTRINGS
.iter()
.any(|needle| lower.contains(needle))
}
pub fn sanitize(value: &Value) -> Value {
match value {
Value::Array(items) => Value::Array(items.iter().map(sanitize).collect()),
Value::Object(map) => Value::Object(
map.iter()
.map(|(key, val)| {
let sanitized = if is_sensitive_key(key) {
Value::String("[REDACTED]".to_string())
} else {
sanitize(val)
};
(key.clone(), sanitized)
})
.collect(),
),
other => other.clone(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn redacts_sensitive_keys_at_every_depth() {
let input = json!({
"username": "alice",
"password": "hunter2",
"nested": { "apiKey": "abc123", "note": "keep me" },
"tokens": ["a", "b"],
});
let output = sanitize(&input);
assert_eq!(output["username"], "alice");
assert_eq!(output["password"], "[REDACTED]");
assert_eq!(output["nested"]["apiKey"], "[REDACTED]");
assert_eq!(output["nested"]["note"], "keep me");
}
#[test]
fn leaves_non_sensitive_values_untouched() {
let input = json!({ "count": 3, "name": "widget" });
assert_eq!(sanitize(&input), input);
}
}