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
- Only if the new key is verified as trusted, re-run with --accept-new-key to update the pin
- Inspect and update the pinned key file shown in the error path as part of a deliberate key rotation
- 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
- Treat any pin mismatch as a potential supply-chain attack until proven otherwise
- Document and automate signing-key rotations with pin updates
- Verify new keys out-of-band before using --accept-new-key
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
- capsule '{}' is {}; explicit local approval is required
- capsule provenance changed after authority decision
- durable capsule {id} provenance differs from authority recei
- durable capsule {id} authority claims provenance absent from
- signed shuttle for '{distro_id}' is missing its manifest_has
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/a1c8dbe2115cf2a3.
Report an issue: GitHub.