-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsensitive_data.py
More file actions
89 lines (81 loc) · 3.1 KB
/
Copy pathsensitive_data.py
File metadata and controls
89 lines (81 loc) · 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
"""Canonical bounded secret redaction for persistence, memory, and telemetry."""
from __future__ import annotations
import re
from collections.abc import Mapping
from typing import Any
REDACTED = "[REDACTED]"
SENSITIVE_QUERY_KEYS = frozenset(
{
"access_token",
"api_key",
"apikey",
"auth",
"authorization",
"client_secret",
"code",
"credential",
"id_token",
"key",
"password",
"passwd",
"refresh_token",
"secret",
"session",
"sessionid",
"signature",
"token",
}
)
_SENSITIVE_KEY = re.compile(
r"(?i)(authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|"
r"password|passwd|secret|private[_-]?key|cookie|session|credential|token|"
r"client[_-]?(?:id|secret)|consent[_-]?string|tc[_-]?string|gclid)"
)
_TOKEN_PATTERNS = (
re.compile(r"\bsk-[A-Za-z0-9_-]{12,}\b"),
re.compile(r"\bgh[pousr]_[A-Za-z0-9]{12,}\b"),
re.compile(r"\bAKIA[A-Z0-9]{12,}\b"),
re.compile(r"\bAIza[A-Za-z0-9_-]{12,}\b"),
re.compile(r"(?i)\bBearer\s+[A-Za-z0-9._~+/-]+=*"),
)
_LABELED_SECRET = re.compile(
r"(?i)(\b(?:authorization|api[_ -]?key|access[_ -]?token|refresh[_ -]?token|"
r"password|passwd|secret|private[_ -]?key|cookie|set-cookie|session|credential|"
r"token|auth|signature|"
r"client[_ -]?secret)\s*[:=]\s*)([^\s,;&]+)"
)
_AUTH_SCHEME_SECRET = re.compile(r"(?i)(\b(?:authorization\s*:\s*)?(?:bearer|basic)\s+)([^\s,;]+)")
_SENSITIVE_QUERY = re.compile(
r"(?i)([?&](?:access_token|api_key|apikey|auth|authorization|client_secret|"
r"password|refresh_token|secret|session|signature|token)=)([^&#\s]*)"
)
def redact(value: Any, *, key: str = "") -> Any:
"""Recursively redact bounded credential forms without logging the input."""
if key and _SENSITIVE_KEY.search(key):
return REDACTED
if isinstance(value, Mapping):
return {str(item_key): redact(item, key=str(item_key)) for item_key, item in value.items()}
if isinstance(value, (list, tuple)):
return [redact(item) for item in value]
if not isinstance(value, str):
return value
result = _SENSITIVE_QUERY.sub(r"\1[REDACTED]", value)
result = _AUTH_SCHEME_SECRET.sub(r"\1[REDACTED]", result)
result = _LABELED_SECRET.sub(r"\1[REDACTED]", result)
for pattern in _TOKEN_PATTERNS:
result = pattern.sub(REDACTED, result)
return result
def redact_evidence_fields(
source: str | None,
run_id: str | None,
payload: Mapping[str, Any],
scope: Mapping[str, Any],
) -> tuple[str | None, str | None, dict[str, Any], dict[str, Any]]:
"""Redact an evidence record while preserving its required mapping shapes."""
safe_payload = redact(payload)
safe_scope = redact(scope)
if not isinstance(safe_payload, dict) or not isinstance(safe_scope, dict):
raise TypeError("sanitized evidence payload and scope must remain mappings")
safe_source = None if source is None else str(redact(source))
safe_run_id = None if run_id is None else str(redact(run_id))
return safe_source, safe_run_id, safe_payload, safe_scope