jdx/mise · error
blob digest mismatch: got {actual}, expected {digest}
Error message
blob digest mismatch: got {actual}, expected {digest} What it means
`write_blob_with_digest` in src/oci/layout.rs verifies that the blob bytes it is about to write hash (SHA-256) to the digest the caller claims. If the computed digest differs from `digest`, it refuses to write, protecting the OCI layout from corrupt or substituted content pulled from a registry.
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 afd2eddd3a)
Solutions
- Re-pull the blob from the registry (delete any partially cached content first) and retry
- Verify the digest you pass matches the blob you downloaded (recompute sha256 over the exact bytes received)
- Check the registry/CDN for corruption; try a different mirror or proxy
- Fix caller bookkeeping so each digest is paired with its own bytes
Example fix
// before layout.write_blob_with_digest(layer_digest, &manifest_bytes)?; // wrong bytes for this digest // after let blob_bytes = registry.fetch_blob(layer_digest).await?; layout.write_blob_with_digest(&layer_digest, &blob_bytes)?;
Defensive patterns
Strategy: validation
Validate before calling
fn sha256_hex(bytes: &[u8]) -> String {
let mut h = Sha256::new(); h.update(bytes);
format!("sha256:{}", hex::encode(h.finalize()))
}
assert_eq!(sha256_hex(&bytes), digest, "refusing to write mismatched blob"); Try / catch
match layout.write_blob_with_digest(&digest, &bytes) {
Ok(()) => /* ... */,
Err(e) if e.to_string().contains("digest mismatch") => {
// discard cached bytes and re-pull from registry
}
Err(e) => return Err(e),
} Prevention
- Recompute the digest over received bytes before writing
- Never reuse a digest variable across different blobs
- Re-download blobs on mismatch instead of retrying writes
When it happens
Trigger: Calling `write_blob_with_digest(digest, bytes)` where `bytes` do not hash to `digest` — e.g. a registry returned a different blob than advertised, the download was truncated/corrupted, or the caller mixed up blobs (wrote layer bytes under the manifest's digest).
Common situations: `pull_base_image` downloading layers over a flaky/middleboxed network; a compromised or misbehaving registry returning wrong content; CDN caching bugs; caller-side bookkeeping bugs passing the wrong digest for the bytes.
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
- brew-cask:{}: cask metadata has no sha256
- packslip:{project}@{version}: manifest digest differs from s
- the packslip at {} is not the one the signed release list po
- base image {ref_} has {base_layers.len} layers in its manife
- invalid blob digest (expected sha256: prefix): {digest}
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/744fd1520f4da4e7.
Report an issue: GitHub.