git-gate rejects all new-branch pushes: pre-receive scans full history and trips on test-fixture secrets #106
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Pushing a new branch through git-gate never reaches the upstream. The pre-receive hook scans the entire ancestry of a new ref, and the repo history contains 11 deliberately secret-shaped strings (test fixtures + demo files), so gitleaks rejects every new-branch push. To the agent it presents as a silent hang, not a rejection.
Repro
From inside a bottle whose
bottle.gitpoints at this repo:The push hangs and the branch never appears on Gitea. The ref is never written to git-gate's bare repo either.
Root cause
In
git_gate_render_hook()(bot_bottle/git_gate.py), for a new ref (old = 0000…):A new branch therefore re-scans all ~500 commits.
gitleaksfinds 11 pre-existing hits and the hookexit 1s, rejecting the push. Confirmed against the live bare repo:438 commits scanned … leaks found: 11.The 11 are all fixtures/demos, never real secrets:
generic-api-key, 4xgithub-pat, 2xaws-access-tokenbot-bottle.demo.json/claude-bottle.demo.json,docs/demo.tape, and thetests/integration/test_pipelock_blocks_secret_*,test_sandbox_escape,test_git_gate_sidecarfiles — i.e. strings that exist to exercise the DLP.Because they're in history, no new branch can ever be pushed through the gate.
Secondary problems surfaced
git-gate: gitleaks rejected push to …to stderr, but over thegit://+ smolmachines port-forward path the agent saw a ~75s silent hang with noremote:lines. Blocks should be visible.sshlacksBatchMode=yes/ConnectTimeout(ssh -i $keyfile -o UserKnownHostsFile=… -o StrictHostKeyChecking=yes -o IdentitiesOnly=yes). Any prompt or unreachable upstream hangs the hook indefinitely. (Not the cause here — the forward itself works, verifiedEverything up-to-date— but a latent hang risk.)Proposed fix
--log-opts="$new --not --all"(or exclude refs already on the gate/upstream) so a push is scanned for what it introduces, not the whole repo history. A pre-existing historical finding must not block unrelated new branches.remote:error instead of a silent hang (investigate sideband relay over git:// + the smolmachines forward).sshwithBatchMode=yesand aConnectTimeoutso a bad upstream fails fast instead of hanging.Notes
Diagnosed live: the rewrite/
insteadOf, the deploy key (gitea-delos-2, authenticates asdidericiswith write), and the forward push all work — the only blocker is the full-history gitleaks scan on new refs.Why does gitleaks default to scanning all? Is there a potential for pushing to the upstream if we exclude refs already on the gate? I want to ensure change doesn't introduce a security issue.
Good question — two parts: why it scans everything today, and whether scoping it is safe.
Why it scans all today
gitleaks isn't defaulting to anything — it scans whatever revision range the hook hands it. The hook picks the range:
For an existing branch there's an
oldtip to diff against ($old..$new= just the pushed delta). For a new brancholdis all-zeros, so the hook falls back to$new— andgit log <new>means "everything reachable from the new tip," i.e. the full ancestry. So it's the new-ref branch of the hook that's over-broad, not gitleaks.Is
--not --allsafe? Yes — here's the argument.git log $new --not --all= commits reachable from the pushed tip but not from any ref the gate already has. The worry is whether excluding "already-on-the-gate" commits lets a secret reach the upstream unscanned. It can't, because of how commits enter the gate's bare repo. There are only two paths:remote add --mirror=fetch origin, and the access-hook fetches upstream before every upload-pack; andSo every commit reachable from a gate ref is either already on the upstream or was already scanned when pushed. A commit excluded by
--not --alltherefore can't be a new secret arriving upstream via this push:The only commits that can carry a new secret upstream are the ones this push introduces — exactly the set
$new --not --allscans. No gap. An agent can't pre-seed a secret commit as "already known" to dodge the scan: it can't write refs into the bare repo except by pushing (scanned), and the mirror refs come only from the trusted upstream.Invariant the fix relies on: the bare repo's refs are populated only by upstream mirror-fetch and gitleaks-gated pushes. That holds in the current design (nothing writes refs out-of-band); if it ever changes, revisit.
Bonus:
--not --allis also safer than today's$old..$newfor force-pushes — on a non-fast-forward update$old..$newcan skip commits off the direct path, whereas--not --allscans everything genuinely new regardless of ancestry shape. Worth using it for both branches of the hook.