CVE-2026-58443: Gitea public-only tokens can write private repos and trigger Actions, CVSS 9.6 PoC public
Self-hosted Gitea has a critical token-scope bypass, CVE-2026-58443 (CVSS 9.6). A public-only token can push public commits into a private head repo via the PR update endpoint and trigger private Actions. Fixed in v1.27.0. Full PoC is public.
CVE-2026-58443 (GHSA-xxjv-752h-3vp2) is a critical authz bypass in self-hosted Gitea, CVSS 3.1 9.6. A caller holding only a public-only scoped token can push commits from a public base repo into a private head repo via the PR update endpoint, then trigger Actions on that private repo. Token-scope isolation is fully bypassed. Fixed in Gitea v1.27.0. Full PoC is public.
Overview
Gitea is a widely used lightweight self-hosted Git service and a common internal code host / CI trigger. The bug is in `POST /api/v1/repos/{owner}/{repo}/pulls/{index}/update`. Gitea's public-only check applies only to the route/base repo. `UpdatePullRequest()` then runs a normal `IsUserAllowedToUpdate()` RBAC check on the private head repo and does not re-apply token-scope limits. If Actions is enabled on the head repo, the injected push is treated as a legitimate push, creating `ActionRun` and `ActionRunJob`. Public-scope credentials then drive private CI/CD.
Technical Details
Official snippet (`routers/api/v1/api.go:1358-1394`) shows PR routes nested under `checkTokenPublicOnly()`, but the inner `/update` endpoint does not repeat that check on the head repo:
`m.Group("/pulls", func() {
...
m.Group("/{index}", func() {
...
m.Post("/update", reqToken(), repo.UpdatePullRequest)
...
})
}, mustAllowPulls, reqRepoReader(unit.TypeCode), ...
}, repoAssignment(), checkTokenPublicOnly())`
Prerequisites:
- Attacker holds a valid `public-only + write:repository` token
- That token's user has normal write access to the target private head branch
- A PR exists that can merge/rebase the public base repo onto the private head repo
CWE-863 Incorrect Authorization. Impacts Integrity and Availability. Scope is Changed (C). PoC check: direct writes to the private repo return 404; the PR update endpoint merges public commits into the private branch and fires Actions.
Impact
All Gitea v1.26.4 and earlier are affected. CVSS vector `AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H`. On self-hosted installs, an attacker can inject code into private repos and private pipelines without direct write on the private repo. EPSS is high. Researchers published a full PoC. No confirmed in-the-wild use. With the PoC public, and several Gitea scope-boundary failures in the last six months (reverse-proxy auth bypass, webhook SSRF), mass-exploit cost is low.
Remediation
- Upgrade now: self-hosted instances to Gitea v1.27.0 or later (fix PR #38406). After upgrade, re-open Actions and confirm expected triggers.
- Token audit: before upgrade, list tokens that combine public-only with write:repository. Revoke unnecessary combos and reissue least-privilege tokens.
- Actions audit: review recent private-repo `ActionRun` records. Flag `event=push` runs where `trigger_user_id` does not match the committer, or the source is a PR-update trigger.
- RBAC and MFA: enforce MFA for all users. Recheck minimum privileges for `Push`, `Push to protected branch`, and `Run workflows` on private repos. Treat Token only public as a mandatory baseline.
- Endpoint controls: at the reverse proxy, add trusted IP, UA, and rate limits on `/api/v1/repos/*/pulls/*/update`. WAF logs should capture every call for later review.
References
- GitHub Advisory GHSA-xxjv-752h-3vp2
- OSV entry
- Gitea v1.27.0 Release Notes (fix PR #38406)
- SecurityOnline analysis