A AegiFlow
HIGHCVSS 8.1

CVE-2026-58439

Gitea: Branch Protection Bypass via PR Retargeting Preserves Stale `official` Approval Flag

Published
2026-07-21
Modified
2026-07-21
Aliases
GHSA-w5pg-649r-p6gg
Sources
github-advisory

Summary

## Summary Gitea does not re-evaluate the `official` flag on existing pull request reviews when a PR's target branch is changed. An attacker with write access to a repository can obtain an `official: true` approval on a PR targeting an unprotected branch, then retarget the PR to a protected branch (e.g., `master`). The approval, which would have been `official: false` if submitted against the protected branch, is preserved and satisfies the protected branch's required approvals, allowing the attacker to merge without legitimate maintainer approval. - Confirmed on Gitea **1.25.4** (`1.25.4+41-g96515c0f20`) ## Vulnerability Details ### Root Cause When a review is submitted on a pull request, Gitea computes the `official` flag by checking whether the reviewer is in the **target branch's** approval whitelist (`IsUserOfficialReviewer` in `models/git/protected_branch.go`). This flag is stored in the database as a boolean on the review record. When a PR's target branch is subsequently changed via `ChangeTargetBranch` (`services/pull/pull.go:218`), the function: - Updates `pr.BaseBranch` - Recalculates merge feasibility and divergence - Deletes old push comments - Creates a "change target branch" comment But it does **not**: - Re-evaluate `official` on existing reviews - Dismiss existing approvals - Check whether reviewers are in the new target branch's approval whitelist At merge time, `GetGrantedApprovalsCount` (`models/issues/pull.go:766`) counts reviews where `official = true AND dismissed = false AND type = Approve`. It reads the stored boolean — it does not re-check the whitelist. The stale `official: true` from the unprotected branch satisfies the protected branch's approval requirement. ### Relevant Code Paths 1. **Review creation** — `services/pull/review.go:SubmitReview` calls `IsOfficialReviewer` against the current `pr.BaseBranch`'s protection rules, stores `official=true/false` 2. **Target branch change** — `services/pull/pull.go:ChangeTargetBranch` modifies `pr.BaseBranch` but does not touch existing reviews 3. **Merge check** — `services/pull/check.go:CheckPullMergeable` → `models/issues/pull.go:GetGrantedApprovalsCount` counts stored `official=true` reviews without re-evaluating against the new branch's whitelist ### Prerequisites The attacker needs: - **Write (push) access** to the repository (collaborator with write role, or the ability to create branches — not admin) - The ability to create pull requests (standard for any user with push access) - A second account (or any non-admin account) to submit the approval on the unprotected branch The attacker does **not** need: - Admin access - To be in the approval whitelist for the protected branch - Any interaction from the branch protection's designated approvers ## Proof of Concept ### Setup Repository `owner/repo` with branch `master` protected: - Required approvals: 1 - Approval whitelist enabled, containing only user `admin-reviewer` - User `attacker` has write access but is **not** in the approval whitelist ### Steps ```bash BASE="http://gitea-instance:3000" OWNER="owner" REPO="repo" ATTACKER_AUTH="attacker:password" ACCOMPLICE_AUTH="accomplice:password" # any non-whitelisted user # 1. Create an unprotected temporary branch from master curl -X POST "$BASE/api/v1/repos/$OWNER/$REPO/branches" \ -u "$ATTACKER_AUTH" \ -H "Content-Type: application/json" \ -d '{"new_branch_name": "tmp-unprotected", "old_branch_name": "master"}' # 2. Push a malicious commit to a feature branch git checkout -b malicious-branch origin/master echo "malicious payload" > payload.txt git add payload.txt git commit -m "innocent looking commit" git push origin malicious-branch # 3. Create PR targeting the UNPROTECTED branch curl -X POST "$BASE/api/v1/repos/$OWNER/$REPO/pulls" \ -u "$ATTACKER_AUTH" \ -H "Content-Type: application/json" \ -d '{ "head": "malicious-branch", "base": "tmp-unprotected", "title": "Add feature" }' # Returns PR #N # 4. Appro

Affected packages

EcosystemPackageAffected versionsFixed versions
Gocode.gitea.io/gitea1.27.0

Remediation: Upgrade to 1.27.0 or later.

References

Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.

CVE® is a registered trademark of The MITRE Corporation. CVE content reproduced under the CVE Terms of Use; copyright designation © MITRE.