🛡️ CVE-2026-58443 — gitea.dev

🔴 CVSS 9.5 — Critical ✅ No Known Exploit CWE-863 OSV
9.5
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

Gitea: Public-only repository tokens can update private PR head branches

Summary

Gitea allows a public-only,write:repository token to update a private pull request head branch through a public base repository route.

The vulnerable endpoint is:

```text

POST /api/v1/repos/{public-owner}/{public-repo}/pulls/{index}/update

```

Gitea checks the token's public-only restriction against the route repository, which is the public base repository. UpdatePullRequest() then authorizes the pull request head repository with ordinary user RBAC and calls the pull update service. If the head repository is private, the active token's public-only restriction is not re-applied to that private repository before Gitea pushes changes into it.

As a result, the same token that cannot directly write to the private repository can still cause Gitea to push public base commits into the private head branch.

Details

The pull request API routes are attached under a repository route group. The public-only check applies to ctx.Repo.Repository, the route/base repository.

```go

// routers/api/v1/api.go:1358-1394

m.Group("/pulls", func() {

m.Combo("").Get(repo.ListPullRequests).

Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)

m.Get("/pinned", repo.ListPinnedPullRequests)

m.Post("/comments/{id}/resolve", reqToken(), mustNotBeArchived, repo.ResolvePullReviewComment)

m.Post("/comments/{id}/unresolve", reqToken(), mustNotBeArchived, repo.UnresolvePullReviewComment)

m.Group("/{index}", func() {

m.Combo("").Get(repo.GetPullRequest).

Patch(reqToken(), bind(api.EditPullRequestOption{}), repo.EditPullRequest)

m.Get(".{diffType:diff|patch}", repo.DownloadPullDiffOrPatch)

m.Post("/update", reqToken(), repo.UpdatePullRequest)

m.Get("/commits", repo.GetPullRequestCommits)

m.Get("/files", repo.GetPullRequestFiles)

m.Combo("/merge").Get(repo.IsPullRequestMerged).

Post(reqToken(), mustNotBeArchived, bind(forms.MergePullRequestForm{}), repo.MergePullRequest).

Delete(reqToken(), mustNotBeArchived, repo.CancelScheduledAutoMerge)

m.Group("/reviews", func() {

m.Combo("").

Get(repo.ListPullReviews).

Post(reqToken(), bind(api.CreatePullReviewOptions{}), repo.CreatePullReview)

m.Group("/{id}", func() {

m.Combo("").

Get(repo.GetPullReview).

Delete(reqToken(), repo.DeletePullReview).

Post(reqToken(), bind(api.SubmitPullReviewOptions{}), repo.SubmitPullReview)

m.Combo("/comments").

Get(repo.GetPullReviewComments)

m.Post("/dismissals", reqToken(), bind(api.DismissPullReviewOptions{}), repo.DismissPullReview)

m.Post("/undismissals", repo.UnDismissPullReview)

})

})

m.Combo("/requested_reviewers", reqToken()).

Delete(bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests).

Post(bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests)

})

m.Get("/{base}/*", repo.GetPullRequestByBaseHead)

}, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo())

```

```go

// routers/api/v1/api.go:1465-1466

}, repoAssignment(), checkTokenPublicOnly())

}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))

```

For POST /api/v1/repos/{public-owner}/{public-repo}/pulls/{index}/update, the route repository can be public, so a public-only,write:repository token passes the route-level public-only check.

The update handler then checks whether the caller can update the PR head branch:

```go

// routers/api/v1/repo/pull.go:1220-1270

pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))

if err != nil {

if issues_model.IsErrPullRequestNotExist(err) {

ctx.APIErrorNotFound()

} else {

ctx.APIErrorInternal(err)

}

return

}

if pr.HasMerged {

ctx.APIError(http.StatusUnprocessableEntity, err)

return

}

if err = pr.LoadIssue(ctx); err != nil {

ctx.APIErrorInternal(err)

return

}

if pr.Issue.IsClosed {

ctx.APIError(http.StatusUnprocessableEntity, err)

return

}

if err = pr.LoadBaseRepo(ctx); err != nil {

ctx.APIErrorInternal(err)

return

}

if err = pr.LoadHeadRepo(ctx); err != nil {

ctx.APIErrorInternal(err)

return

}

rebase := ctx.FormString("style") == "rebase"

allowedUpdateByMerge, allowedUpdateByRebase, err := pull_service.IsUserAllowedToUpdate(ctx, pr, ctx.Doer)

if err != nil {

ctx.APIErrorInternal(err)

return

}

if (!allowedUpdateByMerge && !rebase) || (rebase && !allowedUpdateByRebase) {

ctx.Status(http.StatusForbidden)

return

}

// default merge commit message

message := fmt.Sprintf("Merge branch '%s' into %s", pr.BaseBranch, pr.HeadBranch)

```

The service checks the head repository using the user's normal repository permission:

```go

// services/pull/update.go:136-164

// IsUserAllowedT

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs low-level privileges on the target. No user interaction is required. The scope is changed, meaning a successful attack can affect components beyond the vulnerable one. Rated impact: confidentiality none, integrity high, availability high.

Weakness class

CVE-2026-58443 is classified as CWE-863: Incorrect Authorization. An authorisation check runs but reaches the wrong conclusion, permitting actions it should refuse.

Affected software

CVE-2026-58443 is recorded against 2 packages.

  • code.gitea.io/gitea
  • gitea.dev

Timeline and source

Published on 21 July 2026 and last revised on 27 July 2026. No public exploit is currently recorded for this entry. Record sourced from OSV.

References

github.com (Web)
github.com (Package)
github.com (Web)

Details

Severity CRITICAL
CVSS Score 9.5
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H
CWE CWE-863
Public Exploit ✅ No
Source OSV
Published 2026-07-21
Updated 2026-08-12
Modified 2026-07-27
Fix URL N/A

Affected Packages

Software From version Fixed in
code.gitea.io/gitea
gitea.dev

Similar Threats

Exploit Protection

Are you running gitea.dev?

CVE-2026-58443 carries CVSS 9.5 Critical rating. BotEraser checks your installation against this and other known CVE records, and blocks IPs associated with exploit activity.

Check My Site For CVE-2026-58443 →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.