add pre-commit-secret-scan hook (blocks commits containing real credentials; install via core.hooksPath)
This commit is contained in:
45
scripts/pre-commit-secret-scan.sh
Executable file
45
scripts/pre-commit-secret-scan.sh
Executable file
@@ -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
|
||||
Reference in New Issue
Block a user