35 lines
1.4 KiB
Python
35 lines
1.4 KiB
Python
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
|