github_mcp/core/
sanitizer.rs1use serde_json::Value;
4
5const SENSITIVE_KEY_SUBSTRINGS: &[&str] = &[
10 "password",
11 "token",
12 "secret",
13 "authorization",
14 "apikey",
15 "api_key",
16 "api-key",
17 "credential",
18];
19
20pub fn is_sensitive_key(key: &str) -> bool {
21 let lower = key.to_lowercase();
22 SENSITIVE_KEY_SUBSTRINGS
23 .iter()
24 .any(|needle| lower.contains(needle))
25}
26
27pub fn sanitize(value: &Value) -> Value {
32 match value {
33 Value::Array(items) => Value::Array(items.iter().map(sanitize).collect()),
34 Value::Object(map) => Value::Object(
35 map.iter()
36 .map(|(key, val)| {
37 let sanitized = if is_sensitive_key(key) {
38 Value::String("[REDACTED]".to_string())
39 } else {
40 sanitize(val)
41 };
42 (key.clone(), sanitized)
43 })
44 .collect(),
45 ),
46 other => other.clone(),
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use serde_json::json;
54
55 #[test]
56 fn redacts_sensitive_keys_at_every_depth() {
57 let input = json!({
58 "username": "alice",
59 "password": "hunter2",
60 "nested": { "apiKey": "abc123", "note": "keep me" },
61 "tokens": ["a", "b"],
62 });
63
64 let output = sanitize(&input);
65
66 assert_eq!(output["username"], "alice");
67 assert_eq!(output["password"], "[REDACTED]");
68 assert_eq!(output["nested"]["apiKey"], "[REDACTED]");
69 assert_eq!(output["nested"]["note"], "keep me");
70 }
71
72 #[test]
73 fn leaves_non_sensitive_values_untouched() {
74 let input = json!({ "count": 3, "name": "widget" });
75 assert_eq!(sanitize(&input), input);
76 }
77}