goharbor/harbor · warning · lib/errors.Error

BAD_REQUEST

BAD_REQUEST

Error message

require digest

What it means

The blob controller's Get(ctx, digest) uses the digest string as the only lookup key (it builds an OrList on keywords["digest"]). An empty digest cannot identify any blob record, so the controller rejects the call with BAD_REQUEST before it reaches the DAO layer. This is a guard against callers forwarding a missing/zero-value digest.

Source

Thrown at src/controller/blob/controller.go:223

	associated := make(map[string]bool, len(associatedBlobs))
	for _, blob := range associatedBlobs {
		associated[blob.Digest] = true
	}

	var results []*blob.Blob
	for _, blob := range blobs {
		if !associated[blob.Digest] {
			results = append(results, blob)
		}
	}

	return results, nil
}

func (c *controller) Get(ctx context.Context, digest string, options ...Option) (*blob.Blob, error) {
	if digest == "" {
		return nil, errors.New(nil).WithCode(errors.BadRequestCode).WithMessage("require digest")
	}

	opts := newOptions(options...)

	keywords := make(map[string]any)
	if digest != "" {
		ol := q.OrList{
			Values: []any{
				digest,
			},
		}
		keywords["digest"] = &ol
	}
	if opts.ProjectID != 0 {
		keywords["projectID"] = opts.ProjectID
	}
	if opts.ArtifactDigest != "" {
		keywords["artifactDigest"] = opts.ArtifactDigest

View on GitHub (pinned to 7b2fd08cc5)

Solutions

  1. Pass the non-empty sha256 digest of the blob you want to fetch.
  2. Validate the digest at the boundary (route handler / API client) before calling the controller, ideally with the standard digest regexp.
  3. If the digest comes from another subsystem, debug why it arrives empty (nil manifest, truncated response) instead of retrying unchanged.

Example fix

// before
b, err := blobCtl.Get(ctx, digest) // digest == ""

// after
if digest == "" {
    return liberrors.BadRequestError(errors.New("require digest"))
}
b, err := blobCtl.Get(ctx, digest)
Defensive patterns

Strategy: validation

Validate before calling

var digestRe = regexp.MustCompile(`^sha256:[a-fA-F0-9]{64}$`)

func validDigest(d string) bool { return digestRe.MatchString(d) }

// call before blobCtl.Get:
if !validDigest(digest) {
    return liberrors.BadRequestError(fmt.Errorf("require digest"))
}

Type guard

func isDigestable(s string) bool {
    return len(s) > len("sha256:") && strings.HasPrefix(s, "sha256:")
}

Try / catch

if _, err := blobCtl.Get(ctx, digest); err != nil {
    if liberrors.IsErr(err, liberrors.BadRequestCode) && err.Error() == "require digest" {
        // caller bug: fix the digest source, do not retry
    }
    return err
}

Prevention

When it happens

Trigger: Calling controller/blob Get with digest == "" - e.g. an API handler that forwards a missing :digest path/query parameter, or internal code (replication, scanning, GC) that reads a digest field that was never populated.

Common situations: Client calls a blob endpoint without the digest segment; upstream struct has an empty Digest because the manifest was fetched partially; code assumes an earlier layer already validated the digest.

Related errors


AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16). Data as JSON: /api/errors/6b2423d525bb55dd. Report an issue: GitHub.