{"record":{"id":"9c82f117bfd1ccba","repo":"astrid-runtime/astrid","slug":"capsule-provenance-changed-after-authority-decisio","errorCode":null,"errorMessage":"capsule provenance changed after authority decision","messagePattern":"capsule provenance changed after authority decision","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/astrid-capsule-install/src/authority.rs","lineNumber":929,"sourceCode":"    source_dir: &Path,\n    manifest: &CapsuleManifest,\n    approved: Option<InstalledAuthority>,\n) -> anyhow::Result<InstalledAuthority> {\n    let verification = artifact::verify_directory(source_dir)?;\n    let content_digest = verification.content_digest().to_string();\n    let manifest_digest = digest_manifest(&std::fs::read(source_dir.join(\"Capsule.toml\"))?);\n    let (signer, signature) = verification_provenance(&verification);\n\n    if let Some(approved) = approved {\n        if approved.content_digest != content_digest {\n            bail!(\n                \"capsule content changed after authority decision (approved {}, found {})\",\n                approved.content_digest,\n                content_digest\n            );\n        }\n        if approved.signer != signer || approved.signature != signature {\n            bail!(\"capsule provenance changed after authority decision\");\n        }\n        if approved.capsule_id != manifest.package.name\n            || approved.version != manifest.package.version\n        {\n            bail!(\"capsule identity or version changed after authority decision\");\n        }\n        if approved.manifest_digest != manifest_digest {\n            bail!(\"capsule manifest changed after authority decision\");\n        }\n        if approved.approved_capabilities != manifest.capabilities {\n            bail!(\"capsule capabilities changed after authority decision\");\n        }\n        return Ok(approved);\n    }\n\n    // Calling the legacy library install API is itself an operator-authority\n    // action. User-facing CLI and daemon entry points use explicit decisions;\n    // this path preserves the existing trusted embedding API while recording","sourceCodeStart":911,"sourceCodeEnd":947,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/authority.rs#L911-L947","documentation":"`authority_for_install_source` compares the signer identity and signature of the freshly verified source against those recorded in the approved authority decision. This error is thrown when provenance changed — the capsule is now signed by a different signer or carries a different signature than what was approved — so the library refuses to reuse the old authority decision for content from a different origin.","triggerScenarios":"Calling `install_from_local_path_internal` with an approved `InstalledAuthority` where `approved.signer != current signer` or `approved.signature != current signature`, as recomputed by `verification_provenance(&verification)` over the re-verified source directory.","commonSituations":"Re-signing the capsule with a new key after approval; switching between signed and unsigned builds (or vice versa) in the source directory; pulling content from a different publisher into the reviewed directory.","solutions":["Re-run the authority approval against the current signing identity so the receipt records the new signer/signature","Restore the capsule to the exact signed artifact that was originally approved (same signer key and signature)","Verify the signing key was not unexpectedly rotated; if rotation is legitimate, publish and approve the re-signed release"],"exampleFix":"// before\nbail!(\"capsule provenance changed after authority decision\");\n// after: re-approve with the new signer\n// let authority = decision::approve(source_dir)?;\n// install_from_local_path_internal(source_dir, Some(authority))","handlingStrategy":"validation","validationCode":"// Verify provenance matches the approved decision before install\nlet verification = artifact::verify_directory(source_dir)?;\nlet (signer, signature) = verification_provenance(&verification);\nassert_eq!(approved.signer.as_deref(), signer.as_deref());\nassert_eq!(approved.signature.as_deref(), signature.as_deref());","typeGuard":"fn provenance_unchanged(approved: &InstalledAuthority, signer: &Option<String>, signature: &Option<String>) -> bool {\n    approved.signer == *signer && approved.signature == *signature\n}","tryCatchPattern":"match authority_for_install_source(source_dir, &manifest, Some(approved)) {\n    Err(e) if e.to_string().contains(\"provenance changed\") => reapprove_with_new_signer(),\n    other => other.map(install),\n}","preventionTips":["Sign with a stable, pinned release key; avoid ad-hoc key rotation between review and install","Never swap signed/unsigned builds in a reviewed directory","Re-run approval after any re-signing event","Record signer identity in CI so installs use the reviewed key"],"tags":["security","signature","provenance","install"],"backgroundTag":"signature-verification-failed","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"}