fix: the pre-commit hook refused every deletion-only commit

secrets.sh exits 2 for "nothing was scanned", which is the right answer for a
commit that only deletes files — a deletion has no added lines to look at. The
hook treated any non-zero as a refusal and printed "possible credential in the
staged changes" while doing it.

So `git rm` was impossible, and the message pointed at a security problem that
did not exist. Found while removing a bin/ directory that had been committed by
accident; the commit only went through because a .gitignore edit happened to be
staged alongside it, which gave the scanner something to read.

The safety property is kept rather than traded away: a 2 still refuses whenever
the staged diff adds any lines, because then the scanner did have something to
look at and checking nothing is exactly the failure exit 2 exists to report.
This commit is contained in:
null 2026-08-18 14:59:24 -05:00
parent 6fc12b6c56
commit c5b3a50dbb
1 changed files with 32 additions and 1 deletions

View File

@ -77,14 +77,45 @@ if git diff --cached --quiet; then
fi fi
# Credentials, before the commit exists. # 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 [ -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 "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 "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 "commit that already exists. If it is not, --allow the path or adjust"
say "the patterns; do not silence the check." say "the patterns; do not silence the check."
exit 1 exit 1
fi 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 else
# Said out loud rather than passed over: a missing scanner reads exactly like # Said out loud rather than passed over: a missing scanner reads exactly like
# a scanner that found nothing. # a scanner that found nothing.