[RSI, security] pre-push guard against mirror-like pushes and remote branch deletions - #2446
Conversation
Prime Agent performance — completedPR Overall: 25 regressed · 0 improved · 15 no clear change.
Python runtime
UI interactions
Sandbox cost: ~$0.1364 — no inference calls. Methodology and samplesMain resolved at 2026-09-17T20:14:03.786207+00:00. Harness
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 53bfa85. Configure here.
…ertions The pre-push guard's refusal output and header now describe the current rules (allowed ref shapes, at-most-10 threshold, escape hatch) instead of retelling the 2026-09-17 incident; the provenance stays as a one-line header note. check-push-guard now asserts the behavioral refusal signature (refusal line, reason detail with ref/deletion counts or the malformed-stdin failure, escape hatch) instead of incident prose, so message copy edits no longer break the check while behavior mutations still fail it. development.md documents the git push --no-verify bypass as a known limit and names GitHub branch protection rules as the server-side mitigation.

Context
On 2026-09-17 an agent ran
git push --mi(git resolves it to--mirror) from a worktree against the real origin; the mirror push force-updated refs and deleted 62 remote branches, auto-closing 63 PRs. The kernelbash()guards are text scans inside the tool layer and were bypassed entirely because the push ran through a raw subprocess. Git's pre-push hook is invoked by git itself for every push, whatever process calls it, so it is the only enforcement layer that intercepts this exact incident path.Changes
.husky/pre-pushwrapper andscripts/pre-push-guard.sh. For real GitHub remotes (github.com and ssh.github.com in scp,http://,https://, andssh://forms, with optional credentials and ports, includingwww.and trailing-dot host spellings git redirects to the same origin) the guard refuses pushes that delete a ref (stdin first field(delete)), update more than 10 refs, or target refs outsiderefs/headsandrefs/tags(mirror pushes copyrefs/remotes/*onto the remote; normal pushes never do). Local,file://, and other remotes are always allowed, malformed stdin fails closed, and an empty stdin (up-to-date push) is allowed.PRIME_AGENT_ALLOW_MIRROR_PUSH=1is the documented escape hatch, printed in the refusal message.npm ci->prepare-> husky).docs/development.mddocuments thegit config core.hooksPath .huskyone-liner for shimless clones/worktrees, thenpm cireset trap for shimless worktrees, and the known limits (deletions by--mirrorpruning are invisible to the pre-push stdin; ssh aliases,insteadOfproxies, and uppercase hostnames are not classified;git push --no-verifybypasses pre-push hooks entirely — client-side hooks are advisory, and GitHub branch protection rules are the server-side mitigation, withmainprotected, which is what rejected the incident's force update tomain).scripts/check-push-guard.mjs, wired intonpm run checkascheck:push-guard: a 31-case table-driven check that drives the hook with pre-push stdin fixtures (mirror, deletions, URL matrix, threshold boundary, malformed and empty stdin, escape-hatch semantics); refusals are asserted on the hook's behavioral signature (refusal line, reason detail, escape hatch), not on message copy. A vitest file cannot carry this PR:check:test-policycounts zero source lines for a hooks-only change, so the check follows the existingscripts/check-*convention instead.Validation
npm run checkpasses (biome,check:test-policyagainst origin/main, tsgo,check:installer,check:push-guard,check:browser-smoke).git@github.com:URL through aGIT_SSH_COMMANDshim (no real remote is contacted): a python-subprocessgit push --mirrorwithout the hook deletes every remote-only branch and copiesrefs/remotes/*onto the remote; with the hook installed the same push, the--mivariant, and an explicit branch deletion are refused with exit 1 and the remote is untouched; a plain push and--force-with-leasestill work;PRIME_AGENT_ALLOW_MIRROR_PUSH=1permits the mirror push as documented.core.hooksPath .huskyone-liner, and directshinvocation, including pushes from subdirectories and linked worktrees.http://github.com/,https://www.github.com/,https://github.com./) were each verified against the real repository withgit ls-remotebefore adding them to the match set.Note
Medium Risk
Introduces client-side enforcement on pushes to production GitHub remotes with documented bypass paths (
--no-verify, env opt-out, SSH aliases); misconfiguration could block legitimate large tag pushes until the escape hatch is used.Overview
Adds a Git pre-push hook (via
.husky/pre-push→scripts/pre-push-guard.sh) that blocks mirror-like pushes and remote branch deletions when the push target is a real GitHub remote (github.com/ssh.github.com, common URL spellings). Allowed pushes are limited to updates onrefs/heads/*andrefs/tags/*, with no deletions and at most 10 refs; local/file:///other hosts are unchanged. Intentional overrides usePRIME_AGENT_ALLOW_MIRROR_PUSH=1.CI and docs:
scripts/check-push-guard.mjs(31 stdin-driven cases) runs asnpm run check:push-guard;development.mddocuments worktree activation (git config core.hooksPath .husky), limits, and that the guard is advisory (--no-verifystill bypasses).Reviewed by Cursor Bugbot for commit 2654bee. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add pre-push guard to block mirror-like pushes and branch deletions to GitHub remotes
.husky/pre-pushwrapper and refuses pushes to matching GitHub remotes that contain more than 10 refs, any deletion, or destinations outsiderefs/headsandrefs/tags.PRIME_AGENT_ALLOW_MIRROR_PUSH=1opt-out skip all checks. Malformed ref input exits 1 with a diagnostic to stderr.npm run checkvia a newcheck:push-guardscript, backed by a 31-case table-driven test suite in scripts/check-push-guard.mjs.pre-push-guard.shcovers all expected GitHub host forms (scp, credentials, ports, www, trailing-dot).Macroscope summarized 2654bee.