astrid-runtime/astrid · critical

durable capsule {id} WASM hash differs between authority and

Error message

durable capsule {id} WASM hash differs between authority and archive

What it means

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.

Source

Thrown at crates/astrid-capsule-install/src/storage.rs:309

    if authority.wasm_hash_pinned && metadata.wasm_hash != authority.approved_wasm_hash {
        bail!("durable capsule {id} metadata executable hash differs from authority receipt");
    }
    if let Some(component) = manifest.components.first() {
        let Some(relative) = component.path.to_str() else {
            bail!("durable capsule {id} component path is not UTF-8");
        };
        let Some(bytes) = archive_files.get(relative) else {
            bail!("durable capsule {id} component is missing from its archive");
        };
        if Path::new(relative)
            .extension()
            .is_some_and(|extension| extension.eq_ignore_ascii_case("wasm"))
        {
            let archive_hash = blake3::hash(bytes).to_hex().to_string();
            if authority.wasm_hash_pinned
                && authority.approved_wasm_hash.as_deref() != Some(archive_hash.as_str())
            {
                bail!("durable capsule {id} WASM hash differs between authority and archive");
            }
            if metadata.wasm_hash.as_deref() != Some(archive_hash.as_str()) {
                bail!("durable capsule {id} WASM hash differs between metadata and archive");
            }
        } else if metadata.wasm_hash.is_some() {
            bail!("durable capsule {id} metadata names a hash for a non-WASM component");
        }
    } else if metadata.wasm_hash.is_some() {
        bail!("durable capsule {id} metadata names a component absent from its archive");
    }
    let mut effective_capabilities = manifest.capabilities.clone();
    for component in &manifest.components {
        if let Some(capabilities) = &component.capabilities {
            effective_capabilities.merge_from(capabilities);
        }
    }
    if !effective_capabilities
        .expansions_from(&authority.approved_capabilities)

View on GitHub (pinned to affd8760f4)

Solutions

  1. Repack the archive with the exact approved component binary whose blake3 hash equals authority.approved_wasm_hash.
  2. Have the authority re-approve the archived build so approved_wasm_hash matches the actual archived bytes.
  3. Re-download the capsule from the trusted source — the local archive may be corrupted or tampered with.
  4. Verify the blake3 hash of the archived .wasm manually and compare against the receipt to confirm which binary was packaged.

Example fix

// before: repackaged with a rebuilt wasm
let bytes = std::fs::read("target/debug/component.wasm")?;
// after: package the approved artifact
let bytes = std::fs::read("approved/component.wasm")?;
debug_assert_eq!(blake3::hash(&bytes).to_string(), authority.approved_wasm_hash.unwrap());
Defensive patterns

Strategy: validation

Validate before calling

if authority.wasm_hash_pinned {
    let bytes = archive_files.get(component_path).ok_or_else(|| anyhow!("missing component"))?;
    let hash = blake3::hash(bytes).to_hex().to_string();
    anyhow::ensure!(authority.approved_wasm_hash.as_deref() == Some(hash.as_str()), "archived wasm hash {} not approved", hash);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/4744b8a511cbf0a3. Report an issue: GitHub.