{"record":{"id":"e0974b2c6619f649","repo":"JuliusBrussee/caveman","slug":"signature-check-failed-for-artifact-partial-d","errorCode":null,"errorMessage":"signature check failed for ${artifact} — partial download deleted","messagePattern":"signature check failed for (.+?) — partial download deleted","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/shared/binary-installer/installer.mjs","lineNumber":186,"sourceCode":"  const artifact = `${name}_${os}_${arch}`;\n  const base = (process.env.CAVE_BINARY_RELEASE_BASE ?? BINARY_RELEASE_BASE_DEFAULT).replace(/\\/+$/, \"\");\n  const release = `${base}/${BINARY_RELEASE}`;\n  const timeout = timeoutMs();\n  const [checksumsResponse, signatureResponse] = await Promise.all([\n    asset(`${release}/checksums.txt`, timeout),\n    asset(`${release}/checksums.txt.keysig`, timeout),\n  ]);\n  const [checksums, signature] = await Promise.all([checksumsResponse.text(), signatureResponse.text()]);\n  if (!signedDigest(checksums, signature)) {\n    throw new Error(\"signature check failed for checksums.txt — refusing to install\");\n  }\n  const expected = expectedDigest(checksums, artifact);\n  mkdirSync(binDir, { recursive: true });\n  const part = `${target}.part`;\n  cleanup(part);\n  try {\n    const actual = await download(`${release}/${artifact}`, part, timeout);\n    if (actual !== expected) throw new Error(`signature check failed for ${artifact} — partial download deleted`);\n    chmodSync(part, 0o755);\n    renameSync(part, target);\n  } catch (error) {\n    cleanup(part);\n    throw error;\n  }\n  process.stderr.write(`${name}  ${os}/${arch}  checksum verified\\n`);\n  return target;\n}\n","sourceCodeStart":168,"sourceCodeEnd":196,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/shared/binary-installer/installer.mjs#L168-L196","documentation":"After streaming the platform artifact to a .part file and hashing it incrementally, ensureBinary() compares the computed SHA-256 against the entry in the signature-verified checksums.txt. A mismatch means the bytes received are not the bytes signed — corrupted, truncated, or altered in transit — so the partial download is deleted and the install aborted.","triggerScenarios":"Truncated download (connection dropped mid-stream without fetch erroring); proxies or antivirus rewriting bodies; disk-full producing a short file; bit-level corruption on flaky links; a mirror serving a different build under the same name.","commonSituations":"Corporate TLS-inspecting proxies altering payloads; antivirus injecting bytes; CI runners with flaky egress; disk quota exhaustion mid-write; slow links where partial flushes survive.","solutions":["Free disk space and retry the install — the .part file is auto-cleaned, so a plain rerun is safe","If a proxy or AV middleware is on the path, bypass it for the release host or add an exception, then retry","Verify by hashing manually: download the artifact with curl, run sha256sum, compare to checksums.txt — a persistent mismatch means a bad mirror or host, not your network","Self-supply a trusted binary via CAVEMAN_MCP_BIN / CAVEMAN_SHRINK_BIN / CAVEMAN_BROWSE_BIN if the host cannot be fixed"],"exampleFix":"# before (flaky proxy mangles the body)\nnpm run setup   # signature check failed … partial download deleted\n\n# after\nexport NO_PROXY=release.host.example   # or fix the proxy exception\nnpm run setup","handlingStrategy":"retry","validationCode":"// pre-flight disk space so the streamed write cannot come up short\nconst st = await fs.promises.statfs(binDir);\nif (st.bsize * st.bavail < 100 * 1024 * 1024) {\n  throw new Error(\"insufficient disk space for binary install\");\n}","typeGuard":null,"tryCatchPattern":"for (const wait of [0, 3000, 10000]) {\n  await sleep(wait);\n  try { return await ensureBinary({ name, envVar }); }\n  catch (e) {\n    if (!/signature check failed for .* — partial download deleted/.test(String(e?.message))) throw e;\n  }\n}\n// corrupted-truncation failures are safe to retry — the .part file is always cleaned","preventionTips":["Ensure adequate disk space before setup","Exempt the release host from TLS-inspecting proxies and antivirus body-rewriting","On persistent mismatch, hash a manual curl download and compare against checksums.txt before reporting the host"],"tags":["security","installer","checksum","network"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}