diff --git a/.githooks/pre-commit b/.githooks/pre-commit index f81e005..3c1025b 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -77,14 +77,45 @@ if git diff --cached --quiet; then fi # Credentials, before the commit exists. +# +# The two non-zero exits mean different things and the hook has to tell them +# apart, which it did not at first: +# +# 1 a credential shape was found — refuse, always. +# 2 NOTHING WAS SCANNED. Usually alarming, and *expected* for a commit that +# only deletes files, because a deletion has no added lines to look at. +# +# Treating 2 as a refusal made every deletion-only commit impossible, and said +# "possible credential in the staged changes" while doing it — a wrong and +# frightening message for a plain `git rm`. Found while removing a directory of +# build output that had been committed by accident. +# +# So a 2 is checked rather than trusted: if the staged diff adds any lines then +# the scanner had something to look at, and a 2 is a real problem. if [ -f scripts/secrets.sh ]; then - if ! bash scripts/secrets.sh; then + bash scripts/secrets.sh + secrets_rc=$? + + if [ "$secrets_rc" -eq 1 ]; then say "possible credential in the staged changes — commit refused." say "If it is real, ROTATE IT. Deleting the line does not remove it from a" say "commit that already exists. If it is not, --allow the path or adjust" say "the patterns; do not silence the check." exit 1 fi + + if [ "$secrets_rc" -eq 2 ]; then + added=$(git diff --cached --numstat | awk '{ if ($1 != "-") total += $1 } END { print total + 0 }') + if [ "$added" -gt 0 ]; then + say "the secret scan checked NOTHING while ${added} line(s) are being added." + say "That is not a pass — see docs/TOOLS.md on exit code 2." + exit 1 + fi + say "no added lines to scan — this commit only removes content." + elif [ "$secrets_rc" -ne 0 ]; then + say "secrets.sh exited ${secrets_rc} — commit refused." + exit 1 + fi else # Said out loud rather than passed over: a missing scanner reads exactly like # a scanner that found nothing.