fix(git-gate): scan $new --not --all for every push, not $old..$new
test / unit (push) Successful in 32s
test / integration (push) Successful in 31s
lint / lint (push) Successful in 43s
Update Quality Badges / update-badges (push) Successful in 38s
test / coverage (push) Successful in 40s

The pre-receive hook scanned existing-branch updates with the delta range
$old..$new. On a rebase / non-fast-forward force-push onto an advanced main,
$old is no longer an ancestor of $new, so $old..$new expands to all of main's
new history — including the deliberate sandbox-escape gitleaks fixtures — and
the push is rejected on commits that belong to main, not the branch.

Unify the range on `$new --not --all` for every non-delete push (this is the
deferred open question from PRD 0028, which already applied it to new refs
for #106). It scans only the commits the push introduces and is
security-equivalent: the bare repo's refs come only from trusted upstream
mirror-fetch and gitleaks-gated pushes, so an excluded commit is
already-upstream or already-scanned. It is also more correct for
non-fast-forward pushes, where $old..$new can skip commits off the direct path.

Fixes #421

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1qRZTJC6qgBsUSjNrBdkX
This commit was merged in pull request #422.
This commit is contained in:
2026-07-18 16:45:15 -04:00
parent 015ff52eda
commit 5b359fe8d2
2 changed files with 27 additions and 19 deletions
+18 -12
View File
@@ -419,18 +419,24 @@ PY
while IFS=' ' read -r old new ref; do
[ -z "$ref" ] && continue
[ "$new" = "$zero" ] && continue
if [ "$old" = "$zero" ]; then
# New ref: scan only the commits this push introduces — those
# reachable from $new but not from any ref the gate already has.
# Everything already on the gate arrived via upstream mirror-fetch
# or a previously gitleaks-scanned push, so it's already-upstream
# or already-scanned; re-scanning it (the old `$new` full-ancestry
# range) only resurfaces historical findings and blocks every new
# branch. See PRD 0028 / issue #106.
log_opts="$new --not --all"
else
log_opts="$old..$new"
fi
# Scan only the commits this push introduces — those reachable from
# $new but not from any ref the gate already has. Everything already
# on the gate arrived via upstream mirror-fetch or a previously
# gitleaks-scanned push, so it's already-upstream or already-scanned;
# re-scanning it only resurfaces historical fixture findings.
#
# Applies to both new refs and updates. The old existing-branch range
# `$old..$new` walks commits reachable from the new tip but not the
# *old branch tip*: on a rebase/force-push onto a freshly-advanced
# main that pulls in all of main's new history (incl. the deliberate
# sandbox-escape gitleaks fixtures), blocking the push. `--not --all`
# excludes anything already on the gate regardless of ancestry, so it
# is also correct for non-fast-forward pushes (a rebase can skip
# commits off the direct path). Security-equivalent per PRD 0028's
# analysis: the bare repo's refs come only from trusted upstream
# mirror-fetch or gitleaks-gated pushes.
# See PRD 0028 (open question) / issues #106, #346.
log_opts="$new --not --all"
echo "git-gate: gitleaks scanning $ref ($log_opts)" >&2
if ! gitleaks git --log-opts="$log_opts" --no-banner --redact 1>&2; then
echo "git-gate: gitleaks rejected push to $ref" >&2