Skip to main content
VulnerabilityPublished

CVE-2026-56654: Gitea · Privilege Escalation via Access Token Scope Escalation in API

Affects code.gitea.io/giteaSource GCVE Database

0
Compare
Authoritative record:CVE-2026-56654· Critical 9.8

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

CVSS 3.1 base score
9.8Critical
How severe the flaw is on the 0–10 CVSS scale.
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:H

Exploitation evidence

t = 0 is the CVE’s publication on cve.org

  1. Disclosed: evidence found.

    CVE published 13 Aug 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponized: evidence found.

    CISA SSVC: proof of concept public

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

Timeline

  1. CVE ID reserved

    by Gitea Limited

    , 36 days before disclosure

  2. CVE published

    on cve.org

    , t = 0

  3. 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

ProductAffectedFixed in
Gocode.gitea.io/giteaAffected
  • <1.27.0
Fixed in
  • 1.27.0

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

Community reviews

Loading reviews…