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

  1. 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
  2. Check for a proxy/mirror intercepting registry traffic and disable it or trust the registry directly
  3. Re-run `mise oci pull` / the build to rule out a transient network truncation
  4. 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

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


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)