{"record":{"id":"2954c624f5b737c7","repo":"docker/compose","slug":"invalid-digest-s-w","errorCode":null,"errorMessage":"invalid digest %s: %w","messagePattern":"invalid digest (.+?): %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/oci/resolver.go","lineNumber":125,"sourceCode":"\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 {\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)","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/docker/compose/blob/ddc4b044b62e9f715212ea4143fa830fac76382f/internal/oci/resolver.go#L107-L143","documentation":"Before hashing the downloaded bytes, GetBlob calls descriptor.Digest.Validate() to check that the digest string itself is well-formed (algorithm:hex with correct length/charset). A malformed digest — missing algorithm, wrong hex length for the algorithm, invalid characters, empty string — fails here. This is a descriptor-quality check distinct from content verification (error 97).","triggerScenarios":"Passing a spec.Descriptor whose Digest string is like \"sha256:xyz\", \"abc123\" (no algorithm), \"sha512:\" (empty hex), or hex of the wrong length for the declared algorithm.","commonSituations":"Descriptors assembled by hand or parsed from custom YAML/JSON instead of from a registry manifest; truncated digest strings from config files; copy/paste typos in annotations or lockfiles.","solutions":["Use full, canonical digests: sha256:<64 lowercase hex chars>.","Generate descriptors from real manifests (resolver.Resolve) rather than constructing them manually.","Validate digests at ingestion time when reading them from user-supplied files.","Check for truncation (e.g. 12-char short IDs are not valid full digests)."],"exampleFix":"// before\ndesc := spec.Descriptor{Digest: \"sha256:deadbeef\", Size: 123} // invalid hex length\n\n// after\ndg, _ := digest.Parse(\"sha256:<full-64-hex>\")\ndesc := spec.Descriptor{Digest: dg, Size: 123}","handlingStrategy":"validation","validationCode":"if err := descriptor.Digest.Validate(); err != nil {\n    return fmt.Errorf(\"descriptor carries malformed digest %q: %w\", descriptor.Digest, err)\n}","typeGuard":"func isValidDigest(s string) bool {\n    d, err := digest.Parse(s)\n    return err == nil && d.Validate() == nil\n}","tryCatchPattern":"if err := descriptor.Digest.Validate(); err != nil {\n    return nil, fmt.Errorf(\"invalid digest %s: %w\", descriptor.Digest, err)\n    // reject the descriptor; do not attempt a fetch with it\n}","preventionTips":["Always digest.Parse/Validate strings coming from config or user input.","Never hand-assemble digests; derive them from real content or manifests.","Reject short IDs (12-hex) where full digests are required."],"tags":["oci","digest","validation","integrity","go"],"backgroundTag":null,"analyzedSha":"ddc4b044b62e9f715212ea4143fa830fac76382f","analyzedAt":"2026-08-15T13:31:42.319Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}