critical
CVE
CVE-2026-27771
CWE
CWE-284, CWE-862
Affected Surface
Gitea versions before 1.26.2 with the built-in OCI/container registry enabled, Forgejo and Gitea-derived forks sharing the affected container registry implementation, Self-hosted Gitea package registries where private container images may contain source code, credentials, TLS material, or deployment configuration, Internet-facing Gitea instances with anonymous access paths to /v2/ OCI registry endpoints
NoScope disclosed CVE-2026-27771, a Gitea built-in container registry authorization flaw that allowed unauthenticated remote users to pull private container images from affected self-hosted instances. Gitea credited NoScope in the 1.26.2 release notes and shipped the relevant package-registry security fix in that release.
This is a software supply-chain issue because private registry images are deployable artifacts. They often contain application code, dependency graphs, internal paths, .npmrc or package-manager cache mistakes, TLS material, API endpoints, and secrets accidentally baked into layers. Pulling the image can be enough to reconstruct how an application is built and deployed.
Affected projects
| Project | Affected state |
|---|---|
| Gitea | Versions before 1.26.2 when the built-in OCI/container registry is enabled |
| Forgejo | Reported as affected by NoScope where it shares the same registry implementation; operators should follow Forgejo-specific fixed releases when available |
| Gitea-derived forks | Treat as affected until the fork has audited or backported the Gitea package-registry fix |
The safest operational boundary is “any self-hosted Gitea or Forgejo registry that stored private container images before the fix.” Patching stops new unauthenticated pulls, but it does not prove that older image layers were never retrieved.
Vulnerable access pattern
OCI registries expose image metadata and content through predictable endpoints:
GET /v2/<namespace>/<image>/manifests/<tag-or-digest>
GET /v2/<namespace>/<image>/blobs/<sha256:digest>
For a private image, the authorization decision must bind all of these facts:
requester identity
requested package owner
package visibility: public | internal | private
token scope
repository/package ACL
Public technical analyses describe the vulnerable Gitea path as allowing an anonymous or ghost user to reach the registry API without a package visibility check. Reduced to the important mistake, the access gate looked like this:
func ReqContainerAccess(ctx *context.Context) {
if ctx.Doer == nil || (setting.Service.RequireSignInViewStrict && ctx.Doer.IsGhost()) {
apiUnauthorizedError(ctx)
return
}
// Missing decision: is the requested package owner private/internal/public,
// and is this requester allowed to pull its manifests and blobs?
}
That style of check asks “is there some request context?” but does not answer “is this user authorized for this private package?” For container images, that is enough to leak the full artifact because a manifest points to blobs and blobs contain the filesystem layers.
Why leaked images become credential incidents
Container image privacy is commonly treated as a source-code and secrets boundary. A single image can disclose:
/app source code
package lockfiles
compiled frontend assets
runtime configuration templates
internal service hostnames
database connection strings
cloud SDK config files
private CA bundles
tokens accidentally copied during docker build
build arguments persisted in image history
Even when the final filesystem does not contain a visible secret, image metadata can reveal build commands and layer history:
docker history --no-trunc registry.example.internal/team/app:prod
docker save registry.example.internal/team/app:prod | tar -tf -
For incident response, assume every private image on an affected registry has the same exposure level as a leaked tarball. Then check whether the image also contained credentials that require rotation.
Exposure triage
Start with the registry hosts, not only the source repositories:
# Identify self-hosted Gitea versions.
gitea --version
# Search config for registry and anonymous view settings.
rg -n "PACKAGES|REQUIRE_SIGNIN_VIEW|container|registry" /etc/gitea /var/lib/gitea 2>/dev/null
# Inventory images that were meant to be private.
# Exact paths vary by deployment; export from Gitea's packages UI or database.
For each private image pushed before the fixed release:
- Pull a clean copy from an authenticated account after patching.
- Inspect
docker history --no-truncfor build arguments, commands, and copied secret files. - Export layers and scan for credentials, certificates,
.env,.npmrc,.pypirc,.m2/settings.xml, cloud configs, and SSH material. - Rotate anything found in layers or history, even if the file was deleted in a later Dockerfile step.
Remediation
Upgrade Gitea to 1.26.2 or later. The release includes multiple security fixes, including the package-registry item credited to NoScope.
If an immediate upgrade is not possible, NoScope recommends requiring sign-in for all content as a temporary stopgap:
[service]
REQUIRE_SIGNIN_VIEW = true
That mitigation can break intentionally public repositories and package registries, so treat it as an emergency control while planning the upgrade.
After patching:
- Rotate secrets found inside affected images and any credentials that could deploy those images.
- Rebuild images from clean sources after removing secret-producing Dockerfile steps.
- Delete old private image versions if they contain secrets that cannot be confidently scoped.
- Review Gitea, reverse proxy, and registry logs for unauthenticated
/v2/access to manifests or blobs. - Prefer short-lived workload identity over long-lived credentials baked into images or build contexts.
The core lesson is simple: a private registry is part of the application supply chain. If the registry authorization model fails, the application artifact has leaked even if the Git repository remains private.