{"record":{"id":"16663c50a4654d13","repo":"docker/compose","slug":"blob-digest-mismatch-expected-s-got-s","errorCode":null,"errorMessage":"blob digest mismatch: expected %s, got %s","messagePattern":"blob digest mismatch: expected (.+?), got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/oci/resolver.go","lineNumber":128,"sourceCode":"\t\treturn nil, fmt.Errorf(\"fetching blob %s: %w\", descriptor.Digest, err)\n\t}\n\tdefer func() { _ = fetch.Close() }()\n\t// bound the read by the declared size so a rogue registry can't cause\n\t// unbounded allocation; the extra byte detects oversized responses.\n\tcontent, err := io.ReadAll(io.LimitReader(fetch, descriptor.Size+1))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading blob %s: %w\", descriptor.Digest, err)\n\t}\n\tif int64(len(content)) != descriptor.Size {\n\t\treturn nil, fmt.Errorf(\"blob %s size mismatch: expected %d bytes, got %d\", descriptor.Digest, descriptor.Size, len(content))\n\t}\n\t// GetBlob bypasses containerd's content store, so integrity must be\n\t// checked here before callers write the bytes to disk.\n\tif err := descriptor.Digest.Validate(); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid digest %s: %w\", descriptor.Digest, err)\n\t}\n\tif actual := descriptor.Digest.Algorithm().FromBytes(content); actual != descriptor.Digest {\n\t\treturn nil, fmt.Errorf(\"blob digest mismatch: expected %s, got %s\", descriptor.Digest, actual)\n\t}\n\treturn content, nil\n}\n\nfunc Copy(ctx context.Context, resolver remotes.Resolver, image reference.Named, named reference.Named) (spec.Descriptor, error) {\n\tsrc, desc, err := resolver.Resolve(ctx, image.String())\n\tif err != nil {\n\t\treturn spec.Descriptor{}, err\n\t}\n\tif desc.Annotations == nil {\n\t\tdesc.Annotations = make(map[string]string)\n\t}\n\t// set LabelDistributionSource so push will actually use a registry mount\n\trefspec := reference.TrimNamed(image).String()\n\tu, err := url.Parse(\"dummy://\" + refspec)\n\tif err != nil {\n\t\treturn spec.Descriptor{}, err\n\t}","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/docker/compose/blob/ddc4b044b62e9f715212ea4143fa830fac76382f/internal/oci/resolver.go#L110-L146","documentation":"The strict integrity check: GetBlob recomputes the digest of the downloaded bytes with the descriptor's algorithm and compares it to descriptor.Digest. A mismatch means the content served does not match what the manifest promises — corrupt storage, a compromised/rogue registry, a MITM tampering the stream, or a descriptor built against different bytes. The bytes are rejected and never written to disk by callers.","triggerScenarios":"descriptor.Digest.Algorithm().FromBytes(content) != descriptor.Digest after a successful, correctly-sized download: registry bit rot, proxy tampering, manifest referencing a blob rewritten by GC dedup, or a malicious registry.","commonSituations":"Self-hosted registry with disk corruption; transparent TLS-inspecting proxies rewriting bodies; artifacts built by tooling that computed digests before final byte changes; CDN caching mismatched bodies under the same URL.","solutions":["Re-pull from a trusted network path without TLS-intercepting proxies.","Re-push the artifact from a known-good source so manifest and blobs agree.","Run registry storage checks (fsck/repair) if self-hosting.","Treat repeated mismatches from one registry as a security incident: verify the artifact from another mirror."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// verify before trusting anything the registry served\nactual := descriptor.Digest.Algorithm().FromBytes(content)\nif actual != descriptor.Digest {\n    return fmt.Errorf(\"content integrity failure for %s: got %s\", descriptor.Digest, actual)\n}","typeGuard":"func blobMatchesDigest(content []byte, expected digest.Digest) bool {\n    return expected.Algorithm().FromBytes(content) == expected\n}","tryCatchPattern":"if actual := descriptor.Digest.Algorithm().FromBytes(content); actual != descriptor.Digest {\n    return nil, fmt.Errorf(\"blob digest mismatch: expected %s, got %s\", descriptor.Digest, actual)\n    // discard bytes; do not retry from same source; treat as tampering/corruption\n}","preventionTips":["Never skip content verification for blobs fetched outside containerd's content store.","Re-push from a trusted source on mismatch; investigate the registry if it repeats.","Keep TLS-intercepting proxies out of the registry path."],"tags":["oci","integrity","security","registry","digest"],"backgroundTag":null,"analyzedSha":"ddc4b044b62e9f715212ea4143fa830fac76382f","analyzedAt":"2026-08-15T13:31:42.319Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}