{"record":{"id":"4744b8a511cbf0a3","repo":"astrid-runtime/astrid","slug":"durable-capsule-id-wasm-hash-differs-between-aut","errorCode":null,"errorMessage":"durable capsule {id} WASM hash differs between authority and archive","messagePattern":"durable capsule (.+?) WASM hash differs between authority and archive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/astrid-capsule-install/src/storage.rs","lineNumber":309,"sourceCode":"    if authority.wasm_hash_pinned && metadata.wasm_hash != authority.approved_wasm_hash {\n        bail!(\"durable capsule {id} metadata executable hash differs from authority receipt\");\n    }\n    if let Some(component) = manifest.components.first() {\n        let Some(relative) = component.path.to_str() else {\n            bail!(\"durable capsule {id} component path is not UTF-8\");\n        };\n        let Some(bytes) = archive_files.get(relative) else {\n            bail!(\"durable capsule {id} component is missing from its archive\");\n        };\n        if Path::new(relative)\n            .extension()\n            .is_some_and(|extension| extension.eq_ignore_ascii_case(\"wasm\"))\n        {\n            let archive_hash = blake3::hash(bytes).to_hex().to_string();\n            if authority.wasm_hash_pinned\n                && authority.approved_wasm_hash.as_deref() != Some(archive_hash.as_str())\n            {\n                bail!(\"durable capsule {id} WASM hash differs between authority and archive\");\n            }\n            if metadata.wasm_hash.as_deref() != Some(archive_hash.as_str()) {\n                bail!(\"durable capsule {id} WASM hash differs between metadata and archive\");\n            }\n        } else if metadata.wasm_hash.is_some() {\n            bail!(\"durable capsule {id} metadata names a hash for a non-WASM component\");\n        }\n    } else if metadata.wasm_hash.is_some() {\n        bail!(\"durable capsule {id} metadata names a component absent from its archive\");\n    }\n    let mut effective_capabilities = manifest.capabilities.clone();\n    for component in &manifest.components {\n        if let Some(capabilities) = &component.capabilities {\n            effective_capabilities.merge_from(capabilities);\n        }\n    }\n    if !effective_capabilities\n        .expansions_from(&authority.approved_capabilities)","sourceCodeStart":291,"sourceCodeEnd":327,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/storage.rs#L291-L327","documentation":"After locating the component in the archive and confirming it has a .wasm extension, verify_package_identity hashes the archived bytes with blake3 and compares the hash to the authority's approved_wasm_hash when a hash is pinned. This bail fires when the authority pins a WASM hash and the archived component's hash differs from it — the packaged binary is not the one the authority approved. It prevents installing an archive whose contents were substituted or rebuilt after approval.","triggerScenarios":"Calling read_verified_durable_package_for_owner on a capsule where authority.wasm_hash_pinned is true and blake3(archive component bytes) != authority.approved_wasm_hash — the archive's .wasm file differs from the approved binary even though metadata may agree with the receipt.","commonSituations":"Recompiling the WASM component (different toolchain/flags) and repacking without re-approval; a tampered or corrupted archive in transit or storage; accidentally packaging a debug build while the receipt pins the release build; pulling the capsule from an untrusted mirror.","solutions":["Repack the archive with the exact approved component binary whose blake3 hash equals authority.approved_wasm_hash.","Have the authority re-approve the archived build so approved_wasm_hash matches the actual archived bytes.","Re-download the capsule from the trusted source — the local archive may be corrupted or tampered with.","Verify the blake3 hash of the archived .wasm manually and compare against the receipt to confirm which binary was packaged."],"exampleFix":"// before: repackaged with a rebuilt wasm\nlet bytes = std::fs::read(\"target/debug/component.wasm\")?;\n// after: package the approved artifact\nlet bytes = std::fs::read(\"approved/component.wasm\")?;\ndebug_assert_eq!(blake3::hash(&bytes).to_string(), authority.approved_wasm_hash.unwrap());","handlingStrategy":"validation","validationCode":"if authority.wasm_hash_pinned {\n    let bytes = archive_files.get(component_path).ok_or_else(|| anyhow!(\"missing component\"))?;\n    let hash = blake3::hash(bytes).to_hex().to_string();\n    anyhow::ensure!(authority.approved_wasm_hash.as_deref() == Some(hash.as_str()), \"archived wasm hash {} not approved\", hash);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only package artifacts whose blake3 hash matches the approved receipt.","Download capsules over verified channels and verify hashes before install.","Re-run authority approval after any toolchain or flag change that rebuilds the wasm."],"tags":["integrity-verification","blake3","wasm-hash","tamper-detection","rust"],"backgroundTag":"checksum-mismatch","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}