{"record":{"id":"1bd2776d58f52600","repo":"gitbutlerapp/gitbutler","slug":"signature-verification-failed-the-download-may-h","errorCode":null,"errorMessage":"Signature verification failed - the download may have been tampered with: {e}","messagePattern":"Signature verification failed - the download may have been tampered with: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"critical","filePath":"crates/but-installer/src/install.rs","lineNumber":88,"sourceCode":"    let mut file =\n        File::open(installable).context(\"Failed to open installable for verification\")?;\n    let mut verifier = public_key\n        .verify_stream(&signature)\n        .map_err(|e| anyhow!(\"Failed to initialize signature verifier: {e}\"))?;\n\n    // Read and verify file in 64KB chunks\n    let mut buffer = [0u8; 65536];\n    loop {\n        let bytes_read = file.read(&mut buffer)?;\n        if bytes_read == 0 {\n            break;\n        }\n        verifier.update(&buffer[..bytes_read]);\n    }\n\n    // Finalize verification\n    verifier.finalize().map_err(|e| {\n        anyhow!(\"Signature verification failed - the download may have been tampered with: {e}\")\n    })?;\n\n    ui::info(\"Signature verification passed\");\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use std::io::Write;\n\n    use super::*;\n\n    #[test]\n    fn test_verify_signature_empty() {\n        let temp_dir = tempfile::tempdir().unwrap();\n        let test_file = temp_dir.path().join(\"test.tar.gz\");\n\n        // Create a dummy file","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-installer/src/install.rs#L70-L106","documentation":"The installer verifies the downloaded artifact against a pinned minisign public key (`RWTrOEI+im1XYA9RBwyxnzFN/evFzJhU1lbQ70LVayWH3WRo7xQnRLD2`) by streaming the file through the verifier in 64 KB chunks. `verifier.finalize()` failing means the streamed bytes do not match the signature — a corrupted or truncated download, a mismatched artifact/signature pair, or genuinely modified content.","triggerScenarios":"`verify_signature` runs after a download whose bytes don't match the minisignature: an interrupted HTTP transfer saved to disk, a signature taken from a different release than the artifact, a body rewritten by a TLS-inspecting proxy or antivirus, or a pinned key that no longer matches the project's current signing key.","commonSituations":"Flaky networks truncating downloads; mirrors or caches pairing artifacts with stale signatures; corporate proxies with TLS inspection rewriting bodies; signing-key rotations leaving old installers with the previous pinned key; locally rebuilt artifacts shipped with the official signature.","solutions":["Delete the artifact and signature, re-download both from the official release, and retry.","Check the artifact's size and checksum against the release manifest to catch truncation.","Ensure artifact and signature come from the same release version — no version skew between the two downloads.","If the project rotated its signing key, move to a current installer build whose pinned key matches.","Rule out proxies/AV rewriting downloads; fetch over a clean network path."],"exampleFix":"// before\nverify_signature(&artifact, &sig_b64, &tmp)?;\ninstall(&artifact)?;\n\n// after\nif let Err(e) = verify_signature(&artifact, &sig_b64, &tmp) {\n    if e.to_string().contains(\"Signature verification failed\") {\n        std::fs::remove_file(&artifact)?; // discard untrusted bytes\n        re_download(&release).await?;     // fresh artifact + signature pair\n    }\n}\nverify_signature(&artifact, &sig_b64, &tmp)?;\ninstall(&artifact)?;","handlingStrategy":"try-catch","validationCode":"use std::fs;\n\n// Cheap pre-checks before minisign verification\nlet sig = fs::read_to_string(&sig_path)?;\nlet len = fs::metadata(&artifact)?.len();\nif sig.trim().is_empty() || len == 0 {\n    anyhow::bail!(\"Incomplete download - refetch artifact and signature\");\n}","typeGuard":null,"tryCatchPattern":"match verify_signature(&artifact, &signature_b64, &tmp_dir) {\n    Ok(()) => {}\n    Err(e) if e.to_string().contains(\"Signature verification failed\") => {\n        // Security-sensitive: do NOT install. Remove the artifact and alert.\n        let _ = std::fs::remove_file(&artifact);\n        anyhow::bail!(\"Install aborted, possible tampering: {e}\");\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Download artifact and signature together from the same official release; never mix versions.","Check size/SHA-256 before signature verification to catch truncated downloads cheaply.","Treat verification failure as tamper-suspected: quarantine and alert — never fall back to installing unverified bytes.","Track the project's signing-key rotations; a stale pinned key causes false 'tampered' failures on legitimate artifacts."],"tags":["rust","installer","minisign","signature-verification","security","download","supply-chain"],"backgroundTag":"signature-verification-failed","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}