36 lines
1.2 KiB
Python
36 lines
1.2 KiB
Python
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
|