auditaur_core/
redaction.rs1use serde_json::Value;
2use std::collections::HashSet;
3
4pub const DEFAULT_REDACTION_KEYS: &[&str] = &[
5 "password",
6 "passwd",
7 "pwd",
8 "secret",
9 "token",
10 "access_token",
11 "refresh_token",
12 "id_token",
13 "authorization",
14 "api_key",
15 "apikey",
16 "key",
17 "cookie",
18 "set-cookie",
19 "session",
20 "credential",
21 "connection_string",
22];
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct RedactionResult {
26 pub value: Value,
27 pub redacted: bool,
28}
29
30pub fn redact_json(value: &Value, extra_keys: &[String]) -> RedactionResult {
31 redact_json_with_options(value, true, extra_keys)
32}
33
34pub fn redact_json_with_options(
35 value: &Value,
36 redact_defaults: bool,
37 extra_keys: &[String],
38) -> RedactionResult {
39 let keys = redaction_key_set(extra_keys);
40 let keys = if redact_defaults {
41 keys
42 } else {
43 extra_keys
44 .iter()
45 .map(|key| key.to_ascii_lowercase())
46 .collect()
47 };
48 let mut redacted = false;
49 let value = redact_value(value, &keys, &mut redacted);
50 RedactionResult { value, redacted }
51}
52
53fn redaction_key_set(extra_keys: &[String]) -> HashSet<String> {
54 DEFAULT_REDACTION_KEYS
55 .iter()
56 .map(|key| key.to_ascii_lowercase())
57 .chain(extra_keys.iter().map(|key| key.to_ascii_lowercase()))
58 .collect()
59}
60
61fn redact_value(value: &Value, keys: &HashSet<String>, redacted: &mut bool) -> Value {
62 match value {
63 Value::Object(map) => Value::Object(
64 map.iter()
65 .map(|(key, value)| {
66 if keys.contains(&key.to_ascii_lowercase()) {
67 *redacted = true;
68 (key.clone(), Value::String("[REDACTED]".to_string()))
69 } else {
70 (key.clone(), redact_value(value, keys, redacted))
71 }
72 })
73 .collect(),
74 ),
75 Value::Array(items) => Value::Array(
76 items
77 .iter()
78 .map(|item| redact_value(item, keys, redacted))
79 .collect(),
80 ),
81 _ => value.clone(),
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::redact_json;
88 use serde_json::json;
89
90 #[test]
91 fn redacts_matching_keys_recursively() {
92 let input = json!({
93 "name": "auditaur",
94 "nested": {
95 "token": "abc",
96 "items": [{ "api_key": "def" }]
97 }
98 });
99
100 let result = redact_json(&input, &[]);
101
102 assert!(result.redacted);
103 assert_eq!(result.value["name"], "auditaur");
104 assert_eq!(result.value["nested"]["token"], "[REDACTED]");
105 assert_eq!(result.value["nested"]["items"][0]["api_key"], "[REDACTED]");
106 }
107
108 #[test]
109 fn can_use_extra_keys_without_default_keys() {
110 let input = json!({
111 "token": "kept",
112 "custom_secret": "hidden"
113 });
114
115 let result = super::redact_json_with_options(&input, false, &["custom_secret".to_string()]);
116
117 assert!(result.redacted);
118 assert_eq!(result.value["token"], "kept");
119 assert_eq!(result.value["custom_secret"], "[REDACTED]");
120 }
121}