astrid-runtime/astrid · critical

durable capsule {id} provenance differs from authority recei

Error message

durable capsule {id} provenance differs from authority receipt

What it means

The archive carries a signature (ArtifactVerification::Signed) whose signer or signature does not match the signer/signature recorded in the InstalledAuthority receipt. The library throws this because durable capsules must be provably signed by the same identity the authority receipt approved; a different signer or signature means the artifact provenance was replaced or the receipt is stale.

Source

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

    for component in &manifest.components {
        if let Some(capabilities) = &component.capabilities {
            effective_capabilities.merge_from(capabilities);
        }
    }
    if !effective_capabilities
        .expansions_from(&authority.approved_capabilities)
        .is_empty()
    {
        bail!("durable capsule {id} manifest exceeds its authority receipt");
    }
    match verification {
        ArtifactVerification::Signed(provenance) => {
            let signer = provenance.signer.to_string();
            let signature = provenance.signature.to_string();
            if authority.signer.as_deref() != Some(signer.as_str())
                || authority.signature.as_deref() != Some(signature.as_str())
            {
                bail!("durable capsule {id} provenance differs from authority receipt");
            }
        },
        ArtifactVerification::Unsigned { .. } => {
            if authority.signer.is_some() || authority.signature.is_some() {
                bail!("durable capsule {id} authority claims provenance absent from archive");
            }
        },
    }
    Ok(())
}

struct ArchiveInventory {
    files: std::collections::BTreeMap<String, Vec<u8>>,
    directories: std::collections::BTreeSet<String>,
}

fn read_archive_files(archive_bytes: &[u8]) -> anyhow::Result<ArchiveInventory> {
    let decoder = flate2::read::GzDecoder::new(Cursor::new(archive_bytes));

View on GitHub (pinned to affd8760f4)

Solutions

  1. Re-issue the authority receipt with the current signer and signature values.
  2. Re-sign the archive with the key matching authority.signer so provenance matches the receipt.
  3. Restore the original signed artifact if the wrong one was installed.
  4. Update the key-rotation workflow so receipts are refreshed whenever signing keys change.

Example fix

// before: receipt holds old signer
authority.signer = Some("old-publisher".into());
// after: refresh receipt after re-signing
authority.signer = Some(provenance.signer.to_string());
authority.signature = Some(provenance.signature.to_string());
Defensive patterns

Strategy: validation

Validate before calling

if let ArtifactVerification::Signed(p) = verification {
    if authority.signer.as_deref() != Some(p.signer.to_string().as_str())
        || authority.signature.as_deref() != Some(p.signature.to_string().as_str()) {
        return Err("provenance does not match authority receipt");
    }
}

Type guard

fn provenance_matches(p: &Provenance, authority: &InstalledAuthority) -> bool {
    authority.signer.as_deref() == Some(p.signer.to_string().as_str())
        && authority.signature.as_deref() == Some(p.signature.to_string().as_str())
}

Try / catch

match read_verified_durable_package_for_owner(&store, owner, id).await {
    Ok(pkg) => pkg,
    Err(e) if e.to_string().contains("provenance differs from authority receipt") => {
        // re-sign with approved key or refresh the receipt
    },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: read_verified_durable_package_for_owner where provenance.signer.to_string() != authority.signer or provenance.signature.to_string() != authority.signature for a Signed artifact.

Common situations: Re-signing an artifact with a new key without updating the authority receipt; importing a capsule signed by a different publisher; rotating signing keys; receipt pinned to an old signature of the same package.

Related errors


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