{"record":{"id":"a1c8dbe2115cf2a3","repo":"astrid-runtime/astrid","slug":"distro-distro-id-is-pinned-to-but-this-arti","errorCode":null,"errorMessage":"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.","messagePattern":"distro '(.+?)' is pinned to (.+?) but this artifact is signed by (.+?) — refusing\\. Re-run with --accept-new-key only if you trust the new key\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/astrid-cli/src/commands/distro/trust.rs","lineNumber":156,"sourceCode":"    lock: &DistroLock,\n    accept_new_key: bool,\n    policy: TrustPolicy,\n) -> anyhow::Result<TrustOutcome> {\n    let pubkey = sign::parse_pubkey(manifest_pubkey)?;\n    let key_str = sign::pubkey_to_wire(&pubkey);\n\n    // The signature MUST verify under the manifest's declared key first —\n    // a bad signature is fatal regardless of trust state. (Cases 1–5.)\n    sign::verify_lock(lock, sig_hex, &pubkey)\n        .context(\"distro signature is invalid — refusing to install\")?;\n\n    let pinned = read_pinned(home, distro_id)?;\n    let action = match pinned {\n        Some(pin_key) if pin_key == pubkey => TrustAction::PinnedMatch,\n        Some(pin_key) => {\n            // Valid signature, but under a key that differs from the pin.\n            if !accept_new_key {\n                bail!(\n                    \"distro '{distro_id}' is pinned to {} but this artifact is signed by {} — \\\n                     refusing. Re-run with --accept-new-key only if you trust the new key.\",\n                    sign::pubkey_to_wire(&pin_key),\n                    key_str,\n                );\n            }\n            write_pin(home, distro_id, &key_str)?;\n            TrustAction::NewKeyAccepted\n        },\n        None if policy == TrustPolicy::RequireExistingPin => {\n            bail!(\n                \"distro '{distro_id}' has no signing-key pin at {} — install the operator-verified \\\n                 key before use; this path does not create a first-use pin\",\n                trust_path(home, distro_id).display()\n            );\n        },\n        None => {\n            write_pin(home, distro_id, &key_str)?;","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/trust.rs#L138-L174","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"// before\n// astrid distro install distro-id --from-shuttle /media/shuttle  # refuses: wrong key\n// after (only after verifying the new key out-of-band)\n// astrid distro install distro-id --from-shuttle /media/shuttle --accept-new-key","handlingStrategy":"try-catch","validationCode":"let pinned = read_pinned(home, distro_id)?;\nif let Some(pin) = pinned {\n    if pin != artifact_pubkey && !accept_new_key {\n        return Err(anyhow!(\"signing key differs from pin for {distro_id}\"));\n    }\n}","typeGuard":null,"tryCatchPattern":"match trust::verify_and_pin(...) {\n    Err(e) if e.to_string().contains(\"is pinned to\") => {\n        // confirm key rotation out-of-band, then retry with --accept-new-key\n    }\n    ...\n}","preventionTips":["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"],"tags":["security","key-pinning","signature","supply-chain"],"backgroundTag":"authentication-required","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}