from __future__ import annotations import re from collections.abc import Mapping, Sequence _SECRET_KEYS = re.compile(r"token|secret|password|authorization|cookie|api[_-]?key", re.I) _BEARER = re.compile(r"(?i)\bBearer\s+[A-Za-z0-9._~+\-/]+=*") _URL_CREDENTIALS = re.compile(r"(?i)(https?://)([^/@\s:]+):([^/@\s]+)@") _ASSIGNMENT = re.compile( r"(?i)\b(token|secret|password|authorization|api[_-]?key)\s*([=:])\s*([^\s,;]+)" ) def redact_text(value: object, *, limit: int = 8_000) -> str: text = str(value) text = _BEARER.sub("Bearer [REDACTED]", text) text = _URL_CREDENTIALS.sub(r"\1[REDACTED]@", text) text = _ASSIGNMENT.sub(r"\1\2[REDACTED]", text) if len(text) > limit: return text[:limit] + "\n...[truncated]" return text def redact_data(value: object) -> object: if isinstance(value, Mapping): return { str(key): "[REDACTED]" if _SECRET_KEYS.search(str(key)) else redact_data(item) for key, item in value.items() } if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)): return [redact_data(item) for item in value] if isinstance(value, str): return redact_text(value) return value