GHSA-cp3q-vrj2-ghhh Gitea: Personal access token scope enforcement bypass on the repository home page (`GET /{owner}/{repo}`) discloses private repository contents
SummaryA personal access token (PAT) or OAuth2 token that does **not** carry the
`repository` scope or that is **public-only** is correctly rejected (HTTP 403)
by the recently hardened web content routes (archive download, raw/media file
download, and repository RSS/Atom feeds). However, the repository home page route
`GET /{owner}/{repo}` (handler `repo.Home`) serves the **private** repository's
rendered README, root file/directory tree, description, language statistics,
license, and latest-release information to that same token.This is a token-scope enforcement bypass and private-repository content
disclosure. It is the same source→sink pattern already fixed for neighbouring
routes in:**GHSA-cr4g-f395-h25h** (CVE-2026-20706) token scope bypass on web archive download**GHSA-3pww-vcvm-3gmj** (CVE-2026-27761) token scope bypass on repository RSS/Atom feeds`repo.Home` is the remaining token-auth-enabled content route that was not given
the guard.Details / Root causeWeb routes accept token authentication only when explicitly opted in with
`webAuth.AllowBasic` / `webAuth.AllowOAuth2`. The repository home route carries
`AllowBasic` (added so that `go get` can resolve private modules):When a PAT/OAuth2 token is supplied via HTTP Basic auth, `services/auth/basic.go`
sets `IsApiToken = true` and records `ApiTokenScope`:The patched sibling handlers all call the web-side scope guard
`context.CheckRepoScopedToken(...)`, which enforces both the public-only
restriction and the `repository` scope:`repo.Home` (`routers/web/repo/view_home.go:389`) performs **no** such check. Its
only gate is `checkHomeCodeViewable`, which verifies the **user's** permission and
that the code unit is enabled neither of which constrains the **token's** scope.
The README, file listing, and sidebar are then rendered. (The
`handleRepoHomeFeed` sub-path is guarded via `ShowRepoFeed`, but the HTML repo
view is not.)Proof of ConceptReproduced against `gitea/gitea:main-nightly` (build `g2e1be0b114`, identical to
the source commit above). A private repository `admin/secretrepo` is created, and
a token is minted with **only** the `read:user` scope (no `repository` scope).A second token with scope `public-only,read:repository` behaves identically:
`/archive` → 403, but `/admin/secretrepo` → 200 and returns the private content.`canary=1` means the private README marker was returned in the HTML response; the
private file name `SECRET.md` is also disclosed in the rendered file tree.Full self-contained reproducer (Docker, prints a PASS/FAIL verdict):ImpactAny holder of a non-`repository`-scoped or public-only token belonging to a user
who has access to private repositories can read those repositories' README, root
file/directory listing, description, languages, license, and latest release
content the token was explicitly scoped to be unable to read. This defeats the
purpose of fine-grained and public-only tokens (for example a CI token granted
only `read:issue`, or a `public-only` token issued to a third-party integration).The disclosure is bounded to the repository root view because the deeper
`/{owner}/{repo}/src/*` routes do not enable `AllowBasic`.Suggested remediationMirror the sibling handlers: at the start of `repo.Home`
(`routers/web/repo/view_home.go`), or as route middleware on `routers/web/web.go:1256`,
add:It is also worth auditing the other `AllowBasic`/`AllowOAuth2` web routes that
render repository data for the same omission notably
`actions.GetWorkflowBadge` (`routers/web/web.go:1568`), which currently exposes a
private repository's workflow build status (pass/fail) to a token without the
`repository` scope (lower impact, possibly intended since badges are designed to
be embeddable, but worth confirming).
CVSS v3.1 score: 4.3