CVE-2026-28699 is a high-severity (CVSS 8.1) CWE-284 vulnerability in code.gitea.io/gitea. A fix is available for code.gitea.io/gitea — see the affected versions and patch details below.
Gitea Basic Auth bypasses OAuth2 access token scopes
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- A successful exploit gives an attacker total control of the affected component, not partial access.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-28699.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
CVE-2026-28699 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.
Where this sits among everything scored
Of 381,682 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.
Real-World Exposure
code.gitea.io/giteaReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
Description
Summary
Gitea fails to enforce OAuth2 access token scopes when the token is submitted via HTTP Basic authentication instead of a Bearer token. An OAuth2 application granted only read:user can use the same token as Authorization: Basic base64(<token>:x-oauth-basic) and perform write actions, including modifying profiles, adding email addresses, creating repositories, and deleting repositories as the authorizing user.
Details
Root cause: services/auth/basic.go accepts OAuth2 access tokens through the Basic auth path but does not store the token scope in the request context:
// services/auth/basic.go
if uid != 0 {
store.GetData()["LoginMethod"] = OAuth2TokenMethodName
store.GetData()["IsApiToken"] = true // scope is NOT set
return u, nil
}
The scope enforcement middleware in routers/api/v1/api.go exits early when ApiTokenScope is absent:
// routers/api/v1/api.go — tokenRequiresScopes
scope, scopeExists := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
if ctx.Data["IsApiToken"] != true || !scopeExists {
return //<- exits without checking scope, all actions permitted
}
When a token arrives via Bearer, ApiTokenScope is populated and scope checks apply normally. When the same token arrives via Basic auth, ApiTokenScope is never set, so tokenRequiresScopes returns immediately and no scope is enforced.
Suggested fix: When an OAuth2 access token is accepted in services/auth/basic.go, populate ApiTokenScope in the request context identically to the Bearer-token OAuth2 path.
PoC
- Create an OAuth2 application in Gitea.
- Authorize it as a normal user with scope
read:useronly. - Take the resulting access token and call a write endpoint both ways:
Bearer | correctly blocked:
Authorization: Bearer <token>
PATCH /api/v1/user/settings -> 403 Forbidden
Basic | bypass:
Authorization: Basic base64(<token>:x-oauth-basic)
PATCH /api/v1/user/settings -> 200 OK
All verified bypass endpoints using a read:user-only token:
| Endpoint | Bearer | Basic |
|---|---|---|
PATCH /api/v1/user/settings | 403 | 200 |
POST /api/v1/user/emails | 403 | 200 |
POST /api/v1/user/repos | 403 | 200 |
PATCH /api/v1/repos/{owner}/{repo} | 403 | 200 |
DELETE /api/v1/repos/{owner}/{repo} | 403 | 200 |
The bypass respects the user's normal repository permissions, it does not grant access to repositories the user cannot otherwise reach, and does not escalate to admin.
Impact
Any OAuth2 application with any restricted scope can silently operate beyond its granted permissions by switching from Bearer to Basic auth. An attacker who obtains a token (e.g. via a malicious OAuth2 app a user authorized) can:
- Modify the victim's profile and settings
- Add attacker-controlled email addresses to the victim's account
- Create repositories as the victim
- Modify or delete the victim's private repositories
The entire OAuth2 scope system is effectively bypassed for any token submitted via Basic auth.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | code.gitea.io/gitea | all versions | 1.26.2go get code.gitea.io/gitea@v1.26.2 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for code.gitea.io/gitea, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update code.gitea.io/gitea to 1.26.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-28699 is resolved across your whole dependency graph.
Workarounds
Put an independent control in front of the weakness: restrict the affected endpoint or interface to trusted networks, require an additional authentication factor or proxy-level check, and invalidate existing sessions and credentials in case the flaw has already been used.
Frequently Asked Questions
Is CVE-2026-28699 in your dependencies?
Find it across Go, including transitive dependencies.