VulnWatch VulnWatch
← Back to dashboard
Low github · GHSA-2v5f-5r6w-p67r

MCP Registry: OCI validator skips ownership check on upstream rate limits

Published May 19, 2026 CVSS 3.5

OCI ownership validation fails open on upstream rate limits, allowing attacker to claim arbitrary public OCI images under their own namespace

Severity: Low (re-scored post-triage; see Maintainer triage note below) Affected: modelcontextprotocol/registry main branch at commit fe0cb3b (current HEAD as of 2026-05-09). Live deployment: https://registry.modelcontextprotocol.io (per repo README). Route: GitHub private security advisory (per repo SECURITY.md).


Title

OCI ownership validation skips label-match check when upstream OCI registry returns HTTP 429, letting any authenticated publisher bind their io.github./* namespace to OCI images they do not control.

Summary

internal/validators/registries/oci.go:104-119 fails open on http.StatusTooManyRequests: when the registry's anonymous fetch to the upstream OCI registry is rate-limited, ValidateOCI returns nil and the publish is accepted without ever running the io.modelcontextprotocol.server.name label-match check at lines 122-141. That label check is the only cross-system ownership proof the registry applies to OCI packages — every other registry type (NPM, PyPI, NuGet, MCPB) treats a non-200 upstream response as a hard error.

The fail-open trigger is attacker-controllable. The registry uses authn.Anonymous against Docker Hub, which is rate-limited to 100 manifest pulls per 6 hours per egress IP, and the production NGINX rate limit allows 180 publishes/minute (3 RPS, burst 540) per source IP. A single attacker from a single IP can exhaust the registry's shared anonymous quota in roughly 33 seconds, then submit a final publish that points packages[].identifier at a Docker Hub image they do not own. The validator hits the 429 fail-open branch, returns nil, and the registry stores a record under the attacker's namespace claiming the unrelated image as its package payload, with no label proof in evidence.

The fail-open is also reached without an attacker present. Docker Hub routinely 429s busy egress IPs during organic traffic, so publishes during those windows skip OCI ownership validation silently.

Vulnerable code

internal/validators/registries/oci.go:97-142:

img, err := remote.Image(ref, remote.WithAuth(authn.Anonymous), remote.WithContext(timeoutCtx))
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        return fmt.Errorf("OCI image validation timed out after 30 seconds for '%s'. The registry may be slow or unreachable", pkg.Identifier)
    }

    var transportErr *transport.Error
    if errors.As(err, &transportErr) {
        switch transportErr.StatusCode {
        case http.StatusTooManyRequests:
            // Rate limited - skip validation to avoid blocking publishers
            // This is intentional: we prioritize UX over strict validation during high traffic
            log.Printf("Skipping OCI validation for %s due to rate limiting", pkg.Identifier)
            return nil                                              //

Affected AI Products

mcp server
Get the weekly digest. Every Monday: top AI security stories of the week. Free.