Skip to content

fix: pull-through fallback token scope for registries returning placeholder scope (e.g. ghcr.io) - #149

Open
ariesy wants to merge 2 commits into
cloudflare:mainfrom
ariesy:fix/ghcr-fallback-scope
Open

fix: pull-through fallback token scope for registries returning placeholder scope (e.g. ghcr.io)#149
ariesy wants to merge 2 commits into
cloudflare:mainfrom
ariesy:fix/ghcr-fallback-scope

Conversation

@ariesy

@ariesy ariesy commented Aug 8, 2026

Copy link
Copy Markdown

Fixes #148

Summary

Pull-through fallback to ghcr.io (and any registry whose /v2/ endpoint returns a scope attribute in WWW-Authenticate) currently fails with manifest unknown (404). Two issues in src/registry/http.ts:

  1. Double-prefixed scope: authenticate() only overrides the upstream scope when empty, but ghcr.io returns a placeholder scope (repository:user/image:pull), so authenticateBearer() builds repository:repository:user/image:pull:pull,push which the token endpoint rejects.

  2. Unnecessary push permission: authenticateBearer() requests :pull,push, but ghcr.io's token endpoint returns 403 DENIED for combined scopes and only accepts single :pull. Fallback is read-only, so push permission is not needed.

Changes

src/registry/http.ts:

  • Always use the request namespace for the token scope, ignoring the placeholder scope returned by upstream /v2/
  • Request only :pull permission instead of :pull,push

Verification

Tested against a deployed instance with REGISTRIES_JSON = Docker Hub + ghcr.io + registry.k8s.io + quay.io + gcr.io:

Registry Image Before After
ghcr.io actions/actions-runner 404 200
ghcr.io home-assistant/home-assistant 404 200
ghcr.io open-telemetry/opentelemetry-collector-releases/opentelemetry-collector 404 200
docker.io library/alpine 200 200
registry.k8s.io pause 200 200
quay.io prometheus/node-exporter 200 200
gcr.io google-containers/pause 200 200

ariesy added 2 commits August 8, 2026 14:27
…e (e.g. ghcr.io)

Two issues prevented pull-through fallback from working with ghcr.io:

1. The scope returned by the upstream /v2/ endpoint (e.g. ghcr.io returns
   "repository:user/image:pull") was used as-is and then double-prefixed
   with "repository:", producing garbage scopes like
   "repository:repository:user/image:pull:pull,push" which the token
   endpoint rejects. Always use the actual request namespace instead.

2. Requested scope included push permission (":pull,push") even though
   fallback only performs read operations. ghcr.io rejects combined
   pull,push scopes with 403 DENIED; requesting only ":pull" works.

Verified: ghcr.io (actions/actions-runner, home-assistant, otel-collector)
now returns 200 through fallback; docker.io, registry.k8s.io, quay.io and
gcr.io regression tests all pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pull-through fallback fails with ghcr.io: token scope is double-prefixed and includes push permission

1 participant