MediaRouter / app /social /security.py
basyx's picture
Upload 236 files
e1104b3 verified
Raw
History Blame Contribute Delete
2.19 kB
"""Small, transport-facing safeguards for social provider data.
Provider access tokens are credentials, not application data. This module is
intentionally used at every boundary that can serialize provider-controlled
metadata (REST, MCP, SDK, n8n, frontend, and audit records).
"""
from __future__ import annotations
import re
from typing import Any
_SENSITIVE_KEY_PARTS = frozenset(
{
"access_token",
"refresh_token",
"id_token",
"token",
"secret",
"authorization",
"cookie",
"password",
"api_key",
"credential",
}
)
_BEARER = re.compile(r"(?i)\bbearer\s+[A-Za-z0-9._~+/=-]+")
_ASSIGNED_SECRET = re.compile(
r"(?i)(access[_-]?token|refresh[_-]?token|id[_-]?token|client[_-]?secret|"
r"authorization|api[_-]?key|password|secret|credential)"
r"([\"']?\s*[:=]\s*[\"']?)([^\"'\s,&}]+)"
)
def public_provider_data(value: Any) -> Any:
"""Recursively omit credential-like fields from externally visible data.
This is defense in depth. TokenService remains the only supported
credential reader, but provider responses and future metadata additions
must never be able to bypass that contract accidentally.
"""
if isinstance(value, dict):
return {
str(key): public_provider_data(item)
for key, item in value.items()
if not _is_sensitive_key(str(key))
}
if isinstance(value, list):
return [public_provider_data(item) for item in value]
if isinstance(value, tuple):
return [public_provider_data(item) for item in value]
if isinstance(value, str):
return redact_sensitive_text(value)
return value
def _is_sensitive_key(key: str) -> bool:
normalized = key.strip().lower().replace("-", "_")
return any(part in normalized for part in _SENSITIVE_KEY_PARTS)
def redact_sensitive_text(value: str) -> str:
"""Remove recognizable credentials embedded in provider-controlled text."""
redacted = _BEARER.sub("Bearer [REDACTED]", value)
return _ASSIGNED_SECRET.sub(
lambda match: f"{match.group(1)}{match.group(2)}[REDACTED]",
redacted,
)