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

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

1 participant