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
- Print the hash value from the error and verify it is exactly 32 lowercase hex characters (regex ^[0-9a-f]{32}$)
- Recompute the correct MD5 with ComputeFileMD5 from the source content instead of reusing the stored/external value
- Trim whitespace and lowercase the input before calling blob APIs if your source may vary in case
- 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
- Run resources.IsValidBlobHash(hash) before any blob API call with untrusted input
- Normalize hashes to lowercase and trim whitespace at system boundaries
- Store only hashes produced by ComputeFileMD5; never accept hashes from external systems unchanged
- Add a unit test asserting blob APIs reject empty and malformed hashes
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
- path must not be empty
- input must not be empty
- path is required and cannot be empty
- diff is required and cannot be empty
- Internal
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/7e512df6b9966f73.
Report an issue: GitHub.