OpenSpace/openspace/skill_engine/evidence/redaction.py
2026-07-17 11:43:42 +08:00

80 lines
2.5 KiB
Python

"""Deterministic redaction for evidence previews and metadata."""
from __future__ import annotations
import re
from typing import Any, Mapping
SECRET_REDACTION = "[REDACTED_SECRET]"
_SECRET_KEY_RE = re.compile(
r"(api[_-]?key|token|authorization|cookie|secret|password|credential)",
re.IGNORECASE,
)
_SECRET_PATTERNS: tuple[re.Pattern[str], ...] = (
re.compile(
r"(?i)\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*)"
r"\s*=\s*([^\s\"']+)"
),
re.compile(
r"(?i)\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*)"
r"\s*:\s*([^\s\"']+)"
),
re.compile(r"(?i)\bAuthorization\s*:\s*Bearer\s+[A-Za-z0-9._~+/=-]+"),
re.compile(r"(?i)\bCookie\s*:\s*[^;\n]+(?:;[^\n]+)?"),
re.compile(r"\bsk-[A-Za-z0-9_-]{16,}\b"),
)
def contains_secret(value: Any) -> bool:
if isinstance(value, Mapping):
for key, item in value.items():
if _SECRET_KEY_RE.search(str(key)) and not _is_redacted_value(item):
return True
if contains_secret(item):
return True
return False
if isinstance(value, (list, tuple, set)):
return any(contains_secret(item) for item in value)
if value is None:
return False
text = str(value).replace(SECRET_REDACTION, "")
return any(pattern.search(text) for pattern in _SECRET_PATTERNS)
def _is_redacted_value(value: Any) -> bool:
return isinstance(value, str) and value.strip() == SECRET_REDACTION
def redact_text(text: str) -> str:
redacted = str(text)
redacted = _SECRET_PATTERNS[0].sub(
lambda match: f"{match.group(1)}={SECRET_REDACTION}",
redacted,
)
redacted = _SECRET_PATTERNS[1].sub(
lambda match: f"{match.group(1)}: {SECRET_REDACTION}",
redacted,
)
for pattern in _SECRET_PATTERNS[2:]:
redacted = pattern.sub(SECRET_REDACTION, redacted)
return redacted
def redact_metadata(value: Any) -> Any:
if isinstance(value, Mapping):
result: dict[str, Any] = {}
for key, item in value.items():
key_text = str(key)
if _SECRET_KEY_RE.search(key_text):
result[key_text] = SECRET_REDACTION
else:
result[key_text] = redact_metadata(item)
return result
if isinstance(value, list):
return [redact_metadata(item) for item in value]
if isinstance(value, tuple):
return [redact_metadata(item) for item in value]
if isinstance(value, str):
return redact_text(value)
return value