CVE-2026-56654: Gitea · Privilege Escalation via Access Token Scope Escalation in API
Affects code.gitea.io/giteaSource GCVE Database
Not known to be exploited · CVSS 9.8 Critical : upgrade to 1.27.0 as a priority.
Data as of 30 Oct 2026, 11:52 UTC
- EPSS
- 0.8% chance of exploitation in 30 days – higher than 57% of CVEsExploit Prediction Scoring System: the likelihood attackers will use this flaw soon.
- Patch
- Available – fixed in 1.27.0
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HExploitation evidence
t = 0 is the CVE’s publication on cve.org
Disclosed: evidence found.
CVE published 13 Aug 2026
Detectable: no evidence on record.
No public nuclei template indexed
Weaponized: evidence found.
CISA SSVC: proof of concept public
Exploited in the wildExploited: no evidence on record.
Not in CISA KEV and no source reports exploitation
Observed by OffSeq MirageSeen by Mirage: no evidence on record.
No attack traffic recorded by OffSeq Mirage sensors
Timeline
CVE ID reserved
by Gitea Limited
, 36 days before disclosure
CVE published
on cve.org
, t = 0
CISA SSVC assessment
proof of concept public
, 27 hours after disclosure
A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), CISA SSVC, the Metasploit, Exploit-DB and nuclei-templates index as of 30 Oct 2026.
What to do
Upgrade code.gitea.io/gitea to 1.27.0.
Affected software
| Product | Affected | Fixed in |
|---|---|---|
| Gocode.gitea.io/gitea | Affected
| Fixed in
|
Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.
Description
Gitea's API endpoint for creating Personal Access Tokens (`POST /users/{username}/tokens`) is protected by a middleware (`reqBasicOrRevProxyAuth`) that is intended to require password-based authentication, preventing a compromised token from being used to mint new ones. However, when a token is passed in the `Authorization: Basic <token>:x-oauth-basic` format, the Basic auth handler validates it and sets `AuthedMethod="basic"`, causing `IsBasicAuth=true` and fooling the middleware into passing the request. Once past the guard, the token creation handler applies no scope ceiling — it will create a new token with any requested scope regardless of the caller's scope. An attacker with a restricted token (e.g. `write:user` from a leaked CI secret) can therefore create a fully-privileged `all`-scoped token without knowing the account password. ### Data flow #### Step 1 - Token extracted from Basic auth header When the attacker sends Authorization: Basic base64(<token>:x-oauth-basic), parseAuthBasic detects that the password is "x-oauth-basic" and treats the username field as the token: https://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/services/auth/basic.go#L55-L64 `VerifyAuthToken` then validates the token against the database and sets `LoginMethod = "access_token"` and `ApiTokenScope` to the token's actual scope (`write:user`): https://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/services/auth/basic.go#L100-L106 #### Step 2 - `AuthedMethod` is set to `"basic"`, not `"access_token"` `Basic.Verify()` returns the user successfully, so `group.Verify()` sets `AuthedMethod` to the method's name — `"basic"` — regardless of whether a password or token was used: https://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/services/auth/group.go#L63-L65 #### Step 3 - `IsBasicAuth` is incorrectly set to true `AuthShared` computes `IsBasicAuth` by comparing `AuthedMethod` against the constant `"basic"`. Since step 2 set that field to "basic" for a token-authenticated request, the flag is wrong: https://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/routers/common/auth.go#L27 #### Step 4 - The guard is bypassed `reqBasicOrRevProxyAuth` checks only `ctx.IsBasicAuth`. Because that flag is `true`, the middleware passes and the request reaches `CreateAccessToken`: https://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/routers/api/v1/api.go#L392-L401 #### Step 5 - No scope ceiling in the handler `CreateAccessToken` normalizes the caller-supplied scope and assigns it directly to the new token. There is no check that the requested scope is a subset of `ApiTokenScope (write:user)`: https://github.com/go-gitea/gitea/blob/9155a81b9daf1d46b2380aa91271e623ac947c1e/routers/api/v1/user/app.go#L119-L128 ### Reproducing `tests/integration/api_token_scope_escalation_test.go` ```go package integration import ( "net/http" "testing" auth_model "gitea.dev/models/auth" "gitea.dev/models/unittest" user_model "gitea.dev/models/user" api "gitea.dev/modules/structs" "gitea.dev/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // TestAPIPrivilegeEscalationViaBasicAuthToken is a proof-of-concept for two // interconnected vulnerabilities that together allow full scope escalation: // // 1. reqBasicOrRevProxyAuth() is fooled into passing when a PAT is supplied in // the Authorization: Basic "<token>:x-oauth-basic" format. The Basic auth // handler sets AuthedMethod="basic" (the method name), so IsBasicAuth=true // even though the credential is a token, not a password. // // 2. CreateAccessToken performs no scope-ceiling check — it never verifies that // the requested scopes are a subset of the caller's token scopes. // // Combined effect: an attacker with a write:user-scoped token can create a new // token with the "all" scope, gaining full access to the account. func TestAPIPrivilegeEscalationViaBasicAuthToken(t *testing.T) { defer tests.PrepareTestEnv(t)() // Non-admin user — escalation is meaningful and not trivially justified. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Step 1 — Obtain a legitimately restricted token via password-based Basic auth. // Only write:user scope is granted; repository, admin, etc. are excluded. restrictedToken := createAPIAccessTokenWithoutCleanUp(t, "poc-restricted", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteUser}) defer deleteAPIAccessToken(t, restrictedToken, user) // Confirm the restricted token is blocked from repository-scoped endpoints. // This establishes the baseline: write:user does not imply read:repository. req := NewRequest(t, "GET", "/api/v1/repos/search"). AddTokenAuth(restrictedToken.Token) MakeRequest(t, req, http.StatusForbidden) // Step 2 — Exploit: supply the restricted token as Basic auth credentials. // Authorization: B
References
- github.com/go-gitea/gitea/security/advisories/GHSA-683j-3ff6-hh2x (opens in a new tab)
- github.com/go-gitea/gitea/pull/38406 (opens in a new tab)
- github.com/go-gitea/gitea/pull/38426 (opens in a new tab)
- github.com/go-gitea/gitea/commit/de4b8277e9cb576f2315fb03b5ab6478b42a1d31 (opens in a new tab)
- github.com/go-gitea/gitea/commit/f69e15afe7496cc62e96dab244629c69eb31a7bf (opens in a new tab)
- github.com/go-gitea/gitea (opens in a new tab)
- github.com/go-gitea/gitea/releases/tag/v1.27.0 (opens in a new tab)