fix(merge-queue): close zero-approval-merge + out-of-roster-REQUEST_CHANGES fail-opens (#3210) #3222
Reference in New Issue
Block a user
Delete Branch "fix/merge-gate-failopen-3210"
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?
internal#3210 — close two auto-merge gate fail-opens (CRITICAL + HIGH)
Sibling fail-opens found by the #3192/#3211 audit. Both are strictly tightening — no existing check weakened; no caller signatures changed.
🔴 FIX 1 (CRITICAL) — zero-approval merge
parse_branch_protectioncoerced a boolrequired_approvalsviaisinstance(True,int)(True→1, halving a 2-bar) and passed0/negative straight through;evaluate_merge_readinessstep 3 then hadlen(approvers) < 0always False → a PR could auto-merge with ZERO genuine approvals, defeating the whole 2-genuine gate (admin lowering the bar / a Gitea migration-default reset / a blanked-restored BP record all yield this).Fix: reject bool/non-int → default floor; clamp valid ints UP via
max(approvals, REQUIRED_APPROVALS_DEFAULT)(a stricter BP wins; 0/negative never lower the bar). Defence-in-depth floormax(required_approvals, 1)on the non-exempt path. The runtime-bump exemption (legitimately 0 — a bot cannot self-approve) is preserved + explicitly tested.🟠 FIX 2 (HIGH) — out-of-roster REQUEST_CHANGES dropped
classify_reviewsapplied thereviewer_setroster filter to BOTH verdicts before the verdict was known → an official current-headREQUEST_CHANGESfrom a NON-roster login (e.g. the CTO/founder) was silently dropped and did not block the merge.Fix: reduce over ALL users; apply the roster gate ONLY to the approver tally (
is_genuine_approval(..., reviewer_set=...)); any official current-head REQUEST_CHANGES from ANY login now blocks. Approver strictness byte-for-byte unchanged; thereview-check.shpath (callsis_genuine_approvaldirectly withreviewer_set=None, notclassify_reviews) is unaffected.Tests
+13 across
test_gitea_merge_queue.py+test_approval_validator.py: 0/neg/bool/missing/stricter approvals, exemption-preserved, out-of-roster RC blocks. Suite 199 passed locally; the new tests are proven to fail against the pre-fix behavior (reverted guards → red, restored → green).Addresses #3210. Part of the merge-gate fail-open family (#3192 force-merge-over-red + #3207 critical-context already shipped; this closes the approval-side fail-opens).
APPROVED on
d4044613.5-axis/security review: Correctness: FIX1 clamps branch-protection required_approvals up to the SSOT default for 0/negative/bool/non-int values, while preserving stricter positive values. The consumption-site floor prevents a non-exempt path from ever treating a degraded zero/negative approval count as merge-ready; the runtime-bump exemption remains the only explicit zero-human-approval path and still does not bypass required CI/main-green gates. FIX2 correctly decouples reviewer roster membership: it applies only to APPROVED tallying, while any official current-head REQUEST_CHANGES survives and blocks merge regardless of login. Out-of-roster APPROVED still does not count.
Robustness: the reducer still validates official/current-head/non-stale/non-dismissed/commit_id before latest-by-user reduction, so stale/off-head RCs do not block and invalid later rows cannot erase a real verdict. Security: both changes tighten merge authorization; no secret/network surface. Performance: only small in-memory review/BP parsing changes. Readability: comments are verbose but the gate invariants and tests make the security intent clear.
Reviewed files: .gitea/scripts/_approval_validator.py, .gitea/scripts/gitea-merge-queue.py, and the new regression tests. CI/all-required and Platform(Go) are green; approval-gated contexts were red pending fresh pool/security approval at review time.
APPROVED on
d4044613b0.5-axis + adversarial security review:
CI notes: CI/all-required and review-check-tests are green on this head. Ops Scripts is red in unrelated test_sop_checklist.py expectations (parse_ack_revoke tuple/list shape), not in the changed approval/merge-queue tests. Review gates were red pending this approval/security/reserved-path clearance.