{"record":{"id":"6da1f00ad2b87383","repo":"henrygd/beszel","slug":"unsupported-hash-length-d-expected-40-for-sha1","errorCode":null,"errorMessage":"unsupported hash length: %d (expected 40 for SHA1 or 64 for SHA256)","messagePattern":"unsupported hash length: (.+?) \\(expected 40 for SHA1 or 64 for SHA256\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/tools/fetchsmartctl/main.go","lineNumber":86,"sourceCode":"\ttmp := dest + \".tmp\"\n\tf, err := os.OpenFile(tmp, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"open tmp: %w\", err)\n\t}\n\n\t// Determine hash algorithm based on length (SHA1=40, SHA256=64)\n\tvar hasher hash.Hash\n\tif shaHex := strings.TrimSpace(shaHex); shaHex != \"\" {\n\t\tcleanSha := strings.ToLower(strings.ReplaceAll(shaHex, \" \", \"\"))\n\t\tswitch len(cleanSha) {\n\t\tcase 40:\n\t\t\thasher = sha1.New()\n\t\tcase 64:\n\t\t\thasher = sha256.New()\n\t\tdefault:\n\t\t\tf.Close()\n\t\t\tos.Remove(tmp)\n\t\t\treturn fmt.Errorf(\"unsupported hash length: %d (expected 40 for SHA1 or 64 for SHA256)\", len(cleanSha))\n\t\t}\n\t}\n\n\tvar mw io.Writer = f\n\tif hasher != nil {\n\t\tmw = io.MultiWriter(f, hasher)\n\t}\n\tif _, err := io.Copy(mw, resp.Body); err != nil {\n\t\tf.Close()\n\t\tos.Remove(tmp)\n\t\treturn fmt.Errorf(\"write tmp: %w\", err)\n\t}\n\tif err := f.Close(); err != nil {\n\t\tos.Remove(tmp)\n\t\treturn fmt.Errorf(\"close tmp: %w\", err)\n\t}\n\n\tif hasher != nil && shaHex != \"\" {","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/henrygd/beszel/blob/b38fb7dafa60812cc22e6a84ce313e94f1ce0a32/agent/tools/fetchsmartctl/main.go#L68-L104","documentation":"Thrown by downloadFile when the expected checksum string length is neither 40 (SHA1) nor 64 (SHA256) hex characters. The tool selects the hash algorithm purely by length, so any other length is rejected as a configuration error and the temp file is cleaned up.","triggerScenarios":"Passing a shaHex argument that is empty is fine (skips verification), but a non-empty checksum of wrong length — e.g. truncated SHA256, SHA512 (128 chars), MD5 (32 chars), or containing stray non-hex text pasted from a checksums page.","commonSituations":"Copy-pasting the wrong column from a checksums file; using a SHA512 checksum; shell variable truncation; including a filename in the pasted checksum line.","solutions":["Count the checksum characters: must be exactly 40 (SHA1) or 64 (SHA256) hex digits","Re-copy the checksum, excluding any filename or whitespace artifacts","If only SHA512 is published, compute a SHA256 locally instead of passing it","Strip prefixes like 'sha256=' before passing"],"exampleFix":"// before\ncleanSha := strings.ToLower(strings.ReplaceAll(shaHex, \" \", \"\"))\n// after\ncleanSha := strings.ToLower(strings.TrimSpace(shaHex))\nif i := strings.IndexByte(cleanSha, ' '); i >= 0 {\n\tcleanSha = cleanSha[:i] // drop trailing filename from 'checksum  file' paste\n}","handlingStrategy":"validation","validationCode":"sha := strings.TrimSpace(shaInput)\nif sha != \"\" && len(sha) != 40 && len(sha) != 64 {\n\treturn fmt.Errorf(\"checksum must be 40 (SHA1) or 64 (SHA256) hex chars, got %d\", len(sha))\n}\nfor _, r := range sha {\n\tif !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) {\n\t\treturn fmt.Errorf(\"checksum contains non-hex character %q\", r)\n\t}\n}","typeGuard":"func isHexLen(s string, n int) bool {\n\tif len(s) != n {\n\t\treturn false\n\t}\n\t_, err := hex.DecodeString(s)\n\treturn err == nil\n}","tryCatchPattern":"if err := downloadFile(url, dest, sha); err != nil {\n\tif strings.Contains(err.Error(), \"unsupported hash length\") {\n\t\tfmt.Printf(\"bad checksum %q (len %d): use SHA1(40) or SHA256(64)\\n\", sha, len(strings.TrimSpace(sha)))\n\t}\n\treturn err\n}","preventionTips":["Copy only the hex field from checksum files, never the trailing filename","Verify length is 40 or 64 before passing a checksum","Never paste SHA512/MD5 digests into a SHA1/SHA256 tool","Trim whitespace and prefixes (sha256=) from pasted checksums"],"tags":["checksum","validation","build-tool"],"backgroundTag":"invalid-checksum-format","analyzedSha":"b38fb7dafa60812cc22e6a84ce313e94f1ce0a32","analyzedAt":"2026-08-31T15:10:10.149Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}