fix: reconcile the tests with the POSIX branches they never ran #4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # The second half of the guard. The pre-push hook only protects a machine that | |
| # has it installed; this protects the repository itself, including a push from a | |
| # fork, a web edit, or a contributor who never ran `pre-commit install`. | |
| name: Secret scan | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| scan: | |
| name: Refuse a credential or a private path | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Check out repository code | |
| # Full history, because the question is not only what the tip carries. | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Scan the tracked tree | |
| run: python scripts/secret_scan.py | |
| - name: Scan every blob in history | |
| # A tip that is clean says nothing about a commit three months back, and a | |
| # public repository publishes both. | |
| run: | | |
| python - <<'PY' | |
| import re, subprocess, sys | |
| PATTERNS = { | |
| "Telegram bot token": rb"\b\d{8,10}:[A-Za-z0-9_-]{30,45}\b", | |
| "Telethon session string": rb"\b1[A-Za-z0-9+/_-]{200,}\b", | |
| "private key block": rb"-----BEGIN [A-Z ]*PRIVATE KEY-----", | |
| "AWS access key": rb"\bAKIA[0-9A-Z]{16}\b", | |
| "GitHub token": rb"\bgh[pousr]_[A-Za-z0-9]{36,}\b", | |
| } | |
| compiled = {name: re.compile(p) for name, p in PATTERNS.items()} | |
| names = {} | |
| listing = subprocess.run(["git", "rev-list", "--objects", "--all"], | |
| capture_output=True, text=True, errors="replace").stdout | |
| for line in listing.splitlines(): | |
| parts = line.split(" ", 1) | |
| if len(parts) == 2 and parts[1].strip(): | |
| names.setdefault(parts[0], parts[1].strip()) | |
| dump = subprocess.run( | |
| ["git", "cat-file", "--batch-all-objects", "--batch", "--buffer", "--unordered"], | |
| capture_output=True).stdout | |
| findings, scanned, i = [], 0, 0 | |
| while i < len(dump): | |
| nl = dump.find(b"\n", i) | |
| if nl == -1: | |
| break | |
| header = dump[i:nl].split() | |
| if len(header) < 3: | |
| i = nl + 1 | |
| continue | |
| oid, kind, size = header[0].decode(), header[1].decode(), int(header[2]) | |
| body = dump[nl + 1:nl + 1 + size] | |
| i = nl + 1 + size + 1 | |
| if kind != "blob" or oid not in names: | |
| continue | |
| scanned += 1 | |
| for name, pattern in compiled.items(): | |
| if pattern.search(body): | |
| findings.append(f"{names[oid]} (object {oid[:12]}): {name}") | |
| print(f"scanned {scanned} blob(s) across all refs") | |
| if findings: | |
| print("history carries credential-shaped content:", file=sys.stderr) | |
| for finding in sorted(set(findings)): | |
| print(f" {finding}", file=sys.stderr) | |
| sys.exit(1) | |
| print("no credential-shaped content in any reachable blob") | |
| PY |