| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576 |
- """日志、请求头与运行记录脱敏(任务书 P1-B 安全要求)。
- 规则:
- - 已知 Secret / 密码 / Token 值出现即替换为 ***REDACTED***;
- - 敏感 Header 值只保留 AccessKey 后四位,其余整体遮蔽;
- - 连接串中 pwd=/password= 段遮蔽。
- """
- from __future__ import annotations
- import re
- REDACTED = "***REDACTED***"
- SENSITIVE_HEADERS = {
- "authorization", "x-signature", "x-access-key", "cookie", "set-cookie",
- "proxy-authorization", "x-csrf-token",
- }
- # 键名命中即整体遮蔽(用于运行记录/报告中的嵌套字段)
- SENSITIVE_KEY_RE = re.compile(
- r"(password|passwd|pwd|secret|token|signature|access[-_]?key|credential|jwt)", re.IGNORECASE)
- _CONN_PWD_RE = re.compile(r"(pwd|password)\s*=\s*[^;]+", re.IGNORECASE)
- _BEARER_RE = re.compile(r"(Bearer|Basic)\s+[A-Za-z0-9\-._~+/=]+", re.IGNORECASE)
- def mask_access_key(access_key: str | None) -> str:
- """报告中 AccessKey 只保留后四位。"""
- if not access_key:
- return ""
- return f"****{access_key[-4:]}" if len(access_key) > 4 else "****"
- def redact_headers(headers: dict, secrets: list[str] | None = None) -> dict:
- out = {}
- for k, v in headers.items():
- if str(k).lower() in SENSITIVE_HEADERS:
- out[k] = mask_access_key(str(v)) if str(k).lower() == "x-access-key" else REDACTED
- else:
- out[k] = redact_text(str(v), secrets)
- return out
- def redact_text(text: str, secrets: list[str] | None = None) -> str:
- if not text:
- return text
- result = text
- for secret in secrets or []:
- if secret and secret in result:
- result = result.replace(secret, REDACTED)
- result = _CONN_PWD_RE.sub(lambda m: f"{m.group(1)}={REDACTED}", result)
- result = _BEARER_RE.sub(lambda m: f"{m.group(1)} {REDACTED}", result)
- return result
- def redact_obj(obj, secrets: list[str] | None = None):
- """递归脱敏 dict/list/str,用于运行记录与报告。
- - 敏感 Header 名:Authorization/签名类整体遮蔽,AccessKey 只留后四位;
- - 键名命中 SENSITIVE_KEY_RE 的字段整体遮蔽;
- - 其余字符串按已知 Secret 值与连接串/Bearer 模式遮蔽。
- """
- if isinstance(obj, dict):
- out = {}
- for k, v in obj.items():
- key = str(k).lower()
- if key in SENSITIVE_HEADERS or SENSITIVE_KEY_RE.search(key):
- out[k] = mask_access_key(str(v)) if "access" in key and "key" in key else REDACTED
- else:
- out[k] = redact_obj(v, secrets)
- return out
- if isinstance(obj, list):
- return [redact_obj(v, secrets) for v in obj]
- if isinstance(obj, str):
- return redact_text(obj, secrets)
- return obj
|