mirror of
https://github.com/furyhawk/agent_delta.git
synced 2026-07-21 10:15:36 +00:00
79 lines
2.9 KiB
Python
79 lines
2.9 KiB
Python
"""Logging utilities — PII redaction filter for GDPR/compliance safety."""
|
|
|
|
import logging
|
|
import re
|
|
from typing import ClassVar
|
|
|
|
|
|
class PiiRedactionFilter(logging.Filter):
|
|
"""Logging filter that redacts personally identifiable information.
|
|
|
|
Automatically scrubs email addresses, JWT tokens, API keys, bearer tokens,
|
|
and password-like values from log messages to prevent PII leaks to
|
|
log aggregators (Datadog, CloudWatch, Logfire, etc.).
|
|
|
|
Usage:
|
|
logging.getLogger().addFilter(PiiRedactionFilter())
|
|
"""
|
|
|
|
PATTERNS: ClassVar[list[tuple[re.Pattern[str], str]]] = [
|
|
# Email addresses
|
|
(re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"), "[EMAIL_REDACTED]"),
|
|
# JWT tokens (header.payload.signature)
|
|
(
|
|
re.compile(r"eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+"),
|
|
"[JWT_REDACTED]",
|
|
),
|
|
# OpenAI API keys
|
|
(re.compile(r"sk-[a-zA-Z0-9]{20,}"), "[API_KEY_REDACTED]"),
|
|
# Anthropic API keys
|
|
(re.compile(r"sk-ant-[a-zA-Z0-9_-]{20,}"), "[API_KEY_REDACTED]"),
|
|
# Generic long hex/base64 secrets (40+ chars, likely tokens)
|
|
(
|
|
re.compile(
|
|
r"(?:token|key|secret|password|authorization)[=: ]+['\"]?([A-Za-z0-9_/+=.-]{40,})",
|
|
re.IGNORECASE,
|
|
),
|
|
"[SECRET_REDACTED]",
|
|
),
|
|
# Bearer tokens in headers
|
|
(re.compile(r"Bearer\s+[A-Za-z0-9._~+/=-]{10,}"), "Bearer [TOKEN_REDACTED]"),
|
|
# Password/secret in key=value or key: value patterns
|
|
(
|
|
re.compile(
|
|
r"(password|passwd|pwd|secret_key|api_key|apikey|auth_token|access_token|refresh_token)"
|
|
r"[\s]*[=:]\s*['\"]?\S+['\"]?",
|
|
re.IGNORECASE,
|
|
),
|
|
r"\1=[REDACTED]",
|
|
),
|
|
]
|
|
|
|
def filter(self, record: logging.LogRecord) -> bool:
|
|
"""Redact PII from log record message and args."""
|
|
if isinstance(record.msg, str):
|
|
record.msg = self._redact(record.msg)
|
|
if record.args:
|
|
if isinstance(record.args, dict):
|
|
record.args = {
|
|
k: self._redact(v) if isinstance(v, str) else v for k, v in record.args.items()
|
|
}
|
|
elif isinstance(record.args, tuple):
|
|
record.args = tuple(
|
|
self._redact(a) if isinstance(a, str) else a for a in record.args
|
|
)
|
|
return True
|
|
|
|
def _redact(self, value: str) -> str:
|
|
for pattern, replacement in self.PATTERNS:
|
|
value = pattern.sub(replacement, value)
|
|
return value
|
|
|
|
|
|
def setup_logging() -> None:
|
|
"""Configure root logger with PII redaction filter."""
|
|
root_logger = logging.getLogger()
|
|
# Avoid adding duplicate filters
|
|
if not any(isinstance(f, PiiRedactionFilter) for f in root_logger.filters):
|
|
root_logger.addFilter(PiiRedactionFilter())
|