A private container registry protects more than image layers. It holds build outputs, deployment manifests, attestations, and sometimes the credentials that define a production release. The common Bearer token flow for the Docker Registry HTTP API v2 looks simple: the registry returns a challenge, the client requests a token from an authentication service, and the client retries with that token. The security decision is hidden in the scope field.

A scope such as repository:example/service:pull allows one repository action set. A token with push can change content. A wildcard or administrative scope can affect many repositories. If the token service grants a broader scope than the caller requested, or if the registry does not enforce the scope it receives, the result is a control-plane failure even when every request is authenticated.

The challenge describes the requested authority

The registry documentation shows a 401 Unauthorized response with a WWW-Authenticate header containing the token realm, service, and scope. The client asks the token service for access to that service and scope. The registry then accepts the returned Bearer token on the repository request.

The realm and service values identify the authorization ecosystem. They should not be taken from an untrusted challenge without validation. A client that follows an arbitrary realm could send its credentials to an attacker-controlled service. A registry client should pin expected registry hosts and authentication realms or use a trust policy that rejects unexpected redirects.

The scope is not just a hint. It should map to repository identity and permitted actions. Pull access should not imply push, delete, catalog, or cross-repository access. A token that is valid for one repository should not authorize another because both happen to belong to the same organization.

Token issuance is an authorization service

The token service must authenticate the caller and decide whether the requested scope is within the caller's authority. It should not translate a successful login into broad repository access. The account may be a personal user, a CI workload, a robot account, or an OIDC-federated identity. Each should have a policy that describes which repositories and actions it can use.

A common mistake is to let the token service mirror the requested scope after a coarse membership check. That makes any authenticated member able to request push access to every repository in a namespace. The token service should compare the requested scope with stored grants and return only the intersection. If no intersection exists, it should fail rather than issue a token with no usable scope and rely on the registry to reject the request later.

Short lifetimes reduce the value of a leaked token but do not fix overbroad scope. A stolen token with ten minutes of push access can still replace an image under a mutable tag. Pair short-lived tokens with digest-only deployment, immutable tags, audit logging, and repository-level policy.

The registry also has to enforce

The bearer token is opaque to the client in many implementations, but the registry must validate its signature, issuer, audience, service, expiry, and scope. A registry that only checks token validity and ignores repository scope is not enforcing the authorization model. Test the behavior with a token issued for one repository and send it to another. The expected result is a denial.

The challenge path should be tested through the same proxy and hostname clients use in production. TLS termination, host rewriting, and authentication gateway rewrites can alter the service or realm. Record the full challenge structure without logging credentials.

Repository naming creates another boundary. Some registries support nested namespaces, project paths, or proxy caches. A policy that treats the first path segment as the tenant must account for every repository that shares that segment. A service that can push an image and a signature artifact may have different risk even when both use the same registry.

Credentials and lifecycle

Robot accounts should be created for a specific consumer and repository set. Do not share one account across every pipeline in the organization. The owner, purpose, creation method, allowed actions, and expiry should be recorded. When a pipeline is retired, revoke the account and its tokens rather than leaving a dormant credential with push rights.

Personal credentials should not be embedded in build scripts. Prefer workload identity or short-lived credentials from a trusted issuer. If a token is exposed, revoke it at the authorization service and review registry audit logs for repository changes. A registry receipt proves that a request was accepted; it does not prove that the image came from the intended source.

Review questions

Ask who can issue registry tokens, which scopes they can request, and how scope is derived from identity. Ask whether the registry verifies the audience and service as well as the signature. Ask which repositories accept anonymous pulls, which accept mutable tags, and which can be changed by automation. Ask whether a compromise of one build account can publish to a production namespace.

The correct design starts with the repository as the authorization unit, not the registry as a single global service. Authentication gets the request to the token service. Scope gets the request to the repository. Audit evidence shows what happened after both decisions were made. A review that checks only the login page has not looked at the boundary.

References