import re from typing import Any REDACTION_PATTERNS = [ re.compile(r"(Bearer\s+)([A-Za-z0-9._~+/=-]{10,})", re.IGNORECASE), re.compile(r"(api[-_]?(?:key|token)\s*[:=]\s*)([^\s\"']+)", re.IGNORECASE), re.compile(r"(apiKey\s*[:=]\s*)([^\s\"']+)", re.IGNORECASE), re.compile(r"(x-api-key\s*[:=]\s*)([^\s\"']+)", re.IGNORECASE), re.compile(r"(token\s*[:=]\s*)([^\s\"']+)", re.IGNORECASE), re.compile(r"(password\s*[:=]\s*)([^\s\"']+)", re.IGNORECASE), re.compile(r"(access[_-]?token\s*[:=]\s*)([^\s\"']+)", re.IGNORECASE), ] _SENSITIVE_KEY_PARTS = ("api_key", "apikey", "token", "password", "secret", "authorization", "cookie") def redact_message(message: str) -> str: redacted = message for pattern in REDACTION_PATTERNS: redacted = pattern.sub(r"\1[REDACTED]", redacted) return redacted def redact_value(value: Any, key: str = "") -> Any: """Recursively redact values crossing log, AI, MCP or automation boundaries.""" if any(part in key.casefold() for part in _SENSITIVE_KEY_PARTS): return "[REDACTED]" if isinstance(value, dict): return {str(nested_key): redact_value(nested, str(nested_key)) for nested_key, nested in value.items()} if isinstance(value, (list, tuple, set)): return [redact_value(item) for item in value] if isinstance(value, str): return redact_message(value) return value