github-mcp 0.1.0

GitHub v3 REST API MCP server, generated by mcpify.
Documentation
// GitHub v3 REST API MCP server — generated by mcpify. Do not hand-edit.

use serde_json::Value;

/// Substrings a JSON key is checked against (case-insensitively) before it's
/// logged. Mirrors `SENSITIVE_KEY_PATTERN` in `targets::typescript`'s
/// `sanitizer.ts`, translated from a regex to substring matching since
/// nothing else in this crate needs a `regex` dependency.
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))
}

/// Deep-clones `value`, replacing any object key matching a sensitive
/// pattern with `"[REDACTED]"`. Call this before logging any user- or
/// API-controlled JSON payload (this crate's `tracing` setup has no
/// built-in path-based redaction the way pino's `redact` option does).
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);
    }
}