#!/usr/bin/env python3 """secrets0.py — scan an archive of OTHER PEOPLE'S posts before you republish it. secrets0.py scan ... print counts, never the secret WHY. Republishing someone else's bodies copies their mistakes into your mirrors. A live credential inside one post becomes, after your kindness, a credential on three hosts and in every node that trusted your archive. The scan costs a second; the apology costs a rotation on somebody else's account. WHAT IT PRINTS. Pattern name, count, and the seq/author of each hit — never the matched text. A tool that echoes the secret to prove it found the secret has simply published it again, this time with a helpful label. WHAT IT CANNOT DO, stated so nobody treats a clean run as safety: these are eight shapes of well-known credentials. A key in an unusual format, a password in prose, a token split across lines, or a private URL with an embedded credential will all pass. "0 hits across eight patterns" is exactly that sentence and no more. """ import json, re, sys, collections PATTERNS = { "bearer-header": r"Authorization:\s*Bearer\s+[A-Za-z0-9_\-\.]{16,}", "openai-sk": r"\bsk-[A-Za-z0-9]{20,}", "anthropic-sk": r"\bsk-ant-[A-Za-z0-9_\-]{20,}", "github-pat": r"\bgh[pousr]_[A-Za-z0-9]{20,}", "aws-akid": r"\bAKIA[0-9A-Z]{16}\b", "slack-token": r"\bxox[baprs]-[A-Za-z0-9-]{10,}", "jwt": r"\beyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}", "private-key": r"-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----", } RE = {k: re.compile(v) for k, v in PATTERNS.items()} def rows(path): """Accept JSONL, a JSON list, or a {"items": [...]} export. Anything with a body field is a row; anything else is skipped and counted, because a scanner that silently ignores half a file is worse than no scanner.""" raw = sys.stdin.read() if path == "-" else open(path, encoding="utf-8").read() out, skipped = [], 0 s = raw.lstrip() if s.startswith("{"): # Do not sniff for the word "items" in the first 200 bytes. A pretty-printed # export puts it on line 40, the sniff fails, the file falls through to the # JSONL branch, every line fails to parse, and the tool prints "0 hits" over # ZERO scanned bodies — a clean bill of health for a file it never read. # Found by running this scanner on a second file, not by reading it. obj = json.loads(raw) out = next((v for v in obj.values() if isinstance(v, list) and v and isinstance(v[0], dict)), []) elif s.startswith("["): out = json.loads(raw) else: for line in raw.split("\n"): if not line.strip(): continue try: out.append(json.loads(line)) except Exception: skipped += 1 return out, skipped def main(argv): if not argv: print(__doc__) return for path in argv: items, skipped = rows(path) hits = collections.Counter() where = collections.defaultdict(list) scanned = 0 for it in items: body = it.get("body") or "" if not body: continue scanned += 1 for name, rx in RE.items(): if rx.search(body): hits[name] += 1 where[name].append(f"seq {it.get('seq')} @{it.get('author')}") print(f"{path}: {scanned} bodies scanned" + (f", {skipped} unparsable lines skipped" if skipped else "")) if scanned == 0: print(" REFUSING TO REPORT: 0 bodies were scanned. A scan of nothing is not\n" " a clean scan — check the file format before trusting this run.") continue if not hits: print(" 0 hits across 8 patterns — which is that sentence and no more:\n" " an unusual key format, a password in prose or a token split across\n" " lines would all pass this scan.") for name in sorted(hits): print(f" {name}: {hits[name]}") for w in where[name][:20]: print(f" {w}") print() if __name__ == "__main__": main(sys.argv[1:])