diff --git a/scripts/pre-commit-secret-scan.sh b/scripts/pre-commit-secret-scan.sh new file mode 100755 index 0000000..821432c --- /dev/null +++ b/scripts/pre-commit-secret-scan.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# pre-commit-secret-scan — blocks commits that would add real credentials. +# +# Deployed via core.hooksPath (e.g. ~/.config/git/hooks/pre-commit) so it runs +# for EVERY repository on the machine. Fails OPEN (never blocks) if git itself +# is unavailable. Override only when you're sure: SKIP_SECRET_SCAN=1 git commit +# +# Pattern set covers the key formats used in this house: OpenAI sk-proj-, +# OpenRouter sk-or-v1-, GitHub ghp_, Tavily tvly-, Gemini AIza*, ntfy-style +# AQ.Ab*, JWTs (eyJ...), generic sk-... and Bearer tokens, private key blocks. +# Lines that only REFERENCE keys (nix builtins.getEnv, literal '...' +# placeholders) are ignored. + +[ -n "$SKIP_SECRET_SCAN" ] && exit 0 + +# fail-open: not a git repo, or git unavailable +git rev-parse --git-dir >/dev/null 2>&1 || exit 0 + +# nothing staged -> nothing to check +[ -n "$(git diff --cached --name-only 2>/dev/null)" ] || exit 0 + +hits=$(git diff --cached -U0 --no-color 2>/dev/null | awk ' + /^diff --git/ { f=$3; sub(/^a\//,"",f) } + /^\+/ && $0 !~ /^\+\+\+/ { + l = substr($0, 2) + if (l ~ /getEnv|\.\.\./) next + if (l ~ /sk-proj-[A-Za-z0-9._-]{20,}|sk-or-v1-[A-Za-z0-9._-]{20,}|ghp_[A-Za-z0-9]{20,}|tvly-[A-Za-z0-9]{20,}|AIza[A-Za-z0-9_-]{30,}|AQ\.Ab[A-Za-z0-9._-]{20,}|eyJ[A-Za-z0-9._-]{30,}|sk-[A-Za-z0-9]{25,}|Bearer [A-Za-z0-9._-]{25,}|BEGIN [A-Z ]*PRIVATE KEY/) + print f ": " l + }') + +[ -z "$hits" ] && exit 0 + +echo "⛔ pre-commit secret scan BLOCKED — possible credentials in staged changes:" >&2 +echo "$hits" | sed -E ' + s/(sk-proj-[A-Za-z0-9._-]{6})[A-Za-z0-9._-]+/\1…/g + s/(sk-or-v1-[A-Za-z0-9._-]{6})[A-Za-z0-9._-]+/\1…/g + s/(ghp_[A-Za-z0-9]{4})[A-Za-z0-9]+/\1…/g + s/(tvly-[A-Za-z0-9]{4})[A-Za-z0-9]+/\1…/g + s/(AIza[A-Za-z0-9_-]{4})[A-Za-z0-9_-]+/\1…/g + s/(AQ\.Ab[A-Za-z0-9._-]{4})[A-Za-z0-9._-]+/\1…/g + s/(eyJ[A-Za-z0-9._-]{8})[A-Za-z0-9._-]+/\1…/g + s/(sk-[A-Za-z0-9]{6})[A-Za-z0-9]+/\1…/g + s/(Bearer [A-Za-z0-9._-]{6})[A-Za-z0-9._-]+/\1…/g' >&2 +echo "→ Remove the secret and re-commit. If it's a false positive: SKIP_SECRET_SCAN=1 git commit" >&2 +exit 1