astrid-runtime/astrid · critical

distro '{distro_id}' is pinned to {} but this artifact is si

Error message

distro '{distro_id}' is pinned to {} but this artifact is signed by {} — refusing. Re-run with --accept-new-key only if you trust the new key.

What it means

verify_and_pin enforces the distro's signing-key pin (trust-on-first-use pin stored under the astrid home). If a validly signed artifact arrives under a different key than the pinned one, it is refused unless --accept-new-key is given. This blocks key-substitution attacks where an attacker re-signs a malicious lock with their own key.

Source

Thrown at crates/astrid-cli/src/commands/distro/trust.rs:156

    lock: &DistroLock,
    accept_new_key: bool,
    policy: TrustPolicy,
) -> anyhow::Result<TrustOutcome> {
    let pubkey = sign::parse_pubkey(manifest_pubkey)?;
    let key_str = sign::pubkey_to_wire(&pubkey);

    // The signature MUST verify under the manifest's declared key first —
    // a bad signature is fatal regardless of trust state. (Cases 1–5.)
    sign::verify_lock(lock, sig_hex, &pubkey)
        .context("distro signature is invalid — refusing to install")?;

    let pinned = read_pinned(home, distro_id)?;
    let action = match pinned {
        Some(pin_key) if pin_key == pubkey => TrustAction::PinnedMatch,
        Some(pin_key) => {
            // Valid signature, but under a key that differs from the pin.
            if !accept_new_key {
                bail!(
                    "distro '{distro_id}' is pinned to {} but this artifact is signed by {} — \
                     refusing. Re-run with --accept-new-key only if you trust the new key.",
                    sign::pubkey_to_wire(&pin_key),
                    key_str,
                );
            }
            write_pin(home, distro_id, &key_str)?;
            TrustAction::NewKeyAccepted
        },
        None if policy == TrustPolicy::RequireExistingPin => {
            bail!(
                "distro '{distro_id}' has no signing-key pin at {} — install the operator-verified \
                 key before use; this path does not create a first-use pin",
                trust_path(home, distro_id).display()
            );
        },
        None => {
            write_pin(home, distro_id, &key_str)?;

View on GitHub (pinned to affd8760f4)

Solutions

  1. Only if the new key is verified as trusted, re-run with --accept-new-key to update the pin
  2. Inspect and update the pinned key file shown in the error path as part of a deliberate key rotation
  3. If no rotation was expected, treat this as a possible supply-chain attack and refuse the artifact

Example fix

// before
// astrid distro install distro-id --from-shuttle /media/shuttle  # refuses: wrong key
// after (only after verifying the new key out-of-band)
// astrid distro install distro-id --from-shuttle /media/shuttle --accept-new-key
Defensive patterns

Strategy: try-catch

Validate before calling

let pinned = read_pinned(home, distro_id)?;
if let Some(pin) = pinned {
    if pin != artifact_pubkey && !accept_new_key {
        return Err(anyhow!("signing key differs from pin for {distro_id}"));
    }
}

Try / catch

match trust::verify_and_pin(...) {
    Err(e) if e.to_string().contains("is pinned to") => {
        // confirm key rotation out-of-band, then retry with --accept-new-key
    }
    ...
}

Prevention

When it happens

Trigger: installing a distro artifact signed by a new/different key while the operator pin for that distro_id records the old key, without passing --accept-new-key; rotation of signing keys without updating the pin; a substituted lock re-signed by an attacker.

Common situations: Team rotates signing keys and operators forget the pin update; CI signs with a different key than the original operator; attacker substitutes a re-signed lock (the exact scenario this defends against).

Related errors


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