{"record":{"id":"744fd1520f4da4e7","repo":"jdx/mise","slug":"blob-digest-mismatch-got-actual-expected-dige","errorCode":null,"errorMessage":"blob digest mismatch: got {actual}, expected {digest}","messagePattern":"blob digest mismatch: got (.+?), expected (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/oci/layout.rs","lineNumber":71,"sourceCode":"\n    /// Copy a blob into the layout by its known digest.\n    ///\n    /// Two guards:\n    ///  1. The digest must be `sha256:` followed by 64 lowercase hex chars.\n    ///     This prevents path traversal (e.g. a malicious registry returning\n    ///     `sha256:../../etc/passwd` would otherwise let us write attacker-\n    ///     controlled bytes to an arbitrary filesystem path).\n    ///  2. We verify `sha256(bytes) == digest` before writing so corrupted\n    ///     or tampered content surfaces with a clear \"got X, wanted Y\"\n    ///     message instead of later as a confusing mismatch from skopeo /\n    ///     podman.\n    pub(crate) fn write_blob_with_digest(&self, digest: &str, bytes: &[u8]) -> Result<()> {\n        validate_sha256_digest(digest)?;\n        let mut h = Sha256::new();\n        h.update(bytes);\n        let actual = format!(\"sha256:{}\", crate::oci::layer::hex_encode(&h.finalize()));\n        if actual != digest {\n            eyre::bail!(\"blob digest mismatch: got {actual}, expected {digest}\");\n        }\n        let path = self.blob_path(digest);\n        if !path.exists() {\n            file::write(&path, bytes)?;\n        }\n        Ok(())\n    }\n\n    pub(crate) fn blob_path(&self, digest: &str) -> PathBuf {\n        let hex = digest.trim_start_matches(\"sha256:\");\n        self.root.join(\"blobs/sha256\").join(hex)\n    }\n\n    pub(crate) fn read_blob(&self, digest: &str) -> Result<Vec<u8>> {\n        // Validate before turning the digest into a path component — a crafted\n        // layout (`mise oci push/run --image-dir <untrusted>`) could otherwise\n        // use `sha256:../../etc/passwd` to read outside the blobs directory.\n        validate_sha256_digest(digest)?;","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/oci/layout.rs#L53-L89","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"use sha2::{Digest, Sha256};\nfn digest_of(bytes: &[u8]) -> String {\n    format!(\"sha256:{:x}\", Sha256::digest(bytes))\n}\n// assert digest_of(&bytes) == digest before calling write_blob_with_digest","typeGuard":"fn is_matching_digest(digest: &str, bytes: &[u8]) -> bool {\n    use sha2::{Digest, Sha256};\n    digest == format!(\"sha256:{:x}\", Sha256::digest(bytes))\n}","tryCatchPattern":"match layout.write_blob_with_digest(digest, bytes) {\n    Err(e) if e.to_string().contains(\"blob digest mismatch\") => {\n        eprintln!(\"registry served corrupt blob for {digest}; purge cache and retry\");\n    }\n    Err(e) => return Err(e),\n    Ok(()) => {}\n}","preventionTips":["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"],"tags":["oci","integrity","digest"],"backgroundTag":"checksum-mismatch","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}