CVE-2026-85747
Distribution: Client `manifests.Get()` does not verify returned content against requested digest
Distribution is a toolkit to pack, ship, store, and deliver container content. Prior to 3.1.2, registry/client.(*manifests).Get() reads a manifest returned for a digest-pinned request and passes it to distribution.UnmarshalManifest without hashing the body against the requested digest, allowing a malicious or intercepted registry response to substitute attacker-chosen manifest content even when Docker-Content-Digest claims the requested value. The bundled pull-through proxy in registry/proxy/proxymanifeststore.go can cache and re-serve the substituted manifest. This issue is fixed in version 3.1.2.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
6.8
Distribution: Client `manifests.Get()` does not verify returned content against requested digest
Distribution is a toolkit to pack, ship, store, and deliver container content. Prior to 3.1.2, registry/client.(*manifests).Get() reads a manifest returned for a digest-pinned request and passes it to distribution.UnmarshalManifest without hashing the body against the requested digest, allowing a malicious or intercepted registry response to substitute attacker-chosen manifest content even when Docker-Content-Digest claims the requested value. The bundled pull-through proxy in registry/proxy/proxymanifeststore.go can cache and re-serve the substituted manifest. This issue is fixed in version 3.1.2.
- Record
CVE-2026-85747- CVSS 3.1
- 6.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N - Weakness
- CWE-354
- Updated
- 2026-11-02
EUVDEUVD-2026-99225
6.8
Distribution is a toolkit to pack, ship, store, and deliver container content. Prior to 3.1.2, registry/client.(*manifests).Get() reads a manifest returned for a digest-pinned request and passes it to distribution.UnmarshalManifest without hashing the body against the requested digest, allowing a malicious or intercepted registry response to substitute attacker-chosen manifest content even when Docker-Content-Digest claims the requested value. The bundled pull-through proxy in registry/proxy/proxymanifeststore.go can cache and re-serve the substituted manifest. This issue is fixed in version 3.1.2.
- Record
EUVD-2026-99225- CVSS 3.1
- 6.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-685w-q87j-wqw3
6.8
Distribution: Client `manifests.Get()` does not verify returned content against requested digest
Summary
registry/client.(*manifests).Get(ctx, dgst, …) fetches a manifest by digest but never hashes the returned body and compares it against the requested digest. A malicious or compromised registry, or any MITM on the registry connection, can serve arbitrary manifest bytes in response to a digest-pinned request and the client returns the substituted content transparently to the caller. Any application relying on digest pinning for supply-chain integrity — image policy controllers, mirrors, pull-through caches — is silently undermined. The bundled pull-through proxy (registry/proxy/proxymanifeststore.go) is itself an unguarded consumer and is directly exploitable.
Details
Root cause: registry/client/repository.go:488-505 reads the response body and passes it directly to distribution.UnmarshalManifest without ever computing digest.FromBytes(body) and comparing against the caller-supplied dgst. The Docker-Content-Digest response header is parsed (lines 251-292 in descriptorFromResponse) only to populate an out-parameter (ReturnContentDigest) and is itself server-supplied — it is never recomputed from the body, so a lying server can set it to whatever value the caller requested while delivering different bytes.
Attack path: An attacker who controls the registry endpoint (malicious registry, compromised upstream, DNS hijack, or TLS MITM) responds to GET /v2/{name}/manifests/{digest} with:
Docker-Content-Digest: <requested-digest>(forged header)- A body containing a different, attacker-chosen manifest
The client calls distribution.UnmarshalManifest on the substituted body, returns the attacker's manifest as if it were the pinned content, and the caller has no way to detect the substitution.
Why it matters: The entire value of digest pinning is to guarantee content-addressable integrity against exactly this threat model (hostile or MITM server). The registry/proxy/proxymanifeststore.go:42-87 pull-through cache calls remoteManifests.Get(ctx, dgst) and immediately writes the result to local storage with no re-verification — so a poisoned remote can permanently inject a bad manifest into the mirror cache, affecting all subsequent consumers.
Relevant code path (repository.go, simplified):
// line ~496
func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
...
resp, err := ms.client.Get(u)
...
body, err := ioutil.ReadAll(resp.Body) // no size limit, no digest check
...
m, _, err := distribution.UnmarshalManifest(ct, body) // returns attacker bytes
return m, err
}
PoC
go.mod:
module poc
go 1.24.10
require (
github.com/distribution/reference v0.6.0
github.com/docker/distribution v2.8.3+incompatible
github.com/opencontainers/go-digest v1.0.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/golang/protobuf v1.3.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/prometheus/client_golang v1.1.0 // indirect
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
github.com/prometheus/common v0.6.0 // indirect
github.com/prometheus/procfs v0.0.3 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
golang.org/x/sys v0.13.0 // indirect
)
main.go:
// PoC: registry/client manifests.Get() does not verify the requested digest
// against the returned body. A malicious registry (or MITM) can serve
// arbitrary content for a digest-pinned manifest reference and the client
// will accept it.…
- Record
GHSA-685w-q87j-wqw3- CVSS 3.1
- 6.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N - Weakness
- CWE-354
- Updated
- 2026-11-02
NVD
6.8
Distribution is a toolkit to pack, ship, store, and deliver container content. Prior to 3.1.2, registry/client.(*manifests).Get() reads a manifest returned for a digest-pinned request and passes it to distribution.UnmarshalManifest without hashing the body against the requested digest, allowing a malicious or intercepted registry response to substitute attacker-chosen manifest content even when Docker-Content-Digest claims the requested value. The bundled pull-through proxy in registry/proxy/proxymanifeststore.go can cache and re-serve the substituted manifest. This issue is fixed in version 3.1.2.
- Record
CVE-2026-85747- CVSS 3.1
- 6.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N - Weakness
- CWE-354
- Updated
- 2026-11-02
OSV
6.8
Distribution: Client `manifests.Get()` does not verify returned content against requested digest
Distribution is a toolkit to pack, ship, store, and deliver container content. Prior to 3.1.2, registry/client.(*manifests).Get() reads a manifest returned for a digest-pinned request and passes it to distribution.UnmarshalManifest without hashing the body against the requested digest, allowing a malicious or intercepted registry response to substitute attacker-chosen manifest content even when Docker-Content-Digest claims the requested value. The bundled pull-through proxy in registry/proxy/proxymanifeststore.go can cache and re-serve the substituted manifest. This issue is fixed in version 3.1.2.
- Record
CVE-2026-85747- CVSS 3.1
- 6.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N - Weakness
- CWE-354
- Updated
- 2026-11-06
OSVDEBIAN-CVE-2026-85747
6.8
Distribution is a toolkit to pack, ship, store, and deliver container content. Prior to 3.1.2, registry/client.(*manifests).Get() reads a manifest returned for a digest-pinned request and passes it to distribution.UnmarshalManifest without hashing the body against the requested digest, allowing a malicious or intercepted registry response to substitute attacker-chosen manifest content even when Docker-Content-Digest claims the requested value. The bundled pull-through proxy in registry/proxy/proxymanifeststore.go can cache and re-serve the substituted manifest. This issue is fixed in version 3.1.2.
- Record
DEBIAN-CVE-2026-85747- CVSS 3.1
- 6.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N - Updated
- 2026-11-02
Show 4 moreShow less
OSVGHSA-685w-q87j-wqw3
6.8
Distribution: Client `manifests.Get()` does not verify returned content against requested digest
Summary
registry/client.(*manifests).Get(ctx, dgst, …) fetches a manifest by digest but never hashes the returned body and compares it against the requested digest. A malicious or compromised registry, or any MITM on the registry connection, can serve arbitrary manifest bytes in response to a digest-pinned request and the client returns the substituted content transparently to the caller. Any application relying on digest pinning for supply-chain integrity — image policy controllers, mirrors, pull-through caches — is silently undermined. The bundled pull-through proxy (registry/proxy/proxymanifeststore.go) is itself an unguarded consumer and is directly exploitable.
Details
Root cause: registry/client/repository.go:488-505 reads the response body and passes it directly to distribution.UnmarshalManifest without ever computing digest.FromBytes(body) and comparing against the caller-supplied dgst. The Docker-Content-Digest response header is parsed (lines 251-292 in descriptorFromResponse) only to populate an out-parameter (ReturnContentDigest) and is itself server-supplied — it is never recomputed from the body, so a lying server can set it to whatever value the caller requested while delivering different bytes.
Attack path: An attacker who controls the registry endpoint (malicious registry, compromised upstream, DNS hijack, or TLS MITM) responds to GET /v2/{name}/manifests/{digest} with:
Docker-Content-Digest: <requested-digest>(forged header)- A body containing a different, attacker-chosen manifest
The client calls distribution.UnmarshalManifest on the substituted body, returns the attacker's manifest as if it were the pinned content, and the caller has no way to detect the substitution.
Why it matters: The entire value of digest pinning is to guarantee content-addressable integrity against exactly this threat model (hostile or MITM server). The registry/proxy/proxymanifeststore.go:42-87 pull-through cache calls remoteManifests.Get(ctx, dgst) and immediately writes the result to local storage with no re-verification — so a poisoned remote can permanently inject a bad manifest into the mirror cache, affecting all subsequent consumers.
Relevant code path (repository.go, simplified):
// line ~496
func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
...
resp, err := ms.client.Get(u)
...
body, err := ioutil.ReadAll(resp.Body) // no size limit, no digest check
...
m, _, err := distribution.UnmarshalManifest(ct, body) // returns attacker bytes
return m, err
}
PoC
go.mod:
module poc
go 1.24.10
require (
github.com/distribution/reference v0.6.0
github.com/docker/distribution v2.8.3+incompatible
github.com/opencontainers/go-digest v1.0.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/golang/protobuf v1.3.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/prometheus/client_golang v1.1.0 // indirect
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
github.com/prometheus/common v0.6.0 // indirect
github.com/prometheus/procfs v0.0.3 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
golang.org/x/sys v0.13.0 // indirect
)
main.go:
// PoC: registry/client manifests.Get() does not verify the requested digest
// against the returned body. A malicious registry (or MITM) can serve
// arbitrary content for a digest-pinned manifest reference and the client
// will accept it.…
- Record
GHSA-685w-q87j-wqw3- CVSS 3.1
- 6.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N - Weakness
- CWE-354
- Updated
- 2026-11-06
OSVUBUNTU-CVE-2026-85747
6.8
Distribution is a toolkit to pack, ship, store, and deliver container content. Prior to 3.1.2, registry/client.(*manifests).Get() reads a manifest returned for a digest-pinned request and passes it to distribution.UnmarshalManifest without hashing the body against the requested digest, allowing a malicious or intercepted registry response to substitute attacker-chosen manifest content even when Docker-Content-Digest claims the requested value. The bundled pull-through proxy in registry/proxy/proxymanifeststore.go can cache and re-serve the substituted manifest. This issue is fixed in version 3.1.2.
- Record
UBUNTU-CVE-2026-85747- CVSS 3.1
- 6.8 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N - Updated
- 2026-11-02
OSVBIT-distribution-2026-85747 No score
Distribution: Client `manifests.Get()` does not verify returned content against requested digest
Distribution is a toolkit to pack, ship, store, and deliver container content. Prior to 3.1.2, registry/client.(*manifests).Get() reads a manifest returned for a digest-pinned request and passes it to distribution.UnmarshalManifest without hashing the body against the requested digest, allowing a malicious or intercepted registry response to substitute attacker-chosen manifest content even when Docker-Content-Digest claims the requested value. The bundled pull-through proxy in registry/proxy/proxymanifeststore.go can cache and re-serve the substituted manifest. This issue is fixed in version 3.1.2.
- Record
BIT-distribution-2026-85747- Updated
- 2026-11-06
OSVGO-2026-6778 No score
Distribution: Client does not verify returned content against requested digest in github.com/distribution/distribution
Distribution: Client does not verify returned content against requested digest in github.com/distribution/distribution
- Record
GO-2026-6778- Updated
- 2026-11-06
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
github.com/docker/distributionGo |
through 2.8.3 | None known |
github.com/distribution/distribution/v3Go |
before 3.1.2 | 3.1.2 |
distributionBitnami |
before 3.1.2 | 3.1.2 |
docker-registryDebian:12 |
All versions, plus listed versions 2.8.2+ds1-12.8.3+ds1-12.8.3+ds1-22.8.3+ds1-3 |
None known |
docker-registryDebian:13 |
All versions, plus listed versions 2.8.3+ds1-22.8.3+ds1-3 |
None known |
docker-registryDebian:14 |
All versions, plus listed versions 2.8.3+ds1-22.8.3+ds1-3 |
None known |
github.com/distribution/distributionGo |
All versions | None known |
github.com/docker/distributionGo |
All versions | None known |
docker-registryUbuntu:Pro:16.04:LTS |
All versions, plus listed versions 2.1.1~ds1-42.1.1~ds1-4ubuntu12.3.0~ds1-12.3.0~ds1-1ubuntu0.1~esm1 |
None known |
docker-registryUbuntu:Pro:18.04:LTS |
All versions, plus listed versions 2.6.2~ds1-12.6.2~ds1-1ubuntu0.1~esm1 |
None known |
Show 5 moreShow less
| Package | Affected | Fixed |
|---|---|---|
docker-registryUbuntu:20.04:LTS |
All versions, plus listed versions 2.6.2~ds1-22.7.1~ds1-22.7.1~ds1-32.7.1~ds1-42.7.1+ds2-5 Show 4 moreShow less2.7.1+ds2-72.7.1+ds2-7ubuntu0.12.7.1+ds2-7ubuntu0.22.7.1+ds2-7ubuntu0.3 |
None known |
docker-registryUbuntu:Pro:22.04:LTS |
All versions, plus listed versions 2.7.1+ds2-7build22.8.0+ds1-42.8.0+ds1-4ubuntu0.1~esm12.8.0+ds1-4ubuntu0.1~esm22.8.0+ds1-4ubuntu0.1~esm3 |
None known |
docker-registryUbuntu:24.04:LTS |
All versions, plus listed versions 2.8.2+ds1-1build12.8.2+ds1-1ubuntu0.24.04.12.8.2+ds1-1ubuntu0.24.04.22.8.2+ds1-1ubuntu0.24.04.3 |
None known |
docker-registryUbuntu:26.10 |
All versions, plus listed versions 2.8.3+ds1-2build12.8.3+ds1-3 |
None known |
docker-registryUbuntu:Pro:26.04:LTS |
All versions, plus listed versions 2.8.3+ds1-22.8.3+ds1-2build12.8.3+ds1-2ubuntu0.26.04.1~esm1 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, aliases, facts, references | OSV | |
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-85747 is a medium vulnerability (CVSS 6.8) in Distribution, published October 13, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.22%, the 12th percentile. Fixed in github.com/distribution/distribution/v3 3.1.2.
References
github.com/distribution/distribution/security/advisories/GHSA-685w-q87j-wqw3
github.com/distribution/distribution/commit/960439b09f342cdeb8f1c0e18e8c7349bdcfbf80
github.com/distribution/distribution/releases/tag/v3.1.2
nvd.nist.gov/vuln/detail/CVE-2026-85747
github.com/distribution/distribution/commit/8f91c26c8888a95a36c2357a6e30bc15d4dd76d3
github.com/advisories/GHSA-685w-q87j-wqw3
Left out here: the rest of 2 source descriptions. The JSON record has them.