secret-guard: Twilio shapes (SID/API key, 32-hex secret assignment) + per-kind warn mode
Windy Text 10-01: a live Twilio auth token sat in bridged-repo tests. Auth tokens are bare 32-hex, so they are matched only when assigned to a name containing token/secret/key/password; hash is of the value alone. SECRET_GUARD_WARN_KINDS lets a new shape warn before it blocks. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,8 @@ import secret_shapes as ss # noqa: E402
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ALLOW_FILE = Path(os.environ.get("SECRET_GUARD_ALLOW", ROOT / "ci" / "secret-guard-allow.yml"))
|
||||
MODE = os.environ.get("SECRET_GUARD_MODE", "warn")
|
||||
# Kinds that only WARN (rolled out warn-first); empty = every kind blocks in block mode.
|
||||
WARN_KINDS = {k for k in os.environ.get("SECRET_GUARD_WARN_KINDS", "").split(",") if k}
|
||||
NEVER = re.compile(r"(^|/)(node_modules|vendor|third_party)/")
|
||||
|
||||
|
||||
@@ -94,8 +96,11 @@ def status_for(findings, whole_tree: bool, grant=()):
|
||||
if not findings:
|
||||
return "success", f"OK: no secret-shaped strings {scope}", None
|
||||
f, n = findings[0], len(findings)
|
||||
state = "failure" if MODE == "block" else "success"
|
||||
lead = "BLOCKED" if MODE == "block" else "⚠ WARN (not blocking)"
|
||||
soft = MODE != "block" or all(x.kind in WARN_KINDS for x in findings)
|
||||
state = "success" if soft else "failure"
|
||||
lead = "⚠ WARN (not blocking)" if soft else "BLOCKED"
|
||||
if not soft:
|
||||
f = next(x for x in findings if x.kind not in WARN_KINDS)
|
||||
return state, f"{lead}: {n} secret-shaped string{'s' if n > 1 else ''} {scope}, e.g. {f.path}:{f.line} {f.match}"[:140], f
|
||||
|
||||
|
||||
|
||||
@@ -18,13 +18,18 @@ PATTERNS: list[tuple[str, re.Pattern[str]]] = [
|
||||
("openai key", re.compile(r"\bsk-(?:proj-|svcacct-)?(?!ant-)[A-Za-z0-9_-]{32,}")),
|
||||
("stripe live key", re.compile(r"\b[rs]k_live_[A-Za-z0-9]{20,}")),
|
||||
("google api key", re.compile(r"\bAIza[0-9A-Za-z_-]{35}(?![0-9A-Za-z_-])")),
|
||||
# Twilio (Windy Text 10-01: a live auth token sat in test files for months). An auth token
|
||||
# is a bare 32-hex with no prefix, so it is only caught when ASSIGNED to a secret-ish name.
|
||||
("twilio sid/api key", re.compile(r"\b(?:AC|SK)[0-9a-f]{32}\b")),
|
||||
("32-hex secret assignment", re.compile(
|
||||
r"(?i)\b[a-z0-9_.-]*(?:token|secret|key|password)[a-z0-9_.-]*[\"']?\s*[:=]\s*[\"']?(?P<v>(?<![0-9a-f])[0-9a-f]{32}(?![0-9a-f]))")),
|
||||
("private key block", re.compile(r"-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY-----")),
|
||||
]
|
||||
|
||||
# git grep -E (POSIX ERE) prefilter: cheap superset of PATTERNS.
|
||||
PREFILTER = ("[0-9]{8,10}:[A-Za-z0-9_-]{35}|gh[pousr]_[A-Za-z0-9]{36}|github_pat_|(AKIA|ASIA)[0-9A-Z]{16}"
|
||||
"|xox[abprs]-|sk-ant-|sk-[A-Za-z0-9_-]{32}|sk-proj-|[rs]k_live_|AIza[0-9A-Za-z_-]{35}"
|
||||
"|-----BEGIN [A-Z ]*PRIVATE KEY-----")
|
||||
"|-----BEGIN [A-Z ]*PRIVATE KEY-----|(AC|SK)[0-9a-f]{32}|[0-9a-fA-F]{32}")
|
||||
|
||||
|
||||
def h8(value: str | bytes) -> str:
|
||||
@@ -36,5 +41,5 @@ def find(text: str) -> list[tuple[str, str]]:
|
||||
out = []
|
||||
for kind, rx in PATTERNS:
|
||||
for m in rx.finditer(text):
|
||||
out.append((kind, h8(m.group(0))))
|
||||
out.append((kind, h8(m.group('v') if 'v' in rx.groupindex else m.group(0))))
|
||||
return out
|
||||
|
||||
Reference in New Issue
Block a user