{"record":{"id":"eb83e712e0e4a7b4","repo":"jdx/mise","slug":"remote-cache-blob-pack-failed-digest-verification","errorCode":null,"errorMessage":"remote cache blob pack failed digest verification","messagePattern":"remote cache blob pack failed digest verification","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/mise-cache-core/src/lib.rs","lineNumber":960,"sourceCode":"\n        let path = directory.path().join(blobs.len().to_string());\n        let mut output = tokio::fs::File::create(&path).await?;\n        let mut remaining = digest.size;\n        let mut buffer = [0_u8; 64 * 1024];\n        while remaining > 0 {\n            let limit = usize::try_from(remaining.min(buffer.len() as u64)).unwrap();\n            let count = reader.read(&mut buffer[..limit]).await?;\n            if count == 0 {\n                bail!(\"remote cache blob pack ended before a blob was complete\");\n            }\n            output.write_all(&buffer[..count]).await?;\n            hasher.update(&buffer[..count]);\n            remaining -= count as u64;\n        }\n        output.flush().await?;\n        drop(output);\n        if !hasher.matches(&digest.hash) {\n            bail!(\"remote cache blob pack failed digest verification\");\n        }\n        blobs.push((digest, path));\n    }\n    let blob_count = blobs.len().try_into().unwrap_or(u64::MAX);\n    let metadata = metadata.validate(BlobPackResponseStats {\n        blob_count,\n        payload_bytes,\n        framed_bytes,\n    })?;\n    Ok(DownloadedBlobPack {\n        directory,\n        blobs,\n        metadata,\n    })\n}\n\nenum BlobPackHasher {\n    Blake3(Box<blake3::Hasher>),","sourceCodeStart":942,"sourceCodeEnd":978,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/crates/mise-cache-core/src/lib.rs#L942-L978","documentation":"After streaming each blob payload, mise-cache-core hashes the bytes with the algorithm named in the entry header (blake3 or sha-256) and compares the result against the header digest. A mismatch aborts the whole pack. This is end-to-end integrity verification: either the bytes were corrupted in transit, or the server stored/served wrong bytes under that digest.","triggerScenarios":"Downloading a blob pack over plain HTTP where an intermediary modified the body (the exact scenario `validate_remote_url` warns about); a server whose stored blob content is corrupt but still indexed under the original digest; header/payload misalignment from a framing error so payload bytes are hashed against the wrong header.","commonSituations":"Unencrypted HTTP remote cache on a shared network; server disk corruption silently serving bad bytes; middleboxes that rewrite or recompress response bodies.","solutions":["Switch the remote cache URL to HTTPS so intermediaries cannot modify bytes in transit.","On the server, re-verify stored blobs against their digests and evict mismatched objects so clients re-upload clean copies.","Retry once: in-flight corruption can be a one-off and the next pack re-downloads the affected blobs.","If it reproduces for one specific blob, delete that object server-side to force a fresh upload from a healthy client."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match remote_cache.download(staging_dir).await {\n    Ok(pack) => Ok(pack),\n    Err(err) if err.to_string().contains(\"failed digest verification\") => {\n        if url.scheme() != \"https\" { warn!(\"switch the cache to https\"); }\n        remote_cache.download(staging_dir).await // one retry; persistent mismatch = server blob corruption\n    }\n    other => other,\n}","preventionTips":["Always use HTTPS for the remote cache URL so bodies cannot be modified in transit.","Run periodic server-side CAS verification to evict corrupt blobs.","Treat a reproducible mismatch for one digest as a server-side eviction request, not a client bug."],"tags":["remote-cache","blob-pack","checksum-mismatch","integrity","rust"],"backgroundTag":"checksum-mismatch","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}