herdrdev/herdr · error · io::Error
expected sha256 must be 64 hexadecimal characters
Error message
expected sha256 must be 64 hexadecimal characters
What it means
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.
Source
Thrown at src/checksum.rs:12
use std::{
fs::File,
io::{self, Read},
path::Path,
};
use sha2::{Digest, Sha256};
pub(crate) fn verify_sha256(path: &Path, expected: &str) -> io::Result<()> {
let expected = expected.trim().to_ascii_lowercase();
if expected.len() != 64 || !expected.chars().all(|ch| ch.is_ascii_hexdigit()) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"expected sha256 must be 64 hexadecimal characters",
));
}
let actual = file_sha256(path)?;
if actual != expected {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("sha256 mismatch: expected {expected}, got {actual}"),
));
}
Ok(())
}
fn file_sha256(path: &Path) -> io::Result<String> {
let mut file = File::open(path)?;
let mut hasher = Sha256::new();View on GitHub (pinned to f457cff4f2)
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.
Example fix
// before verify_sha256(&path, "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08")?; // after verify_sha256(&path, "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08")?;
Defensive patterns
Strategy: validation
Validate before calling
fn is_sha256_hex(s: &str) -> bool {
let t = s.trim().to_ascii_lowercase();
t.len() == 64 && t.chars().all(|c| c.is_ascii_hexdigit())
}
assert!(is_sha256_hex(expected), "bad digest: {expected:?}");
verify_sha256(&path, expected)?; Type guard
fn is_sha256_hex(s: &str) -> bool {
let t = s.trim().to_ascii_lowercase();
t.len() == 64 && t.chars().all(|c| c.is_ascii_hexdigit())
} Try / catch
match verify_sha256(&path, expected) {
Err(e) if e.kind() == io::ErrorKind::InvalidData => {
// the EXPECTED digest string is malformed — fix the config/manifest value, not the file
}
other => other,
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- downloaded remote asset checksum verification failed: {err}
- sha256 mismatch: expected {expected}, got {actual}
- {flag} must be an integer between 1 and {}
- {flag} must be greater than 0
- {REMOTE_BINARY_ENV_VAR} path is not a file: {}
AI-assisted analysis of herdrdev/herdr@f457cff4f2 (2026-08-28).
Data as JSON: /api/errors/9d15002069cde8e4.
Report an issue: GitHub.