jdx/mise · error
blob digest mismatch: got
Error message
blob digest mismatch: got {actual}, expected {digest} What it means
mise writes a blob into its local OCI layout only after re-computing its SHA-256 and confirming it matches the digest string supplied by the caller. If the supplied `digest` does not equal `sha256:<hash of bytes>`, the blob would be stored under a wrong path or be corrupted on read, so mise aborts with this error. It is a content-addressing integrity check, not a download failure itself.
Solutions
- Delete the corrupted blob from the local OCI layout cache (mise's oci blobs dir) and re-run the pull so the blob is re-fetched
- Check for a proxy/mirror intercepting registry traffic and disable it or trust the registry directly
- Re-run `mise oci pull` / the build to rule out a transient network truncation
- If writing blobs programmatically, verify you pass the digest computed from the same `bytes` you pass in
Defensive patterns
Strategy: validation
Validate before calling
use sha2::{Digest, Sha256};
fn digest_of(bytes: &[u8]) -> String {
format!("sha256:{:x}", Sha256::digest(bytes))
}
// assert digest_of(&bytes) == digest before calling write_blob_with_digest Type guard
fn is_matching_digest(digest: &str, bytes: &[u8]) -> bool {
use sha2::{Digest, Sha256};
digest == format!("sha256:{:x}", Sha256::digest(bytes))
} Try / catch
match layout.write_blob_with_digest(digest, bytes) {
Err(e) if e.to_string().contains("blob digest mismatch") => {
eprintln!("registry served corrupt blob for {digest}; purge cache and retry");
}
Err(e) => return Err(e),
Ok(()) => {}
} Prevention
- Always compute digests from the exact bytes you hand to the writer
- Re-pull blobs instead of trusting a long-lived cache after network errors
- Monitor registries/proxies for content mutation
- Log manifest digest and computed digest together for debugging
When it happens
Trigger: Calling `write_blob_with_digest(digest, bytes)` (directly or via `pull_base_image`/`push_image`) with a digest string that does not match the SHA-256 of `bytes` — typically because the registry/manifest returned a digest that doesn't match the blob body it served, or the blob was truncated/mutated in transit.
Common situations: A malicious or misbehaving registry serving a body that doesn't match the manifest digest; a truncated download behind a proxy; manually edited cache/blobs directory; manifest digest reused for different content when building or pushing images.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- base image has layers in its manifest but diff_ids in its…
- base image has layers in its manifest but diff_ids in its…
- cached OCI layer diff ID mismatch
- cached OCI layer digest mismatch
- cached OCI layer size mismatch
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/744fd1520f4da4e7.
Report an issue: GitHub.
Appendix: source
Thrown at src/oci/layout.rs:71
/// Copy a blob into the layout by its known digest.
///
/// Two guards:
/// 1. The digest must be `sha256:` followed by 64 lowercase hex chars.
/// This prevents path traversal (e.g. a malicious registry returning
/// `sha256:../../etc/passwd` would otherwise let us write attacker-
/// controlled bytes to an arbitrary filesystem path).
/// 2. We verify `sha256(bytes) == digest` before writing so corrupted
/// or tampered content surfaces with a clear "got X, wanted Y"
/// message instead of later as a confusing mismatch from skopeo /
/// podman.
pub(crate) fn write_blob_with_digest(&self, digest: &str, bytes: &[u8]) -> Result<()> {
validate_sha256_digest(digest)?;
let mut h = Sha256::new();
h.update(bytes);
let actual = format!("sha256:{}", crate::oci::layer::hex_encode(&h.finalize()));
if actual != digest {
eyre::bail!("blob digest mismatch: got {actual}, expected {digest}");
}
let path = self.blob_path(digest);
if !path.exists() {
file::write(&path, bytes)?;
}
Ok(())
}
pub(crate) fn blob_path(&self, digest: &str) -> PathBuf {
let hex = digest.trim_start_matches("sha256:");
self.root.join("blobs/sha256").join(hex)
}
pub(crate) fn read_blob(&self, digest: &str) -> Result<Vec<u8>> {
// Validate before turning the digest into a path component — a crafted
// layout (`mise oci push/run --image-dir <untrusted>`) could otherwise
// use `sha256:../../etc/passwd` to read outside the blobs directory.
validate_sha256_digest(digest)?;View on GitHub (pinned to 533346cc37)