astrid-runtime/astrid · critical

signed shuttle for '{distro_id}' is missing its manifest_has

Error message

signed shuttle for '{distro_id}' is missing its manifest_hash binding — refusing. The signature covers the lock, not Distro.toml; without manifest_hash the manifest (env/selection) is unauthenticated and could be swapped.

What it means

For a signed shuttle, check_manifest_binding requires lock.manifest_hash to be present. The signature covers the lock, not Distro.toml itself, so without the recorded manifest_hash the manifest (environment and capsule selection) is unauthenticated and could be silently swapped. Signed artifacts missing this binding are hard-refused rather than installed.

Source

Thrown at crates/astrid-cli/src/commands/distro/shuttle_install.rs:285

/// Pure (no I/O) so the binding gate is unit-testable.
fn check_manifest_binding(
    distro_id: &str,
    signed: bool,
    lock: &DistroLock,
    manifest_bytes: &[u8],
) -> anyhow::Result<()> {
    let actual = manifest_hash(manifest_bytes);
    match &lock.manifest_hash {
        Some(recorded) => {
            if recorded != &actual {
                bail!(
                    "manifest hash mismatch: lock records {recorded}, archive Distro.toml hashes \
                     to {actual} — the shuttle is inconsistent or tampered"
                );
            }
            Ok(())
        },
        None if signed => bail!(
            "signed shuttle for '{distro_id}' is missing its manifest_hash binding — refusing. \
             The signature covers the lock, not Distro.toml; without manifest_hash the manifest \
             (env/selection) is unauthenticated and could be swapped."
        ),
        None => Ok(()),
    }
}

/// Verify the per-capsule blake3 of every lock entry against the bytes
/// actually present in the mirror. Returns an error on the first
/// mismatch or missing file. Pure (no install side effects) so the
/// integrity gate is unit-testable.
fn verify_capsule_hashes(mirror: &Path, lock: &DistroLock) -> anyhow::Result<()> {
    for entry in &lock.capsules {
        let file = shuttle::capsule_mirror_path(mirror, &entry.name);
        if !file.is_file() {
            bail!(
                "capsule '{}' is missing from the shuttle mirror",

View on GitHub (pinned to affd8760f4)

Solutions

  1. Rebuild the shuttle with the current packing tool so the signed lock includes manifest_hash
  2. Regenerate signatures with an up-to-date sealing workflow rather than hand-editing the lock
  3. If the shuttle must remain unsigned, distribute it unsigned — do not sign a lock without the hash binding

Example fix

// before: signed lock without manifest_hash
// after: repack with the current astrid version
// astrid distro pack --sign <key> --output /media/shuttle
Defensive patterns

Strategy: validation

Validate before calling

if lock.signature.is_some() && lock.manifest_hash.is_none() {
    return Err(anyhow!("signed lock missing manifest_hash binding"));
}

Prevention

When it happens

Trigger: install_from_shuttle with a signed lock that lacks a manifest_hash field — typically produced by an older packing tool version, a hand-crafted lock, or a lock stripped during editing.

Common situations: Old shuttles packed before manifest_hash binding was introduced; manually edited or stripped lock files; mixing a new signed lock format with an old tool or vice versa.

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/6c320ea175d954a5. Report an issue: GitHub.