{"record":{"id":"9d15002069cde8e4","repo":"herdrdev/herdr","slug":"expected-sha256-must-be-64-hexadecimal-characters","errorCode":null,"errorMessage":"expected sha256 must be 64 hexadecimal characters","messagePattern":"expected sha256 must be 64 hexadecimal characters","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"src/checksum.rs","lineNumber":12,"sourceCode":"use std::{\n    fs::File,\n    io::{self, Read},\n    path::Path,\n};\n\nuse sha2::{Digest, Sha256};\n\npub(crate) fn verify_sha256(path: &Path, expected: &str) -> io::Result<()> {\n    let expected = expected.trim().to_ascii_lowercase();\n    if expected.len() != 64 || !expected.chars().all(|ch| ch.is_ascii_hexdigit()) {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            \"expected sha256 must be 64 hexadecimal characters\",\n        ));\n    }\n\n    let actual = file_sha256(path)?;\n    if actual != expected {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"sha256 mismatch: expected {expected}, got {actual}\"),\n        ));\n    }\n    Ok(())\n}\n\nfn file_sha256(path: &Path) -> io::Result<String> {\n    let mut file = File::open(path)?;\n    let mut hasher = Sha256::new();","sourceCodeStart":1,"sourceCodeEnd":30,"githubUrl":"https://github.com/herdrdev/herdr/blob/f457cff4f2648eee85d176f8a41861241d4e8428/src/checksum.rs#L1-L30","documentation":"verify_sha256 checks an artifact against an expected digest. Before hashing the file it trims and lowercases the expected string and requires exactly 64 ASCII hex characters — the canonical SHA-256 digest form. Anything else (wrong length, non-hex characters, empty string) fails fast with ErrorKind::InvalidData and 'expected sha256 must be 64 hexadecimal characters'. This is a caller-input bug, not a file problem; the file is never read when this fires.","triggerScenarios":"Calling verify_sha256 with a malformed expected digest: truncated or pasted-with-newline-adjacent-text string, a sha1 (40 chars) or sha512 (128 chars) digest, a string with 'sha256:' prefix, 0x prefix, or embedded whitespace that trim() doesn't remove (inner spaces/tabs).","commonSituations":"Copy/paste mistakes from release notes; checksum files listing multiple hash formats (md5/sha1/sha512) and picking the wrong column; config or lockfiles with a stale/mis-edited hash; pipelines that pass the filename or URL instead of the digest.","solutions":["Re-copy the expected digest from the source manifest and confirm it is exactly 64 hex chars: echo -n \"$h\" | wc -c and grep -E '^[0-9a-fA-F]{64}$'.","Strip prefixes like 'sha256:' or '0x' before passing the value in.","If your checksum file has multiple columns, make sure you extract the SHA-256 field, not md5/sha1/sha512.","Add a unit test asserting your configured digests match ^[0-9a-f]{64}$ so bad values fail at config time."],"exampleFix":"// before\nverify_sha256(&path, \"sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\")?;\n\n// after\nverify_sha256(&path, \"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\")?;","handlingStrategy":"validation","validationCode":"fn is_sha256_hex(s: &str) -> bool {\n    let t = s.trim().to_ascii_lowercase();\n    t.len() == 64 && t.chars().all(|c| c.is_ascii_hexdigit())\n}\nassert!(is_sha256_hex(expected), \"bad digest: {expected:?}\");\nverify_sha256(&path, expected)?;","typeGuard":"fn is_sha256_hex(s: &str) -> bool {\n    let t = s.trim().to_ascii_lowercase();\n    t.len() == 64 && t.chars().all(|c| c.is_ascii_hexdigit())\n}","tryCatchPattern":"match verify_sha256(&path, expected) {\n    Err(e) if e.kind() == io::ErrorKind::InvalidData => {\n        // the EXPECTED digest string is malformed — fix the config/manifest value, not the file\n    }\n    other => other,\n}","preventionTips":["Validate digests against ^[0-9a-f]{64}$ at config load time.","Strip 'sha256:'/'0x' prefixes and any surrounding whitespace before passing.","When parsing checksum files, select the SHA-256 column explicitly."],"tags":["checksum","sha256","validation","input-validation","rust"],"backgroundTag":"checksum-format-invalid","analyzedSha":"f457cff4f2648eee85d176f8a41861241d4e8428","analyzedAt":"2026-08-28T15:41:09.197Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}