import re from typing import Any ENV_STYLE_PATTERN = re.compile( r"(?i)(api[_-]?key|token|secret|password|passwd|credential|private[_-]?key)\s*[:=]\s*([\"\']?[^\s,\"\']+[\"\']?)", ) URL_CREDENTIAL_PATTERN = re.compile(r"://([^:@]+):([^@]+)@") JWT_PATTERN = re.compile(r"(eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*\.\S+)") AUTH_HEADER_PATTERN = re.compile(r"(bearer\s+|basic\s+|api[_-]?key\s+)([a-zA-Z0-9+/=_-]+)", re.IGNORECASE) REDACTED = "***" MAX_SANITIZE_DEPTH = 20 SENSITIVE_CONFIG_KEYS = frozenset( { "app_secret", "bot_token", "signing_secret", "encrypt_key", "verification_token", "api_key", "access_token", "refresh_token", "private_key", "webhook_secret", "client_secret", "secret", "password", "passwd", "auth_token", "jwt", "credential", "app_key", "master_key", "license_key", } ) _SENSITIVE_KEYWORD_RE = re.compile( r"(?:^|[_-])(" r"app[_-]?secret|bot[_-]?token|signing[_-]?secret|encrypt[_-]?key|" r"verification[_-]?token|api[_-]?key|access[_-]?token|refresh[_-]?token|" r"private[_-]?key|webhook[_-]?secret|client[_-]?secret|secret|" r"password|passwd|auth[_-]?token|jwt|credential|" r"app[_-]?key|master[_-]?key|license[_-]?key" r")(?:$|[_-])" ) def _is_sensitive_key(key: str) -> bool: key_lower = key.lower().replace("-", "_") if key_lower in SENSITIVE_CONFIG_KEYS: return True return bool(_SENSITIVE_KEYWORD_RE.search(key_lower)) def sanitize_text(text: str) -> str: text = ENV_STYLE_PATTERN.sub(r"\1=***", text) text = URL_CREDENTIAL_PATTERN.sub(r"://\1:***@", text) text = JWT_PATTERN.sub(REDACTED, text) text = AUTH_HEADER_PATTERN.sub(r"\1***", text) return text def sanitize_dict(data: dict, _depth: int = 0) -> dict: if _depth > MAX_SANITIZE_DEPTH: return {"__redacted__": REDACTED} result = {} for key, value in data.items(): result[key] = _sanitize_value(key, value, _depth) return result def _sanitize_value(key: str, value: Any, _depth: int = 0) -> Any: if _depth > MAX_SANITIZE_DEPTH: return REDACTED if isinstance(value, dict): return sanitize_dict(value, _depth + 1) if isinstance(value, list): return [_sanitize_value("", item, _depth + 1) for item in value] if isinstance(value, str): if _is_sensitive_key(key): return REDACTED return sanitize_text(value) return value def sanitize_log_record(record_data: dict) -> dict: return sanitize_dict(record_data) def sanitize_for_json_dump(obj: Any, _depth: int = 0) -> Any: if _depth > MAX_SANITIZE_DEPTH: return REDACTED if isinstance(obj, dict): return {k: sanitize_for_json_dump(v, _depth + 1) for k, v in obj.items()} if isinstance(obj, list): return [sanitize_for_json_dump(v, _depth + 1) for v in obj] if isinstance(obj, str): return sanitize_text(obj) return obj sanitize_config_object = sanitize_dict