{"record":{"id":"fe860c6fc96f1f50","repo":"astrid-runtime/astrid","slug":"capsule-content-changed-after-authority-decision","errorCode":null,"errorMessage":"capsule content changed after authority decision (approved {}, found {})","messagePattern":"capsule content changed after authority decision \\(approved (.+?), found (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/astrid-capsule-install/src/authority.rs","lineNumber":922,"sourceCode":"        capability_expansions,\n        manifest_digest: artifact.manifest_digest,\n        requested_capabilities: manifest.capabilities,\n    })\n}\n\npub(crate) fn authority_for_install_source(\n    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\");","sourceCodeStart":904,"sourceCodeEnd":940,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/authority.rs#L904-L940","documentation":"`authority_for_install_source` re-verifies the capsule source directory and compares the freshly computed `content_digest` against the content digest recorded in the previously approved authority decision. This error is thrown when the capsule's bytes changed between the operator's approval and the actual install, preventing a decision made for one artifact from being silently applied to different content.","triggerScenarios":"Calling `install_from_local_path_internal` with an `approved: Option<InstalledAuthority>` whose `content_digest` differs from the digest of the source directory at install time — i.e. files in the source dir were added, removed, or edited after the authority decision was recorded.","commonSituations":"Editing capsule source or rebuilding the WASM artifact after approving the install; a build tool regenerating outputs in the source directory between approval and install; running install from a different (dirty) checkout than the one reviewed.","solutions":["Re-run the authority review/decision on the current source directory so a receipt matching the new content_digest is issued","Restore the source directory to the exact reviewed content (e.g. git checkout the reviewed commit) and retry the install","If the change is intentional and reviewed, redo the approval flow rather than bypassing the digest check"],"exampleFix":"// before\nbail!(\"capsule content changed after authority decision (approved {}, found {})\", approved.content_digest, content_digest);\n// after: refresh the decision for current content\n// let authority = decision::approve(source_dir)?;   // re-approve\n// install_from_local_path_internal(source_dir, Some(authority))","handlingStrategy":"validation","validationCode":"// Compute the current content digest and compare with the approved decision before installing\nlet current = artifact::verify_directory(source_dir)?.content_digest().to_string();\nassert_eq!(approved.content_digest, current, \"content changed since approval; re-approve\");","typeGuard":"fn digest_unchanged(approved: &InstalledAuthority, current: &str) -> bool {\n    approved.content_digest == current\n}","tryCatchPattern":"match authority_for_install_source(source_dir, &manifest, Some(approved)) {\n    Ok(a) => install(a),\n    Err(e) if e.to_string().contains(\"changed after authority decision\") => reapprove_and_retry(),\n    Err(e) => return Err(e),\n}","preventionTips":["Freeze the source directory (clean git state) during review and install","Re-approve whenever the build produces new bytes","Stop file watchers/builders while an install is authorizing","Treat any content change after review as requiring a fresh approval"],"tags":["security","integrity","digest-mismatch","install"],"backgroundTag":"checksum-mismatch","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"}