{"id":"CVE-2026-28744","kind":"vulnerability","aliases":["CVE-2026-28744","GHSA-cc8w-r4qh-3v65"],"related":[],"title":"Gitea: Git Smart HTTP Skips Repository Token Scopes for Bearer Tokens","description":"### Summary\nGitea v1.26.1 enforces repository-scoped access-token permissions on repository operations. In the Git Smart HTTP path, however, this check runs only when the token is presented via HTTP Basic authentication — `CheckRepoScopedToken()` returns early unless `ctx.IsBasicAuth` is true — so the same token sent as `Authorization: Bearer <token>` bypasses the scope check entirely.\n\nAs a result, a PAT or OAuth2 token presented as a Bearer credential can clone or fetch private repositories without the `read:repository` scope, and likewise reach the Git push without `write:repository`.\n\n### Details\nGit Smart HTTP routes allow both Basic auth and OAuth2/Bearer auth:\n\n```go\n// routers/web/web.go\naddOwnerRepoGitHTTPRouters(\n\tm,\n\trepo.HTTPGitEnabledHandler,\n\twebAuth.AllowBasic,\n\twebAuth.AllowOAuth2,\n\trepo.CorsHandler(),\n\toptSignInFromAnyOrigin,\n\tcontext.UserAssignmentWeb(),\n)\n```\n\nThe Git HTTP authorization path calls `CheckRepoScopedToken()` before falling through to normal repository RBAC:\n\n```go\n// routers/web/repo/githttp.go\nif askAuth {\n\tif !ctx.IsSigned {\n\t\tctx.HTTPError(http.StatusUnauthorized)\n\t\treturn nil\n\t}\n\n\tcontext.CheckRepoScopedToken(ctx, repo, auth_model.GetScopeLevelFromAccessMode(accessMode))\n\tif ctx.Written() {\n\t\treturn nil\n\t}\n\n\t// normal repository RBAC follows\n}\n```\n\nHowever, `CheckRepoScopedToken()` only enforces token scopes for Basic-authenticated requests:\n\n```go\n// services/context/permission.go\nfunc CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_model.AccessTokenScopeLevel) {\n\tif !ctx.IsBasicAuth || ctx.Data[\"IsApiToken\"] != true {\n\t\treturn\n\t}\n\n\tscope, ok := ctx.Data[\"ApiTokenScope\"].(auth_model.AccessTokenScope)\n\tif ok {\n\t\trequiredScopes := auth_model.GetRequiredScopes(level, auth_model.AccessTokenScopeCategoryRepository)\n\t\t// public-only and required repository scope checks follow\n\t}\n}\n```\n\nThe Bearer/OAuth2 auth path still records the token scope:\n\n```go\n// services/auth/oauth2.go\naccessTokenScope, uid := GetOAuthAccessTokenScopeAndUserID(ctx, tokenSHA)\nif uid != 0 {\n\tstore.GetData()[\"IsApiToken\"] = true\n\tstore.GetData()[\"ApiTokenScope\"] = accessTokenScope\n}\n```\n\nBearer PATs also set `IsApiToken=true` and `ApiTokenScope`, but `ctx.IsBasicAuth` remains false because the selected auth method is OAuth2/Bearer rather than Basic. The scope is therefore available but ignored.\n\n### PoC\nThis test creates a token for `user2` with only `read:notification`, then requests Git Smart HTTP refs for `user2/repo2`, which is private. The same token is rejected over Basic auth, but succeeds over Bearer auth.\n\n```go\nfunc TestPOCGitSmartHTTPBearerTokenBypassesRepositoryScope(t *testing.T) {\n\tdefer tests.PrepareTestEnv(t)()\n\n\trepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2, OwnerName: \"user2\", Name: \"repo2\"})\n\tassert.True(t, repo.IsPrivate)\n\n\tsession := loginUser(t, \"user2\")\n\ttoken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadNotification)\n\turl := \"/user2/repo2/info/refs?service=git-upload-pack\"\n\n\tbasicReq := NewRequest(t, \"GET\", url)\n\tbasicReq.SetBasicAuth(token, \"x-oauth-basic\")\n\tMakeRequest(t, basicReq, http.StatusForbidden)\n\n\tbearerReq := NewRequest(t, \"GET\", url).AddTokenAuth(token)\n\tresp := MakeRequest(t, bearerReq, http.StatusOK)\n\tassert.Contains(t, resp.Body.String(), \"refs/heads/master\")\n}\n```\n\n### Impact\nAny Gitea instance exposing Git Smart HTTP is affected when users use PATs or OAuth2 tokens as Bearer tokens. The attacker still needs a token for a user who has normal repository RBAC, so this does not grant access to repositories the token owner could not otherwise access.\n\nThe vulnerability breaks the access-token scope boundary. A token intended only for unrelated scopes, such as `read:notification`, can clone or fetch private repository contents over Git Smart HTTP. The same root cause can affect write flows because `git-receive-pack` also calls the same repository scope check before normal write RBAC.","summary":"Gitea's Git Smart HTTP endpoints skip the repository token-scope check when a personal access token or OAuth2 token is sent as a Bearer credential, so the check only applies to Basic auth. An attacker holding a token with an unrelated scope (such as read:notification) can clone or fetch private repositories, and push if the token owner has write access.","severity":"high","cvss":8.100000381469727,"epss":0.004480000119656324,"exploited":false,"exploited_at":null,"products":[{"name":"code.gitea.io/gitea","fixed":["1.26.2"],"affected":"<= 1.26.1","ecosystem":"go"}],"ecosystems":["go"],"tags":["cwe-863","git-over-https","gitea","package","private-repos","self-hosted"],"not_affected_if":["git-over-ssh-only","git-smart-http-disabled","gitea-1.26.2-or-later"],"how_to_check":"Check the Gitea version with 'gitea --version' or the server footer; affected if 1.26.1 or earlier.\nReview tokens in user settings for tokens with narrow scopes that are used with Git over HTTPS.","fix":"Upgrade Gitea to 1.26.2.\nAfter upgrading, rotate any personal access tokens or OAuth2 tokens that may have been exposed.","status":"open","published_at":"2026-06-16T23:38:12+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","triaged_at":"2026-10-23T21:45:28+00:00","updated_at":"2026-10-23T21:45:28+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-10-23T21:45:28Z","model":"claude-haiku-5-5","output":{"fix":"Upgrade Gitea to 1.26.2.\nAfter upgrading, rotate any personal access tokens or OAuth2 tokens that may have been exposed.","reach":"internet","impact":"medium","summary":"Gitea's Git Smart HTTP endpoints skip the repository token-scope check when a personal access token or OAuth2 token is sent as a Bearer credential, so the check only applies to Basic auth. An attacker holding a token with an unrelated scope (such as read:notification) can clone or fetch private repositories, and push if the token owner has write access.","category":"package","who_cares":["gitea","self-hosted","git-over-https","private-repos"],"confidence":0.85,"needs_auth":"yes","worst_case":"Attacker reads or pushes private repository code using a narrowly scoped token","analyst_note":"","how_to_check":"Check the Gitea version with 'gitea --version' or the server footer; affected if 1.26.1 or earlier.\nReview tokens in user settings for tokens with narrow scopes that are used with Git over HTTPS.\nunknown for log lines that show Bearer-authenticated git requests.","needs_analyst":false,"not_affected_if":["git-over-ssh-only","gitea-1.26.2-or-later","git-smart-http-disabled"],"user_interaction":"no","needs_special_setup":"no","severity_if_unknown":"high"},"cost_usd":0.000692,"input_tokens":4512,"output_tokens":481,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-2026':13A,21A '-28744':14A,22A '-863':27B '/gitea':25A '/user2/repo2/info/refs':417C '0':314C '2':399C '3v65':19A 'access':56B,102B,111C,493C,502C,508C 'access-token':110C,507C 'accessmod':240C 'accesstokenscop':307C,320C 'addownerrepogithttprout':204C 'addtokenauth':443C 'affect':463C,539C 'allow':195C 'also':323C,547C 'apitokenscop':279C,319C,328C 'appli':71B 'askauth':229C 'assert.contains':450C 'assert.true':404C 'attack':76B,475C 'auth':74B,198C,201C,298C,336C,380C,385C 'auth_model.accesstokenscope':280C 'auth_model.accesstokenscopecategoryrepository':286C 'auth_model.accesstokenscopelevel':269C 'auth_model.accesstokenscopereadnotification':415C 'auth_model.getrequiredscopes':284C 'auth_model.getscopelevelfromaccessmode':239C 'authent':136C,258C 'author':150C,215C 'avail':347C 'basic':73B,135C,197C,257C,342C,379C,433C 'basic-authent':256C 'basicreq':423C,436C 'basicreq.setbasicauth':428C 'bearer':10A,65B,151C,168C,321C,384C,472C 'bearer/oauth2':297C 'bearerreq':438C,448C 'boundari':511C 'break':505C 'bypass':152C 'call':217C,548C 'caus':537C 'cc8w':17A 'check':52B,69B,125C,155C,294C,553C 'checkreposcopedtoken':137C,218C,250C,263C 'clone':89B,171C,524C 'code.gitea.io':24A 'code.gitea.io/gitea':23A 'content':529C 'context':265C 'context.checkreposcopedtoken':236C 'context.userassignmentweb':211C 'could':499C 'creat':353C 'credenti':66B,169C 'ctx':237C,264C,310C 'ctx.data':272C,278C 'ctx.httperror':232C 'ctx.isbasicauth':141C,271C,330C 'ctx.issigned':231C 'ctx.written':242C 'cve':12A,20A 'cwe':26B 'defer':391C 'detail':190C 'earli':139C 'endpoint':45B 'enforc':106C,252C 'entir':156C 'expos':458C 'fall':220C 'fals':332C 'fetch':91B,173C,526C 'flow':541C 'follow':248C,295C 'func':262C,387C 'get':426C,441C 'getoauthaccesstokenscopeanduserid':309C 'gettokenforloggedinus':412C 'ghsa':16A 'ghsa-cc8w-r4qh-3v65':15A 'git':2A,29B,42B,119C,185C,191C,213C,364C,420C,459C,531C,544C 'git-over-http':28B 'git-receive-pack':543C 'git-upload-pack':419C 'gitea':1A,32B,40B,104C,456C 'go':202C,226C,260C,305C,386C 'grant':492C 'hold':77B 'host':39B 'howev':123C,249C 'http':4A,44B,121C,134C,193C,214C,366C,461C,533C 'http.statusforbidden':437C 'http.statusok':449C 'http.statusunauthorized':233C 'https':31B 'id':398C 'ignor':349C 'impact':454C 'instanc':457C 'intend':514C 'isapitoken':273C,316C,325C 'level':268C,285C 'likewis':182C 'loginus':408C 'm':205C 'makerequest':434C,446C 'method':337C 'name':402C 'need':477C 'newrequest':424C,439C 'nil':235C,244C 'normal':223C,245C,485C,555C 'notif':87B,361C,522C 'oauth':432C 'oauth2':59B,163C,469C 'oauth2/bearer':200C,339C 'ok':277C,282C 'oper':116C 'optsigninfromanyorigin':210C 'otherwis':501C 'owner':99B,498C 'ownernam':400C 'pack':422C,546C 'packag':33B 'pat':161C,322C,467C 'path':122C,216C,299C 'permiss':113C 'person':55B 'poc':350C 'present':132C,165C 'privat':35B,92B,174C,372C,527C 'private-repo':34B 'public':288C 'public-on':287C 'push':95B,186C 'r4qh':18A 'rather':340C 'rbac':225C,247C,487C,557C 'reach':183C 'read':86B,178C,360C,521C 'receiv':545C 'record':301C 'ref':367C 'refs/heads/master':453C 'reject':377C 'remain':331C 'repo':36B,238C,266C,394C 'repo.corshandler':209C 'repo.httpgitenabledhandler':206C 'repo.isprivate':406C 'repo2':403C 'repo_model.repository':267C,397C 'repositori':6A,48B,93B,108C,115C,175C,179C,189C,224C,246C,292C,486C,495C,528C,551C 'repository-scop':107C 'request':259C,363C 'requir':291C 'requiredscop':283C 'resp':445C 'resp.body.string':452C 'result':159C 'return':138C,234C,243C,275C 'root':536C 'rout':194C 'routers/web/repo/githttp.go':227C 'routers/web/web.go':203C 'run':126C 'scope':8A,51B,83B,109C,154C,180C,254C,276C,293C,304C,344C,510C,518C,552C 'select':335C 'self':38B 'self-host':37B 'sent':62B,148C 'servic':418C 'services/auth/oauth2.go':306C 'services/context/permission.go':261C 'session':407C,414C 'set':324C 'skip':5A,46B 'smart':3A,43B,120C,192C,365C,460C,532C 'still':300C,476C 'store.getdata':315C,318C 'succeed':382C 'summari':103C 'test':352C 'testing.t':390C 'testpocgitsmarthttpbearertokenbypassesrepositoryscop':388C 'tests.preparetestenv':392C 'therefor':346C 'token':7A,11A,50B,57B,60B,79B,98B,112C,130C,147C,164C,253C,303C,355C,375C,411C,429C,444C,470C,473C,479C,497C,509C,513C 'token-scop':49B 'tokensha':311C 'true':143C,274C,317C,326C 'uid':308C,313C 'unittest.assertexistsandloadbean':395C 'unless':140C 'unrel':82B,517C 'upload':421C 'url':416C,427C,442C 'use':466C 'user':465C,482C 'user2':357C,401C,410C 'user2/repo2':369C 'v1.26.1':105C 'via':133C 'vulner':504C 'webauth.allowbasic':207C 'webauth.allowoauth2':208C 'without':176C,187C 'write':101B,188C,540C,556C 'x':431C 'x-oauth-bas':430C","nicknames":[],"mitigations":[],"who_affected":null,"tier":null,"verified":{},"analyst_revisit_at":null,"first_public_at":"2026-06-16T23:38:12+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-cc8w-r4qh-3v65","url":"https://github.com/advisories/GHSA-cc8w-r4qh-3v65","severity":"high","published_at":"2026-06-16T23:38:12+00:00","modified_at":"2026-10-22T12:51:12+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","fetched_at":"2026-10-23T19:11:32+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-28744","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-28744","severity":null,"published_at":"2026-07-03T21:17:00+00:00","modified_at":"2026-07-06T18:17:26+00:00","first_seen_at":"2026-10-23T23:44:52+00:00","fetched_at":"2026-10-23T23:44:52+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}