{"record":{"id":"93761d52904dd1e6","repo":"henrygd/beszel","slug":"invalid-sha-256-release-digest-q","errorCode":null,"errorMessage":"invalid SHA-256 release digest %q","messagePattern":"invalid SHA-256 release digest %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/ghupdate/checksum.go","lineNumber":24,"sourceCode":"\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc verifyAssetChecksum(path, digest string) error {\n\talgorithm, expectedHex, ok := strings.Cut(digest, \":\")\n\tif !ok || algorithm == \"\" || expectedHex == \"\" {\n\t\treturn fmt.Errorf(\"invalid release digest %q\", digest)\n\t}\n\tif !strings.EqualFold(algorithm, \"sha256\") {\n\t\treturn fmt.Errorf(\"unsupported release digest algorithm %q\", algorithm)\n\t}\n\n\texpected, err := hex.DecodeString(expectedHex)\n\tif err != nil || len(expected) != sha256.Size {\n\t\treturn fmt.Errorf(\"invalid SHA-256 release digest %q\", digest)\n\t}\n\n\tfile, err := os.Open(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open release for checksum verification: %w\", err)\n\t}\n\tdefer file.Close()\n\n\thash := sha256.New()\n\tif _, err := io.Copy(hash, file); err != nil {\n\t\treturn fmt.Errorf(\"failed to calculate release checksum: %w\", err)\n\t}\n\tactual := hash.Sum(nil)\n\tif !bytes.Equal(actual, expected) {\n\t\treturn fmt.Errorf(\"release checksum mismatch: expected %s, got %s\", expectedHex, hex.EncodeToString(actual))\n\t}\n\n\treturn nil","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/henrygd/beszel/blob/b38fb7dafa60812cc22e6a84ce313e94f1ce0a32/internal/ghupdate/checksum.go#L6-L42","documentation":"The hex portion after \"sha256:\" must decode cleanly and be exactly 32 bytes (sha256.Size, i.e. 64 hex characters). If hex.DecodeString fails (odd length or non-hex characters) or the decoded length differs, the digest is malformed for SHA-256 and verification aborts before touching the file.","triggerScenarios":"ghupdate.Update -> update -> verifyAssetChecksum with a digest whose hex part is truncated, contains non-hex characters (e.g. \"0x\" prefixes, whitespace, uppercase OK but stray chars not), or is 56/128 chars because the publisher hashed with a different length output.","commonSituations":"Hand-edited checksum files; copy-paste that dropped characters; scripts that prefix hashes with \"0x\"; digests copied from sha1 (40 chars) or sha512 (128 chars) outputs.","solutions":["Regenerate the digest with `sha256sum <asset>` and publish exactly the 64 hex characters after the \"sha256:\" prefix.","Strip whitespace, quotes, and any \"0x\" prefix from the digest string before publishing it in release metadata.","If the release JSON comes from a mirror, verify it passes through the original GitHub digest field unmodified."],"exampleFix":"// before — truncated/odd-length hex\ndigest := \"sha256:3f2a9c\"          // 3 bytes, fails length check\n// after\ndigest := \"sha256:3f2a9c...e4\"     // full 64 hex chars from sha256sum","handlingStrategy":"validation","validationCode":"hexPart := strings.SplitN(asset.Digest, \":\", 2)[1]\nif len(hexPart) != 64 {\n    return fmt.Errorf(\"digest hex must be 64 chars, got %d\", len(hexPart))\n}\nif _, err := hex.DecodeString(hexPart); err != nil {\n    return fmt.Errorf(\"digest is not valid hex: %v\", err)\n}","typeGuard":null,"tryCatchPattern":"updated, err := ghupdate.Update(cfg)\nif err != nil && strings.Contains(err.Error(), \"invalid SHA-256 release digest\") {\n    log.Printf(\"publisher's digest is malformed (%v); regenerate with sha256sum and republish\", err)\n}","preventionTips":["Generate digests mechanically with `sha256sum <asset>` — never by hand.","Trim whitespace and quotes before embedding digests in release metadata.","Add CI validation that digests match ^sha256:[0-9a-f]{64}$."],"tags":["checksum","sha256","hex","digest-format"],"backgroundTag":"invalid-checksum-digest-format","analyzedSha":"b38fb7dafa60812cc22e6a84ce313e94f1ce0a32","analyzedAt":"2026-08-31T15:10:10.149Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}