jdx/mise · error · eyre::Report
invalid blob digest (expected 64 lowercase hex chars): {dige
Error message
invalid blob digest (expected 64 lowercase hex chars): {digest} What it means
The second half of validate_sha256_digest: after stripping 'sha256:', the remainder must be exactly 64 characters of lowercase hex ([0-9a-f]). Wrong length, uppercase hex, or non-hex characters (including '../' traversal payloads) all fail here. The strict charset is what makes the digest safe to use as a single filesystem path component.
Source
Thrown at src/oci/layout.rs:147
let bytes = serde_json::to_vec(manifest)?;
self.write_blob(&bytes)
}
}
/// Validate that `digest` is a well-formed `sha256:<64 lowercase hex>` string.
/// Guards against path traversal from a malicious registry returning something
/// like `sha256:../../etc/passwd` as a layer digest — without this check, that
/// would be used directly as a filesystem path component.
pub(crate) fn validate_sha256_digest(digest: &str) -> Result<()> {
let Some(hex) = digest.strip_prefix("sha256:") else {
eyre::bail!("invalid blob digest (expected sha256: prefix): {digest}");
};
if hex.len() != 64
|| !hex
.chars()
.all(|c| c.is_ascii_digit() || ('a'..='f').contains(&c))
{
eyre::bail!("invalid blob digest (expected 64 lowercase hex chars): {digest}");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_path_traversal() {
assert!(validate_sha256_digest("sha256:../../etc/passwd").is_err());
assert!(validate_sha256_digest("sha256:../foo").is_err());
assert!(validate_sha256_digest("../bad").is_err());
assert!(validate_sha256_digest("sha256:DEADBEEF").is_err());
}
#[test]
fn accepts_valid_digest() {View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Use the digest verbatim from the OCI descriptor produced by a conformant registry/tool.
- If generating digests yourself, lowercase the hex and ensure 64 chars: format!("sha256:{:x}", Sha256::digest(bytes)).
- Treat any digest failing this check in untrusted input as tampering and reject the whole manifest rather than retrying.
Example fix
// before
let d = format!("sha256:{:X}", hash); // uppercase hex
// after
let d = format!("sha256:{:x}", hash); // 64 lowercase hex chars Defensive patterns
Strategy: type-guard
Type guard
fn is_sha256_digest(s: &str) -> bool {
let Some(hex) = s.strip_prefix("sha256:") else { return false };
hex.len() == 64 && hex.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))
} // rejects 'sha256:../foo', uppercase, short/long hex Prevention
- When computing digests, use lowercase hex formatting ({:x}, not {:X}).
- Treat traversal-shaped digests from remote registries as tampering; abort the pull, do not sanitize and continue.
- Cover the guard with the same unit cases mise uses (../../etc/passwd, ../foo).
When it happens
Trigger: Digests like 'sha256:ABCDEF...' (uppercase), 'sha256:abc' (short), 'sha256:../../etc/passwd' or 'sha256:../foo' (traversal — covered by the unit test rejects_path_traversal), or digests with whitespace/newlines from sloppy string handling.
Common situations: A malicious or broken registry returning crafted digest strings; uppercase digests copied from docs or generated by tools that uppercase hex; truncated digests from manual copying.
Related errors
- invalid blob digest (expected sha256: prefix): {digest}
- [dotfiles]."{}": target is not a safe OCI path
- unsupported remote cache digest algorithm
- invalid remote cache digest
- oci mount_point must not be empty
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/f5a611ae96208be0.
Report an issue: GitHub.