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:
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.
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.
// 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,...
1.27.0Exploitability
AV:NAC:LPR:LUI:NScope
S:CImpact
C:NI:HA:H9.6/CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:HAccess Control