{"record":{"id":"30e00d5b124603df","repo":"docker/compose","slug":"blob-s-size-mismatch-expected-d-bytes-got-d","errorCode":null,"errorMessage":"blob %s size mismatch: expected %d bytes, got %d","messagePattern":"blob (.+?) size mismatch: expected (.+?) bytes, got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/oci/resolver.go","lineNumber":120,"sourceCode":"// blob content must be fetched directly from the blobs endpoint.\nfunc GetBlob(ctx context.Context, resolver remotes.Resolver, ref reference.Named, descriptor spec.Descriptor) ([]byte, error) {\n\tfetcher, err := resolver.Fetcher(ctx, ref.String())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"creating fetcher for %s: %w\", ref, err)\n\t}\n\tfetch, err := fetcher.Fetch(ctx, descriptor)\n\tif err != nil {\n\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 {","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/docker/compose/blob/ddc4b044b62e9f715212ea4143fa830fac76382f/internal/oci/resolver.go#L102-L138","documentation":"GetBlob deliberately reads descriptor.Size+1 bytes so it can detect both truncation and inflation: if the number of bytes actually read differs from the descriptor's declared size, the registry served a body that does not match its own manifest, and the blob is rejected before any integrity hashing. This guards against rogue or buggy registries causing wrong-size data (and, with the +1 cap, unbounded allocation).","triggerScenarios":"Registry returns fewer bytes than descriptor.Size (truncated blob, streaming bug) or more (pad/injection, wrong blob served, descriptor from a different manifest). Triggered by the comparison int64(len(content)) != descriptor.Size.","commonSituations":"Registry garbage collection raced with the pull; a caching proxy served a partial body; hand-crafted manifests with wrong layer sizes; registries with buggy range-request handling.","solutions":["Re-pull the artifact; if a proxy/CDN is in play, purge or bypass its cache.","Re-push the artifact from the source so manifest sizes match actual blobs.","Verify manually: compare Content-Length of GET /v2/<repo>/blobs/<digest> to descriptor.Size.","Report persistent size skew to the registry operator — the manifest and blob store disagree."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// trust, but verify: cross-check descriptor sizes against the registry\n// HEAD /v2/<repo>/blobs/<digest> -> Content-Length must equal descriptor.Size\nif contentLength != descriptor.Size {\n    return fmt.Errorf(\"registry size %d != manifest size %d; registry state inconsistent\", contentLength, descriptor.Size)\n}","typeGuard":null,"tryCatchPattern":"if int64(len(content)) != descriptor.Size {\n    return nil, fmt.Errorf(\"blob %s size mismatch: expected %d bytes, got %d\", descriptor.Digest, descriptor.Size, len(content))\n    // never use or persist mismatched bytes; re-pull or re-push\n}","preventionTips":["Never bypass the size check when copying this pattern — it bounds allocation.","Re-push artifacts after registry GC to keep manifests and blobs consistent.","Purge or bypass caching proxies that serve partial bodies."],"tags":["oci","registry","blob","integrity","validation"],"backgroundTag":null,"analyzedSha":"ddc4b044b62e9f715212ea4143fa830fac76382f","analyzedAt":"2026-08-15T13:31:42.319Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}