jdx/mise · error · eyre::Report
invalid blob digest (expected sha256: prefix): {digest}
Error message
invalid blob digest (expected sha256: prefix): {digest} What it means
validate_sha256_digest is the gate before any digest is used as a filesystem path component (blobs/sha256/<hex>). It requires the literal 'sha256:' prefix; anything else — bare hex, 'sha512:...', digest objects stringified wrongly — is rejected here. This simultaneously blocks path traversal like 'sha256:../../etc/passwd'.
Source
Thrown at src/oci/layout.rs:140
};
let path = self.root.join("index.json");
file::write(&path, serde_json::to_vec_pretty(&index)?)?;
Ok(())
}
pub fn write_manifest(&self, manifest: &ImageManifest) -> Result<(String, u64)> {
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());View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Normalize digests to 'sha256:<64 lowercase hex>' before passing them in (prepend the prefix when the algorithm is sha256).
- Use digests exactly as they appear in the source manifest/config descriptors — do not reformat.
- Reject non-sha256 content upstream; this code path only supports sha256.
Example fix
// before
layout.write_blob_with_digest(hex_string, &bytes)?; // missing prefix
// after
layout.write_blob_with_digest(&format!("sha256:{hex_string}"), &bytes)?; 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'))
} Prevention
- Pass digests through verbatim from source descriptors; never reconstruct them by string surgery.
- Reject non-sha256 algorithms at your boundary; this writer supports only sha256.
- Add the one-line type guard wherever untrusted manifest data enters your pipeline.
When it happens
Trigger: A registry/manifest response supplying a bare 64-hex digest without the algorithm prefix, or a non-sha256 algorithm digest (sha512) reaching write_blob_with_digest; feeding a Descriptor's raw string that lost its prefix during serialization.
Common situations: Switching a manifest source to a registry that emits different digest formats; hand-written tooling that strips or reformats digests; consuming OCI artifacts not produced by mise.
Related errors
- invalid blob digest (expected 64 lowercase hex chars): {dige
- [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/ddcbdac4cddc3b62.
Report an issue: GitHub.