vxcontrol/pentagi · error

invalid blob hash %q

Error message

invalid blob hash %q

What it means

validateBlobHash (resources.go:96) rejects any hash that is not a valid hex-encoded MD5 digest, as checked by IsValidBlobHash. BlobExists, DeleteBlob, and CommitBlob all call it first, so blob APIs never touch disk with a malformed hash. The error includes the offending value with %q so empty strings, non-hex characters, and wrong lengths are immediately visible.

Source

Thrown at backend/pkg/resources/resources.go:96

}

// IsValidBlobHash reports whether hash is a hex-encoded MD5 digest.
func IsValidBlobHash(hash string) bool {
	if len(hash) != md5.Size*2 {
		return false
	}
	for _, r := range hash {
		if (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F') {
			continue
		}
		return false
	}
	return true
}

func validateBlobHash(hash string) error {
	if !IsValidBlobHash(hash) {
		return fmt.Errorf("invalid blob hash %q", hash)
	}
	return nil
}

// BlobExists returns true if the .blob file for hash already exists on disk.
func BlobExists(dataDir, hash string) (bool, error) {
	if err := validateBlobHash(hash); err != nil {
		return false, err
	}
	_, err := os.Lstat(BlobPath(dataDir, hash))
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Print the hash value from the error and verify it is exactly 32 lowercase hex characters (regex ^[0-9a-f]{32}$)
  2. Recompute the correct MD5 with ComputeFileMD5 from the source content instead of reusing the stored/external value
  3. Trim whitespace and lowercase the input before calling blob APIs if your source may vary in case
  4. If you migrate hash algorithms, re-hash and update stored references rather than passing new-format hashes to these APIs

Example fix

// before
exists, err := resources.BlobExists(dataDir, userHash) // may be arbitrary input
// after
var md5Re = regexp.MustCompile(`^[0-9a-f]{32}$`)
if !md5Re.MatchString(strings.TrimSpace(userHash)) { return fmt.Errorf("bad hash %q", userHash) }
exists, err := resources.BlobExists(dataDir, strings.TrimSpace(userHash))
Defensive patterns

Strategy: validation

Validate before calling

var md5HexRe = regexp.MustCompile(`^[0-9a-f]{32}$`)
func validBlobHash(h string) bool { return md5HexRe.MatchString(h) }

Type guard

func isBlobHash(s string) bool {
    if len(s) != 32 { return false }
    for _, c := range s {
        if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) { return false }
    }
    return true
}

Try / catch

if !isBlobHash(hash) { return fmt.Errorf("skip invalid hash %q", hash) }
if err := resources.DeleteBlob(dataDir, hash); err != nil {
    if strings.Contains(err.Error(), "invalid blob hash") { return errInvalidInput }
    return err
}

Prevention

When it happens

Trigger: Passing an empty string, a SHA-256/sha1 hash instead of a 32-char MD5 hex, an uppercase or '0x'-prefixed hash if IsValidBlobHash rejects it, or a hash containing path separators (which would otherwise enable path traversal) to BlobExists, DeleteBlob, or CommitBlob.

Common situations: Migrating from another storage scheme that used different hash algorithms; trusting user- or API-supplied hash parameters without validating; stale DB records containing truncated or legacy hashes; copy/paste errors including whitespace or quotes.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/7e512df6b9966f73. Report an issue: GitHub.