Files
Unraid-AppOps-Gateway-Public/backend/app/core/redaction.py
T

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