{"record":{"id":"928926baaa200370","repo":"jdx/mise","slug":"bytes-do-not-match-the-declared-cas-digest","errorCode":null,"errorMessage":"bytes do not match the declared CAS digest","messagePattern":"bytes do not match the declared CAS digest","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/mise-cache-core/src/local.rs","lineNumber":60,"sourceCode":"    /// Find and verify a stored object.\n    pub fn find(&self, digest: &CacheDigest) -> Result<Option<PathBuf>> {\n        let path = self.path_for(digest)?;\n        if !path.exists() {\n            return Ok(None);\n        }\n        if !digest.matches_file(&path)? {\n            bail!(\n                \"local CAS blob failed digest verification: {}\",\n                path.display()\n            );\n        }\n        Ok(Some(path))\n    }\n\n    /// Atomically store bytes after verifying their declared digest.\n    pub fn store_bytes(&self, digest: &CacheDigest, bytes: &[u8]) -> Result<PathBuf> {\n        if !digest.matches_bytes(bytes)? {\n            bail!(\"bytes do not match the declared CAS digest\");\n        }\n        self.store_with(digest, |temporary| {\n            temporary.write_all(bytes)?;\n            Ok(())\n        })\n    }\n\n    /// Atomically store a file after verifying its declared digest.\n    pub fn store_file(&self, digest: &CacheDigest, source: &Path) -> Result<PathBuf> {\n        self.store_file_inner(digest, source, true)\n    }\n\n    /// Store a file whose digest was already verified by this crate.\n    pub(crate) fn store_verified_file(\n        &self,\n        digest: &CacheDigest,\n        source: &Path,\n    ) -> Result<PathBuf> {","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/crates/mise-cache-core/src/local.rs#L42-L78","documentation":"`LocalCas::store_bytes` verifies the in-memory bytes against the declared digest (both size and hash, blake3 or sha-256) before writing anything to disk. A mismatch means the digest was computed over different bytes than the ones passed, and the store refuses to publish content that would corrupt the content-addressed layout.","triggerScenarios":"Hashing one buffer but storing another: computing `CacheDigest::blake3(&old_bytes)` then passing `new_bytes` to `store_bytes`; a digest deserialized from JSON or a remote response whose `size` or `hash` does not describe the bytes at hand; mutating the buffer between hashing and storing.","commonSituations":"TOCTOU between digest computation and store in producer code; copy-paste mixing digest/bytes pairs; sizes expressed in non-byte units when constructing CacheDigest manually.","solutions":["Compute the digest from the exact same slice immediately before storing: `let digest = CacheDigest::blake3(&bytes); cas.store_bytes(&digest, &bytes)`.","When the digest comes from a manifest or remote peer, call `digest.matches_bytes(&bytes)` first and handle a mismatch explicitly instead of storing.","Check that `digest.size` is the byte length of the content, not a human-readable size."],"exampleFix":"// before\nlet digest = CacheDigest::blake3(&original);\ncas.store_bytes(&digest, &updated)?; // bails\n\n// after\nlet digest = CacheDigest::blake3(&updated);\ncas.store_bytes(&digest, &updated)?;","handlingStrategy":"validation","validationCode":"if !digest.matches_bytes(&bytes)? {\n    // digest describes different content: recompute it from these bytes\n    let digest = CacheDigest::blake3(&bytes);\n    return cas.store_bytes(&digest, bytes);\n}\ncas.store_bytes(&digest, &bytes)?;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always derive the digest and the bytes from the same buffer in the same expression scope.","Check `digest.matches_bytes` before storing when digests arrive from external sources.","Never construct CacheDigest.size by hand; use the computed byte length."],"tags":["local-cache","cas","checksum-mismatch","digest","rust"],"backgroundTag":"checksum-mismatch","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}