{"record":{"id":"0e7ce506a666621e","repo":"astrid-runtime/astrid","slug":"manifest-hash-mismatch-lock-records-recorded-a","errorCode":null,"errorMessage":"manifest hash mismatch: lock records {recorded}, archive Distro.toml hashes to {actual} — the shuttle is inconsistent or tampered","messagePattern":"manifest hash mismatch: lock records (.+?), archive Distro\\.toml hashes to (.+?) — the shuttle is inconsistent or tampered","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/astrid-cli/src/commands/distro/shuttle_install.rs","lineNumber":278,"sourceCode":"/// to that signature. So when the shuttle is `signed`, a `manifest_hash`\n/// is mandatory and must match — a `None` is a hard fail (an attacker\n/// could otherwise keep a signed lock+sig+pubkey and swap only the\n/// manifest, leaving env/selection unauthenticated). For an unsigned\n/// install there is no signed lock to bind against, so a `None` is\n/// tolerated and a present hash is still checked best-effort.\n///\n/// Pure (no I/O) so the binding gate is unit-testable.\nfn check_manifest_binding(\n    distro_id: &str,\n    signed: bool,\n    lock: &DistroLock,\n    manifest_bytes: &[u8],\n) -> anyhow::Result<()> {\n    let actual = manifest_hash(manifest_bytes);\n    match &lock.manifest_hash {\n        Some(recorded) => {\n            if recorded != &actual {\n                bail!(\n                    \"manifest hash mismatch: lock records {recorded}, archive Distro.toml hashes \\\n                     to {actual} — the shuttle is inconsistent or tampered\"\n                );\n            }\n            Ok(())\n        },\n        None if signed => bail!(\n            \"signed shuttle for '{distro_id}' is missing its manifest_hash binding — refusing. \\\n             The signature covers the lock, not Distro.toml; without manifest_hash the manifest \\\n             (env/selection) is unauthenticated and could be swapped.\"\n        ),\n        None => Ok(()),\n    }\n}\n\n/// Verify the per-capsule blake3 of every lock entry against the bytes\n/// actually present in the mirror. Returns an error on the first\n/// mismatch or missing file. Pure (no install side effects) so the","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/shuttle_install.rs#L260-L296","documentation":"check_manifest_binding recomputes the hash of the archive's Distro.toml and compares it to the manifest_hash recorded in the sealed lock. A mismatch means the lock and the manifest inside the shuttle disagree — either the shuttle was assembled inconsistently or the manifest was swapped/tampered after sealing. The install aborts to guarantee the authenticated lock actually binds to this manifest.","triggerScenarios":"install_from_shuttle on a shuttle whose Distro.toml was edited after the lock was sealed; a mismatched lock/manifest pair copied from two different distro builds; corruption of either file on the shuttle media.","commonSituations":"Manually tweaking env/selection in Distro.toml on a prepared shuttle; mixing files from two build attempts onto one shuttle; transport corruption that survives file copy.","solutions":["Rebuild the shuttle from a clean build so lock.manifest_hash matches the archive's Distro.toml","Do not edit Distro.toml after the shuttle is sealed; change the manifest and rebuild","Re-copy the shuttle if media corruption is suspected and retry"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let actual = manifest_hash(manifest_bytes);\nif lock.manifest_hash.as_deref() != Some(actual.as_str()) {\n    return Err(anyhow!(\"manifest hash mismatch: lock vs Distro.toml\"));\n}","typeGuard":null,"tryCatchPattern":"match install_from_shuttle(...) {\n    Err(e) if e.to_string().contains(\"manifest hash mismatch\") => {\n        // quarantine the shuttle, notify the operator; never retry in place\n    }\n    ...\n}","preventionTips":["Never edit Distro.toml after sealing; rebuild the shuttle instead","Keep lock and manifest from the same build output directory","Re-verify hashes after copying shuttle media"],"tags":["integrity","tamper-detection","hash-mismatch","offline-install"],"backgroundTag":"checksum-mismatch","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}