Safeguard
Supply Chain Security•

OCI Distribution Spec Explained: How Registries Push, Pull and Attach

The OCI Distribution Spec is the HTTP API every container registry speaks. How pull, push and the 1.1 referrers API work, and how to secure them.

Safeguard Security Team
43 min read

The OCI Distribution Specification is the HTTP API that container registries and their clients use to move images and other content: a client fetches a manifest by tag or digest, then fetches the blobs it lists, and a push does the same in reverse. Version 1.1, released in February 2024, added the referrers API, so signatures, SBOMs and attestations can be attached to an image and found later.

Almost every container you run arrived over this protocol. It is small, it is plain HTTP, and it is easy to treat as plumbing. But the security of a deployment depends on details that live here: whether you pulled by a tag someone can move or by a digest nobody can, what a bearer token actually permits, whether the registry will copy a blob between repositories for you, and where the signature for an image is stored. This post walks through the protocol request by request, explains what version 1.1 changed, sets out the weakness classes as concepts, and gives the checks you can run against registries you operate.

Key takeaways

  • The Open Container Initiative (OCI) publishes three specifications: the Image Spec defines what an image is, the Distribution Spec defines the HTTP API that moves it, and the Runtime Spec defines how an unpacked container is run.
  • The Distribution Spec is based on the Docker Registry HTTP API V2. Version 1.0.0 was released on 5 May 2021 and version 1.1.0 on 15 February 2024, alongside Image Spec 1.1.0. The latest tagged release is 1.1.1.
  • Every API path sits under /v2/. A pull fetches a manifest by tag or digest, then each blob by digest. A push uploads blobs first and the manifest last.
  • Authentication is outside the spec. Under the CNCF Distribution token scheme, a registry answers 401 with a WWW-Authenticate: Bearer challenge naming a token service and a scope such as repository:team/app:pull.
  • Digests make content tamper evident only if the digest itself came from somewhere you trust. Tags are mutable; pin production by digest.
  • Version 1.1 added artifactType and subject to manifests and the GET /v2/<name>/referrers/<digest> endpoint. Current Sigstore cosign documentation says signatures use the OCI 1.1 referrer specification.
  • A properly secured registry returns 401 on /v2/ without credentials; the commands below check that, resolve digests, list referrers and run the conformance suite.

What the OCI Distribution Spec is for: the layer and the standard

Before the OCI specifications, the way images moved between machines was defined by one implementation, and other clients and registries had to copy its behaviour. The Distribution Spec's introduction states the goal plainly: it "defines an API protocol to facilitate and standardize the distribution of content." It is also deliberately content agnostic. Container images are the most common payload, but the same API carries Helm charts, WebAssembly modules, policy bundles, SBOMs and signatures.

Distribution, Image and Runtime specs: who defines what

The Distribution Spec constantly refers to the Image Spec for the shape of what it moves, so it helps to know which document owns which word.

SpecificationWhat it definesKey objectsLatest release (GitHub)
Image Format SpecThe format of an image and of other artifacts packaged the same wayImage manifest, image index, image config, filesystem layers, descriptors, digests, annotations, image layoutv1.1.1, March 2025
Distribution SpecThe HTTP API for pushing, pulling, discovering and deleting content in a registryEndpoints under /v2/, upload sessions, tags list, referrers API, error codesv1.1.1, January 2025
Runtime SpecHow a container is configured and run from an unpacked filesystem bundleconfig.json, the filesystem bundle, lifecycle operations, platform specific settings for Linux, Windows, FreeBSD, Solaris, VMs and z/OSv1.3.0, November 2025

The Image Spec describes the hand off between them: once built, an image is discovered by name, downloaded, verified by hash, trusted through a signature and unpacked into an OCI runtime bundle. Discovery and download are the Distribution Spec's job. Verification by hash is shared: the Image Spec defines digests, and the Distribution Spec tells clients when to check them. Trust through a signature is left to other systems, such as Sigstore, which is where the referrers API comes in.

Where it sits in the stack

The Distribution Spec is an application layer protocol that runs over HTTP. The spec says that where it does not define a behaviour, implementations should follow the HTTP semantics in RFC 9110. In practice that means HTTPS on port 443 for any registry that matters. The open source CNCF Distribution registry, the reference implementation that grew out of the original Docker registry code, listens on port 5000 by default, and its deployment guide warns that its first examples are "only appropriate for testing" and that a production registry needs TLS and, ideally, access control. There is no IANA port registration specific to the protocol; it rides on whatever port the HTTP server uses.

Because it is ordinary HTTP, TLS, Authorization headers, redirects and proxies all behave as they do anywhere else. Our explainers on HTTP/1.1, HTTP/2 and TLS cover that layer.

How OCI specifications are released

OCI specifications are not RFCs. Each release is approved by a maintainer vote, published as a tagged GitHub release and announced in a release notice on opencontainers.org. The two dates sometimes differ; this post uses GitHub release dates.

How a pull works on the wire

The spec says a registry must support, at a minimum, all the APIs in the Pull category. Push, content discovery and content management are recommended but optional, and a registry that claims one of those categories must implement all of it.

Step 1: the /v2/ check

A client starts with GET /v2/. The spec says a 200 OK means the registry implements the specification, and that the endpoint may also be used for authentication, which it leaves out of scope. In practice it is where a client learns how to authenticate. The CNCF Distribution deployment guide says a properly secured registry should return 401 when /v2/ is hit without credentials, with a WWW-Authenticate challenge describing how to authenticate, and that responses under /v2/ should carry the header Docker-Distribution-API-Version: registry/2.0.

Step 2: token authentication

The Distribution Spec leaves authentication to the registry. The scheme almost every registry uses is the token authentication specification published with CNCF Distribution. It works like this:

  1. The client tries the operation and receives 401 Unauthorized with a challenge such as WWW-Authenticate: Bearer realm="https://auth.example.com/token",service="registry.example.com",scope="repository:team/app:pull,push". The format follows Section 3 of RFC 6750, the OAuth 2.0 bearer token usage document.
  2. The client calls the realm URL with the service and scope values as query parameters, presenting its own credentials if it has any. A client with no credentials is treated as anonymous.
  3. The token service checks its access control list and returns a token whose access is the intersection of what was requested and what the client is allowed. The spec is explicit that a partial grant is not an error: if you asked for pull,push and may only pull, you get a token for pull.
  4. The client retries the original request with Authorization: Bearer <token>. The registry validates the token's signature and claims and serves the request.

The token response is JSON with a token field (or access_token, both may appear for compatibility), an optional expires_in and an optional issued_at. When expires_in is omitted it defaults to 60 seconds, and the spec says a token "should never be returned with less than 60 seconds to live." The JWT implementation page lists the standard claims (iss, sub, aud, exp, nbf, iat, jti) plus a private access claim that lists each resource type, name and permitted actions.

Scopes follow a small grammar: resourcetype:resourcename:action[,action]. For the repository type the actions are pull for read and push for write. The registry type covers administrative operations that span the whole registry, such as listing every repository. The scope documentation warns that a resource name may contain a colon because of a port number in a hostname, so naive splitting on colons is wrong.

Step 3: fetch the manifest by tag or digest

With a token in hand, the client requests GET /v2/<name>/manifests/<tag-or-digest>. The reference must be either a digest or a tag, nothing else. Repository names must match a lowercase pattern of path components separated by /, and a tag is at most 128 characters drawn from letters, digits, underscore, period and hyphen, not starting with a period or hyphen.

The client sends an Accept header listing the manifest media types it understands. The registry answers 200 OK with a Content-Type naming the manifest type and a Docker-Content-Digest header carrying the manifest's digest. Version 1.1.1 tightened the client side: if the client asked by digest, it should verify the returned bytes hash to that digest, and if it uses the Docker-Content-Digest header at all, it must verify the header matches the body. A HEAD request on the same URL returns the digest and Content-Length without the body, which is how tools resolve a tag to a digest cheaply.

If the manifest is an image index, the client picks the entry whose platform (operating system, architecture, optional variant) matches the host and fetches that platform's image manifest by digest. If it is already an image manifest, it moves on.

Step 4: fetch the blobs

The image manifest lists a config descriptor and a layers array of descriptors. The client fetches each with GET /v2/<name>/blobs/<digest>. The spec says clients should verify the response body matches the requested digest. Registries should support HTTP Range requests so an interrupted download can resume, and they may redirect blob requests to storage elsewhere, for example to a signed URL on object storage.

Client                         Registry                     Token service
  |  GET /v2/                     |                                |
  |------------------------------>|                                |
  |  401 WWW-Authenticate: Bearer realm, service, scope            |
  |<------------------------------|                                |
  |  GET realm?service=...&scope=repository:team/app:pull          |
  |--------------------------------------------------------------->|
  |  200 token, expires_in                                         |
  |<---------------------------------------------------------------|
  |  GET /v2/team/app/manifests/1.4  Accept: index, manifest       |
  |------------------------------>|                                |
  |  200 Content-Type: index  Docker-Content-Digest: sha256:aa...  |
  |<------------------------------|                                |
  |  GET /v2/team/app/manifests/sha256:bb... (linux/amd64 entry)  |
  |------------------------------>|                                |
  |  200 image manifest                                            |
  |<------------------------------|                                |
  |  GET /v2/team/app/blobs/sha256:cc... (config)                 |
  |  GET /v2/team/app/blobs/sha256:dd... (each layer)             |
  |------------------------------>|                                |
  |  200 bytes, or 307 to storage                                  |
  |<------------------------------|                                |
  |  verify every digest, unpack                                   |

The spec's own "Content Verification" use case captures why this order matters. An untrusted registry returns a manifest; after each layer is downloaded, the engine checks the layer digest against the manifest. The layers are only as trustworthy as the manifest that names them, and the manifest is only as trustworthy as the way you obtained its digest.

How a push works on the wire

A push runs in the opposite order: blobs first, manifest last. That way a manifest never points at content the registry does not have yet. The spec allows a registry to reject a manifest whose descriptors reference missing blobs, with a MANIFEST_BLOB_UNKNOWN error. There is one deliberate exception, covered in the 1.1 section: a manifest whose subject points at a manifest that does not exist yet must be accepted.

Checking first

Before uploading, a client usually sends HEAD /v2/<name>/blobs/<digest> for each blob. A 200 means the registry already has it, so shared base layers are rarely uploaded twice.

Monolithic uploads

There are two monolithic forms. In the first, POST /v2/<name>/blobs/uploads/ returns 202 Accepted with a Location header, the upload session URL, and the client then sends PUT <location>?digest=<digest> with the whole blob as the body. The spec says the location may contain critical query parameters, must be used exactly as returned, and must not be assembled by the client. Success is 201 Created with a Location for the finished blob.

In the second form, which registries may support, the client sends a single POST /v2/<name>/blobs/uploads/?digest=<digest> with the blob as the body. A registry that does not support this should answer 202 with a session location, and the client falls back to the PUT.

Chunked uploads

Large blobs can be uploaded in pieces. The client opens a session with a POST carrying Content-Length: 0, then sends each chunk with PATCH <location> and a Content-Range header giving the inclusive byte range. The first chunk starts at 0. Each successful chunk returns 202 with an updated Location and a Range: 0-<end> header. Chunks must arrive in order; an out of order chunk gets 416 Requested Range Not Satisfiable, and the client can GET the session URL to learn the current offset. Version 1.1 added the optional OCI-Chunk-Min-Length response header, so a registry can tell clients the smallest chunk it will accept. The session closes with PUT <location>?digest=<digest>, where the digest is of the whole blob, optionally carrying the final chunk.

A client abandoning an upload should send DELETE <location>; otherwise the server should eventually time out the session. Open sessions consume storage, so operators need limits here.

Cross-repository mounts

If the blob already exists in another repository on the same registry, the client can ask the registry to link it instead of uploading it: POST /v2/<name>/blobs/uploads/?mount=<digest>&from=<other_name>. Success is 201 Created. If the registry cannot or will not mount it, it returns 202 and an ordinary upload session. The spec also allows a registry to treat from as optional and mount the blob from wherever it finds it. That last permission is relevant to the security section below.

Pushing the manifest

Finally the client sends PUT /v2/<name>/manifests/<tag-or-digest> with the manifest as the body and a Content-Type matching the manifest's mediaType field. The registry must store the manifest byte for byte exactly as the client sent it, which is what keeps digests stable: the digest is a hash of those bytes, so any reformatting would change it. Success is 201 Created with a Location and a Docker-Content-Digest that must equal the client's digest. Registries should enforce a maximum manifest size and answer 413 Payload Too Large above it, and both sides should expect to handle manifests of at least 4 megabytes.

Pushing to a tag creates or moves that tag. Pushing by digest stores the manifest without a tag, which is how multi platform images are assembled: each platform manifest is pushed by digest, then the index that lists them is pushed to the tag.

Content discovery and management

GET /v2/<name>/tags/list returns the repository's tags in lexical order, with n and last parameters for pagination and an RFC 5988 Link header with rel="next" when more remain. Deletion is optional: DELETE /v2/<name>/manifests/<digest> removes a manifest, DELETE on a tag removes just the tag, and DELETE /v2/<name>/blobs/<digest> removes a blob. Registries that disable deletion must answer 400 or 405.

Endpoint summary

IDMethodPathSuccessPurpose
end-1GET/v2/200Version and auth check
end-2GET / HEAD/v2/<name>/blobs/<digest>200Fetch or check a blob
end-3GET / HEAD/v2/<name>/manifests/<tag-or-digest>200Fetch or check a manifest
end-4aPOST/v2/<name>/blobs/uploads/202Open an upload session
end-4bPOST/v2/<name>/blobs/uploads/?digest=<digest>201 / 202Single request upload
end-5PATCH<location>202Upload a chunk
end-6PUT<location>?digest=<digest>201Close the session
end-7PUT/v2/<name>/manifests/<tag-or-digest>201Push a manifest
end-8a / 8bGET/v2/<name>/tags/list200List tags, paginated
end-9DELETE/v2/<name>/manifests/<tag-or-digest>202Delete a tag or manifest
end-10DELETE/v2/<name>/blobs/<digest>202Delete a blob
end-11POST/v2/<name>/blobs/uploads/?mount=<digest>&from=<other>201 / 202Cross-repository mount
end-12a / 12bGET/v2/<name>/referrers/<digest>200List referrers (1.1)
end-13GET<location>204Upload status
end-14DELETE<location>204Cancel an upload

Errors use a JSON body with an errors array; the spec defines 14 codes, from BLOB_UNKNOWN and DIGEST_INVALID to UNAUTHORIZED, DENIED and TOOMANYREQUESTS. A 429 should carry Retry-After. Version 1.1 also defined informational Warning headers with code 299, for messages such as a deprecated image, capped at 4,096 bytes in total, and clients must not take automated action based on them.

Manifests, descriptors and digests: the data the protocol moves

The protocol only makes sense once you see the objects it carries. Everything in a manifest that points at something else does so through a descriptor.

Descriptor fieldRequiredMeaning
mediaTypeYesMedia type of the referenced content, per RFC 6838
digestYesalgorithm:encoded hash of the content, for example sha256: plus 64 lowercase hex characters
sizeYesSize in bytes; consumers should check size before hashing
urlsNoAlternative download locations
annotationsNoString to string metadata
dataNoThe content itself, base64 encoded, for very small blobs
artifactTypeNoType of an artifact, when the descriptor points at one (added in 1.1)

The Image Spec says implementations must support SHA-256 verification and may support SHA-512, and that compliant implementations should use SHA-256. It also says that when the digest itself reaches you through a secure channel, you can verify content from an insecure source by recomputing the digest yourself. That condition is the whole security model of content addressing in one line.

An image manifest has schemaVersion 2, a mediaType of application/vnd.oci.image.manifest.v1+json, a config descriptor, a layers array in stack order with the base layer at index 0, and optional annotations. An image index has the type application/vnd.oci.image.index.v1+json and a manifests array whose entries carry a platform object. The pre-defined annotations org.opencontainers.image.base.name and org.opencontainers.image.base.digest record which base image an image was built from.

What Distribution Spec 1.1 added: artifactType, subject and the referrers API

Version 1.0 could store arbitrary content, but there was no standard way to say "this SBOM belongs to that image." Tools invented conventions, usually special tag names derived from the image digest. Version 1.1 of the Distribution and Image specs, released together on 15 February 2024, standardised the relationship.

artifactType and the empty descriptor

Image Spec 1.1 added an optional artifactType field to the image manifest. When a manifest packages something other than a runnable image, artifactType names what it is, and the config can be set to the new empty descriptor: media type application/vnd.oci.empty.v1+json, the two byte JSON object {}, size 2, with the fixed digest sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a. The spec's artifact guidance gives a decision tree: an artifact with no files uses the empty descriptor for both config and a single layer; an artifact with files but no JSON config puts the files in layers; an artifact with its own config uses a config media type of its own. Implementations that copy manifests must not fail on an artifactType they do not recognise.

subject and OCI-Subject

The manifest also gained an optional subject field: a descriptor of another manifest. The Image Spec describes it as a weak association to a separate graph of content. A signature manifest whose subject is the image manifest's descriptor is, by definition, a referrer of that image.

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "artifactType": "application/vnd.example.sbom.v1",
  "config": {
    "mediaType": "application/vnd.oci.empty.v1+json",
    "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
    "size": 2
  },
  "layers": [
    {
      "mediaType": "application/vnd.example.sbom.v1",
      "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317",
      "size": 1234
    }
  ],
  "subject": {
    "mediaType": "application/vnd.oci.image.manifest.v1+json",
    "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
    "size": 7682
  }
}

The Distribution Spec says a registry must accept a manifest whose subject references a manifest that does not exist yet, so a client can push an image and its referrers in either order. A registry that supports the referrers API must answer such a push with the header OCI-Subject: <subject digest>, which tells the client the registry has indexed the relationship itself.

The referrers API

GET /v2/<name>/referrers/<digest> returns an image index whose manifests array lists every manifest or index in the same repository whose subject is that digest. Each entry carries the referrer's artifactType (or, if the manifest has none, its config media type) and copies of its annotations, so a client can choose what to fetch without fetching everything. A registry that supports the API must never return 404 for it; with no referrers it returns an empty list. Long lists are paginated with a Link header. Registries should support filtering with ?artifactType=<type> and, when they apply it, must say so in an OCI-Filters-Applied: artifactType header.

The tag schema fallback

Registries did not all add the endpoint on day one, so 1.1 defines a fallback. If a client pushes a manifest with a subject and gets no OCI-Subject header back, the client maintains the list itself: it pulls an image index stored under a tag derived from the subject digest (the algorithm, a hyphen, and the hex, for example sha256- followed by the 64 characters), appends a descriptor for the new referrer, and pushes the index back to that tag. A client that queries the referrers endpoint and gets 404 must read that tag instead. The spec is frank about the weakness: several clients updating the tag at once can race and lose data, and it leaves protection against that to clients and users. Clients may use conditional requests where the registry supports ETags. When a registry later enables the API, it must include manifests already listed in such tags.

What is in the development branch but not released

The main branch of the Distribution Spec on GitHub contains changes that are not in any tagged release as of 23 October 2026. They include several tag parameters on a manifest push, a digest-algorithm upload parameter, a Registry Proxying section with an ns parameter, and a rule against forwarding Authorization headers across hosts on redirect. Treat these as proposals until released.

How signatures, SBOMs and in-toto attestations attach as referrers

The referrers API is how supply chain metadata travels with an image without changing the image. Because the image's own manifest is untouched, its digest stays the same, and anything that pinned that digest keeps working.

Signatures. Sigstore's cosign signs the image manifest digest. The current cosign documentation for signing containers says "Signatures use the OCI 1.1 referrer specification" and that cosign tree lists the signatures attached to an image. Older cosign releases stored signatures under the tag based scheme in cosign's signature specification, where the image digest is turned into a tag such as sha256-<hex>.sig in the same repository. You will still meet both layouts in registries today. cosign can also keep a signature out of the registry entirely as a Sigstore bundle file, and its documentation recommends ORAS to attach that bundle later as a referrer with the bundle's artifact type. Our post on signing container images with cosign and our explainer on what Sigstore is go further.

In-toto attestations. An in-toto attestation is, in the framework's own words, "authenticated metadata about one or more software artifacts." It has layers: a predicate with type specific content (provenance, an SBOM, test results), a Statement that binds the predicate to subjects by digest and has _type https://in-toto.io/Statement/v1, and an envelope that handles signing. cosign attest wraps a predicate in a signed in-toto statement about the image and stores it with the image, and cosign verify-attestation checks it, optionally against CUE or Rego policies. Our in-toto attestation explainer covers the format.

SBOMs and provenance. An SBOM can be pushed as its own artifact manifest with an artifactType that names the format and a subject pointing at the image. Some build tools instead store SBOM and provenance attestations inside the image index; our guide to turning on build SBOM and provenance attestations shows one such layout.

The design point to remember: a referrer is just a manifest that anyone with push rights can create. The referrers API makes metadata findable, not trustworthy; only verification against a key or identity you chose makes it mean something.

Versions and history

ReleaseDate (GitHub release)What changed
Docker Registry HTTP API V2Before OCIThe original document the Distribution Spec is based on; it replaced an earlier V1 registry API
Image Spec and Runtime Spec v1.0.019 July 2017First stable image format and runtime configuration
Distribution Spec v1.0.0-rc014 February 2019First release candidate of the distribution API
Distribution Spec v1.0.05 May 2021First stable release, with pull, push, content discovery and content management categories
Distribution Spec v1.0.1 / Image Spec v1.0.217 November 2021Maintenance releases
Runtime Spec v1.1.0July 2023Runtime update
Distribution Spec v1.1.0 / Image Spec v1.1.015 February 2024artifactType, subject, empty descriptor, referrers API and tag schema fallback, OCI-Subject, OCI-Filters-Applied, OCI-Chunk-Min-Length, Warning headers
Runtime Spec v1.2.013 February 2024Runtime update
Distribution Spec v1.1.129 January 2025Clarifications: clients should verify manifests and blobs against the requested digest, registries should support Range, a Repository definition and a client name length note
Image Spec v1.1.13 March 2025Maintenance release
Runtime Spec v1.3.04 November 2025Current runtime release; the spec now documents Linux, Windows, FreeBSD, Solaris, VM and z/OS platforms

The OCI release notices page gives some different dates: Image Spec v1.1.0 on 5 February 2024, Distribution Spec v1.1.1 on 1 March 2025 and Image Spec v1.1.1 on 2 April 2025. Those notices start a patent policy clock and need not match the tag date.

Deprecated: the legacy schema1 manifest format (clients may still meet its error codes but should not depend on them) and the non-distributable layer media types. Still widely deployed: registries without the referrers endpoint, which is why every 1.1 client must implement the tag fallback.

Security properties of the distribution protocol

It is worth being precise about what the protocol guarantees, because several common assumptions are wrong.

PropertyProvided by the protocol?Notes
Integrity of content against a known digestYes, if the client verifiesEvery blob and manifest is addressed by a cryptographic hash; the spec tells clients to verify
Integrity of the tag to digest mappingNoA tag is a mutable pointer that anyone with push rights can move
Authenticity (who built or approved this)NoNeeds signatures or attestations verified against a chosen identity
Freshness (is this the latest, and not a replay)NoA registry or mirror can serve an old manifest for a tag; this is the gap TUF style metadata addresses
Confidentiality in transitOnly with TLSThe API is plain HTTP; the deployment guide requires TLS for production
Authentication and authorisationOutside the specToken or basic authentication defined by registry implementations
AvailabilityPartlyError codes, 429 with Retry-After and size limits exist, but limits are left to implementations

In short: digests give tamper evidence for content whose digest you already trust. Which digest to trust, who produced it and whether it is current must come from outside the registry API.

Weakness classes, explained as concepts

These are the classes of problem the protocol's design allows. None of them requires a flaw in a particular registry; they come from how the pieces fit together.

Mutable tags

A tag can be pointed at a different manifest by any push to that tag. The definition in version 1.1.1 says it plainly: a tag is "a custom, human-readable pointer to a manifest", and one digest can have any number of tags. A deployment that names app:1.4 gets whatever 1.4 points to at that moment; a leaked push credential, a wrong build or a cache refresh can change the bytes behind the name. Nothing in the protocol warns the puller. Where a registry offers tag immutability, it is a registry setting; the spec does not require it.

Digest pinning that stops halfway

Pinning by digest fixes the mutable tag problem only if the digest is pinned everywhere the decision is made. Common gaps: one manifest pins a digest while a Helm value still uses a tag, a second build stage uses an unpinned base, or the digest is resolved from the tag at deploy time, which simply trusts the tag. A digest is only as good as the place you got it from, which is the Image Spec's own condition: it has to reach you through a channel you trust.

Cross-repository mounts

A mount request asks the registry to link a blob from another repository without uploading it. If the registry does not check that the requester can read the source repository, a client with push rights to one repository could link content from a repository it cannot read, and then read it back through its own. The spec permits registries to treat from as optional and mount from anywhere they find the blob, which makes this an authorisation decision every registry has to get right: a mount should succeed only when the requester could have read the source blob anyway. The conformance suite has a setting, OCI_API_BLOBS_MOUNT_ANONYMOUS, that tests mounting without a named source, which is a reminder that this is real, expected behaviour to account for.

Anonymous pull exposure

The token service treats a client with no credentials as anonymous and grants whatever anonymous users are allowed, by design. The weakness is drift: a private repository created under a public namespace, an open tags list, or a health check workaround that exposes /v2/. Images often carry internal hostnames and configuration never meant to be public.

Token scope and token lifetime

Bearer tokens are only as narrow as the scope requested and granted. A pipeline that asks for pull,push on every repository, because that was easiest, carries a token that can move any tag. Refresh tokens are worse: the scope documentation says a refresh token is not scoped to specific resources for an audience, so it must only be used with the authorisation server and never sent to a resource provider. The short default access token lifetime helps, but long expires_in values configured for convenience erase that benefit.

Credentials crossing hosts: redirects and proxies

Blob downloads are commonly redirected to object storage, and pull through caches sit between clients and upstream registries. Both create chances for an Authorization header to reach a host it was not meant for. The development branch of the spec adds an explicit rule against forwarding Authorization across host boundaries, and the CNCF Distribution mirror guide warns that if you give a pull through cache upstream credentials, private content that user can see becomes available through your mirror unless you add authentication to the mirror itself.

Unverified referrers and referrer races

Because any client with push rights can attach a referrer, a malicious or mistaken referrer can sit next to a legitimate one: a second "signature" that is not valid, an SBOM that describes a different build. Tools that treat the presence of a referrer as proof are making a category error. On registries without the referrers API, the tag schema fallback adds a second problem: concurrent writers can overwrite each other's updates, so a referrer can silently drop out of the list.

Freshness, rollback and freeze

A registry or mirror can serve an older manifest for a tag, or stop updating, and the protocol has no way for the client to notice. The Update Framework specification names these as rollback, indefinite freeze and mix-and-match attacks and defines signed, versioned, expiring metadata to stop them. Our post on TUF and how Sigstore uses it explains the model; the takeaway here is that the distribution API alone does not provide it.

Resource exhaustion

Upload sessions, large manifests and long referrer lists consume resources. The spec gives the hooks (413, 416, session timeouts, 429) but leaves the limits to each registry.

Secure configuration, with defender-side check commands

Only run these commands against registries and images you own or are authorised to test. Each command comes from the official documentation linked in the sources.

1. Confirm /v2/ demands authentication

The CNCF Distribution deployment guide says a properly secured registry returns 401 on /v2/ without credentials, with a WWW-Authenticate challenge. Check it with curl's -i (show response headers) and -s (silent) options:

curl -si https://registry.example.com/v2/

Expect HTTP/2 401 (or HTTP/1.1 401), a WWW-Authenticate header naming either Basic or a Bearer realm, and Docker-Distribution-API-Version: registry/2.0. A 200 without credentials means anonymous access is enabled for the whole API, which may be intentional for a public mirror but should never be a surprise. The guide also recommends load balancer health checks treat 401 as healthy, so a misconfiguration that drops authentication shows up as an outage rather than silent exposure.

For CNCF Distribution, the guide requires TLS before any authentication and bcrypt hashes for htpasswd, and warns that wrong X-Forwarded-Proto or Host values from a load balancer can make the registry redirect clients to internal hostnames or from https to http.

2. Resolve tags to digests and pin them

crane, from the go-containerregistry project, prints the digest a reference currently resolves to:

crane digest registry.example.com/team/app:1.4
crane digest --full-ref registry.example.com/team/app:1.4

The Docker CLI's imagetools inspect shows the index, each platform's manifest digest and attached attestations, and --raw prints the original JSON:

docker buildx imagetools inspect registry.example.com/team/app:1.4
docker buildx imagetools inspect --raw registry.example.com/team/app:1.4

Then pull and deploy by digest. The Docker CLI reference describes pulling by digest as pulling by an "immutable identifier":

docker pull registry.example.com/team/app@sha256:<digest-from-your-build-record>

Record the digest at build time, from the push output or your build system, not by resolving the tag again at deploy time. That is the step that makes the pin meaningful.

3. List what is attached to an image

ORAS lists referrers, and lets you choose which discovery method to test, which is a quick way to see whether your registry implements the referrers endpoint or relies on the tag schema:

oras discover registry.example.com/team/app:1.4
oras discover --distribution-spec v1.1-referrers-api registry.example.com/team/app:1.4
oras discover --distribution-spec v1.1-referrers-tag registry.example.com/team/app:1.4
oras discover --artifact-type application/vnd.example.sbom.v1 registry.example.com/team/app:1.4

ORAS marks oras discover as a preview command, so its flags may change. cosign has its own view of what it has attached:

cosign tree registry.example.com/team/app@sha256:<digest>

4. Verify signatures and attestations, not just their presence

For keyless signatures, the cosign documentation pins the expected signer identity and issuer:

cosign verify registry.example.com/team/app@sha256:<digest> \
  --certificate-identity=release@example.com \
  --certificate-oidc-issuer=https://accounts.example.com

For self managed keys, cosign verify --key cosign.pub <image>. For attestations, cosign verify-attestation --key cosign.pub <image>, with --policy to evaluate a CUE or Rego policy against the attestation contents. Run verification where the image is admitted, not only on a laptop.

5. Run the OCI conformance suite against your registry

The Distribution Spec repository ships a conformance test. It is configured with environment variables or an oci-conformance.yaml file, needs Go 1.24 or later to run directly, and writes result.yaml, report.html and junit.xml to a results directory. Point it at test repositories you control:

export OCI_REGISTRY="registry.example.com"
export OCI_TLS="enabled"
export OCI_VERSION="1.1"
export OCI_REPO1="conformance/repo1"
export OCI_REPO2="conformance/repo2"
export OCI_USERNAME="conformance-bot"
export OCI_PASSWORD="$CONFORMANCE_PASSWORD"
go run -buildvcs=true .

The suite writes and deletes content, so use dedicated repositories. For a read only registry, set OCI_API_PUSH=false, preload images and list them in the OCI_RO_DATA_* variables. Settings such as OCI_API_REFERRER and OCI_API_BLOBS_MOUNT_ANONYMOUS show which optional behaviours your registry supports. Registry projects can self certify by submitting results, and accepted results are listed at conformance.opencontainers.org.

6. Configure pull through caches deliberately

Per the CNCF Distribution mirror guide, a pull through cache refreshes a tag from upstream when it is pulled, mirrors one upstream at a time, cleans up only if deletion is enabled, and exposes private content its upstream credentials can see unless you secure it. Our post on pull through cache security trade-offs goes into detail. The short version: pin by digest so the cache cannot change what you run, and authenticate the cache like any other registry.

7. Scope credentials narrowly

Give each pipeline push rights only to the repositories it builds, give deploy systems pull only access, keep refresh tokens away from everything but the token service, and review which namespaces allow anonymous pull.

Where Safeguard fits

Safeguard does not run a registry token service, proxy registry traffic, or enforce tag immutability in your registry; those controls belong to the registry itself. The connection is the content. Container visibility and scanning, part of ESSCM and generally available, inventories what is inside an image, so safeguard scan --image <name> lists the packages and libraries in a pulled image with their versions and known vulnerabilities. SBOM generation and analysis in SPDX and CycloneDX is generally available, which pairs naturally with SBOMs stored as referrers: you can generate one for an image digest or analyse one you pulled from the registry. Attestation and signing support for SLSA, Sigstore and in-toto is generally available. Policies, gates and guardrails at build, CI and release, also generally available, let you stop an image whose scan fails policy from moving forward. Gold Registry, generally available at registry.safeguard.sh, provides hardened, drop in replacements for upstream packages and container images, each with SLSA L3 or higher provenance and a Sigstore signature. Our post on using SBOMs to find affected products fast shows the lookup flow when a vulnerable component turns up.

Registry protocol vs pull-through cache vs mirror

OptionWhat it isSecurity propertiesOperational costUse it when
Registry speaking the Distribution SpecThe origin store for your images, accepting pushesYou control authentication, scopes, deletion, immutability settings and referrers supportStorage, backups, garbage collection, token serviceYou build and publish images
Pull through cacheA registry configured with an upstream; fetches on first pull and serves from local storage afterReduces upstream exposure and rate limits; can serve stale content; shares upstream credentials with its users unless secured; refreshes tags from upstreamLow; one upstream per cache in CNCF Distribution; needs deletion enabled for cleanupYou consume public images and want speed, resilience and one egress point
Mirror (copied content)Images copied deliberately into your own registry by a sync jobContent changes only when your job copies it; you can verify signatures before copyingSync jobs, storage, keeping copies currentYou need a reviewed, controlled set of external images

Tags vs digests: decision table

SituationUse a tagUse a digest
Developer pulling a base image to experimentYesNot needed
Production deployment manifestNoYes, recorded at build time
Base image in a Dockerfile for a release buildNoYes, with an automated update process
Human readable release name in a changelogYes, alongside the digestYes
Signing and attestationNo, sign the digestYes, signatures and referrers are bound to the manifest digest
Admission policyNoYes, verify signature and policy for the digest
Rolling to a patched buildMove the tag for humansUpdate the pinned digest through a reviewed change

Tags for people, digests for machines, and automated pull requests to move the pins so you do not freeze on a vulnerable build.

Common mistakes

  • Treating a tag as a version. A tag is a pointer that can move. Two pulls of app:1.4 a week apart can return different images.
  • Pinning a digest you resolved at deploy time. That pins whatever the tag said at that moment, not what you built and tested.
  • Assuming a referrer is a signature. The existence of a signature artifact proves nothing until it is verified against an identity you chose in advance.
  • Checking only one attachment layout. Signatures and attestations can be referrers, digest derived tags or entries inside an image index.
  • Leaving /v2/ open behind a health check. If the load balancer needed a 200 and someone disabled authentication to get one, the whole registry is anonymous. Use 401 as the healthy status instead.
  • Broad pipeline credentials. A CI token with push on every repository can move any production tag. Scope by repository.
  • Giving a pull through cache credentials without securing the cache. Private upstream content becomes readable by anyone who can reach the cache.

FAQ

What is the OCI Distribution Spec?

It is the Open Container Initiative's specification for the HTTP API registries expose for pushing, pulling, discovering and deleting content. It derives from the Docker Registry HTTP API V2; the current release line, 1.1, began on 15 February 2024.

What is the difference between the OCI Image Spec and the Distribution Spec?

The Image Spec defines the objects: manifests, indexes, configs, layers and descriptors. The Distribution Spec defines how those objects move between a client and a registry over HTTP. The Runtime Spec then defines how an unpacked image is configured and run.

What port does a container registry use?

The protocol runs over HTTP, so production registries normally use HTTPS on port 443. The open source CNCF Distribution registry listens on port 5000 by default, which is why local examples use localhost:5000. There is no protocol specific IANA port.

What is the OCI referrers API?

Added in Distribution Spec 1.1, GET /v2/<name>/referrers/<digest> returns an index of every manifest in the repository whose subject field points at that digest. It is how tools find signatures, SBOMs and attestations attached to an image. Registries without it fall back to a tag derived from the digest.

Should I pull container images by tag or by digest?

Use digests for anything automated: production deployments, release builds, signing and admission policy. Tags are fine for people and experiments. Record the digest at build time and automate updates so pinning does not leave you on old builds.

Is the OCI Distribution protocol encrypted?

Not by itself. It is plain HTTP, and confidentiality comes from TLS. The CNCF Distribution deployment guide requires TLS for production and will not accept credentials over clear text.

Where does cosign store signatures?

The current cosign documentation says signatures use the OCI 1.1 referrer specification, discoverable with cosign tree. Older versions used a digest derived tag ending in .sig, which you will still find in many registries.

How do I test whether my registry is OCI conformant?

Run the conformance suite from the opencontainers/distribution-spec repository against test repositories you control. It writes result.yaml, report.html and junit.xml, and has a read only mode.

Fix it without waiting for upstream

Registry protocol bugs and image vulnerabilities usually live in code you depend on: the registry server, the client libraries in your build and deploy tools, and above all the packages baked into the images you pull and push. Those are dependencies like any other, so the gap between a fix shipping and your images carrying it is yours to close.

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

These demos show image, dependency, SBOM and remediation workflows, not registry traffic or token handling. You can download Safeguard and follow the CLI docs to try them on your own projects.

This demo sets up the Safeguard runner on your own machine, approves it with its device code and then scans from the terminal. Pointed at an image you just pulled by digest, the same scan lists the packages and libraries inside it with their versions and known vulnerabilities, which is the inventory you want before you move a tag or update a pinned digest.

Click through it at your own pace, or open it full screen.

This demo is a real CLI session: install, open the interactive terminal, ask it to fix the lodash CVE in a repository, and watch it read the finding and advisory, ask before each command, move lodash to 4.17.21, run npm install and npm test with approval, and print a summary without pushing anything. The same approve each step flow applies when the fix is a newer base image digest in a Dockerfile or a deployment manifest.

Click through it at your own pace, or open it full screen.

This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, you can ask in plain language which of your projects or images include a given library version, and get the answer from your own scan data rather than from memory.

Click through it at your own pace, or open it full screen.

Quick check

Your deployment pins an image by digest, but the digest is looked up from the registry's tag just before each deploy. What does the pin actually protect against?

Very little. The digest is whatever the tag pointed to at lookup time, so a moved tag still changes what you run. The pin only helps once the digest is recorded at build time, from the build you tested and signed, and carried to deployment through a reviewed change.

The referrers API for an image lists two signature artifacts. Is the image signed by your release team?

You cannot tell from the list. Anyone with push rights to the repository can attach a referrer. The image is signed by your release team only if a verifier checks a signature against the identity and issuer, or the key, that you expect, for example with cosign verify and pinned certificate identity flags.

Sources

Last reviewed: 23 October 2026. The Distribution Spec main branch contains unreleased changes; check the tagged release your registry implements before relying on any feature described above.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.