mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-17 03:59:28 +00:00
Comment-only changes outside learn/, plane/ and cli.py: - model/pipeline.py: the `judge` its docstring contrasts with is the deleted model/judge.py, now named as such. - fusion.py: the panels are deterministic and only review and vote; the "critique and revise" step belonged to the retired LLM panels. - mcp.py: hunter tools stay inside the scan's stages, not "harness stages". - fusion, mcp, gate, redaction, skills: their "Phase N" tags now say which plan they cite (the retired openrouter-sast-harness). Requirement citations to retired specs stay: they explain where the code came from. No behaviour change; the byte-identical benchmark golden passes. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
46 lines
2.1 KiB
Python
46 lines
2.1 KiB
Python
"""Secret redaction for traces and reports (Phase 15 hardening of the retired openrouter-sast-harness plan).
|
|
|
|
Scan artifacts (trace events, reports) can quote source code that contains live
|
|
credentials. This module masks well-known secret shapes before they are persisted, so
|
|
a finding's evidence never leaks a key into an artifact an analyst later shares.
|
|
Conservative by design: it targets recognizable secret formats, not arbitrary text.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
REDACTED = "***REDACTED***"
|
|
|
|
# (compiled pattern, replacement). Replacements keep any non-secret prefix group so the
|
|
# redaction is legible (e.g. `api_key = ***REDACTED***`, `Authorization: Bearer ***REDACTED***`).
|
|
_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
|
|
# PEM private key blocks.
|
|
(re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----", re.DOTALL), REDACTED),
|
|
# Authorization: Bearer <token>
|
|
(re.compile(r"(Bearer\s+)[A-Za-z0-9._\-]{12,}"), r"\1" + REDACTED),
|
|
# Provider API keys: OpenAI/Anthropic sk-..., AWS AKIA..., GitHub gh*_..., Slack xox*, Google AIza...
|
|
(re.compile(r"\bsk-[A-Za-z0-9._\-]{16,}"), REDACTED),
|
|
(re.compile(r"\bAKIA[0-9A-Z]{16}\b"), REDACTED),
|
|
(re.compile(r"\bgh[pousr]_[A-Za-z0-9]{20,}"), REDACTED),
|
|
(re.compile(r"\bxox[baprs]-[A-Za-z0-9-]{10,}"), REDACTED),
|
|
(re.compile(r"\bAIza[A-Za-z0-9_\-]{20,}"), REDACTED),
|
|
# Credentials embedded in a URL: scheme://user:secret@host
|
|
(re.compile(r"([a-zA-Z][a-zA-Z0-9+.\-]*://[^\s:/@]+:)[^\s:/@]{3,}(@)"), r"\1" + REDACTED + r"\2"),
|
|
# Generic `key = "value"` assignments for sensitive names.
|
|
(
|
|
re.compile(r"(?i)\b(api[_-]?key|secret|token|password|passwd|pwd|access[_-]?key)\b(\s*[=:]\s*)(['\"]?)[A-Za-z0-9/+_\-]{8,}\3"),
|
|
r"\1\2\3" + REDACTED + r"\3",
|
|
),
|
|
)
|
|
|
|
|
|
def redact_secrets(text: str) -> str:
|
|
"""Return ``text`` with recognizable secrets masked. Idempotent and pattern-scoped."""
|
|
for pattern, replacement in _PATTERNS:
|
|
text = pattern.sub(replacement, text)
|
|
return text
|
|
|
|
|
|
__all__ = ["REDACTED", "redact_secrets"]
|