Skip to main content

github_mcp/core/
sanitizer.rs

1// GitHub v3 REST API MCP server — generated by mcpify. Do not hand-edit.
2
3use serde_json::Value;
4
5/// Substrings a JSON key is checked against (case-insensitively) before it's
6/// logged. Mirrors `SENSITIVE_KEY_PATTERN` in `targets::typescript`'s
7/// `sanitizer.ts`, translated from a regex to substring matching since
8/// nothing else in this crate needs a `regex` dependency.
9const 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
27/// Deep-clones `value`, replacing any object key matching a sensitive
28/// pattern with `"[REDACTED]"`. Call this before logging any user- or
29/// API-controlled JSON payload (this crate's `tracing` setup has no
30/// built-in path-based redaction the way pino's `redact` option does).
31pub 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}