{"record":{"id":"c648d9ed22326c07","repo":"JuliusBrussee/caveman","slug":"signature-check-failed-for-checksums-txt-refusin-c648d9","errorCode":null,"errorMessage":"signature check failed for checksums.txt — refusing to install","messagePattern":"signature check failed for checksums\\.txt — refusing to install","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/shared/binary-installer/installer.mjs","lineNumber":178,"sourceCode":"  }\n  const found = onPath(name);\n  if (found) return found;\n  const binDir = join(process.env.CAVEMAN_HOME ?? join(homedir(), \".caveman\"), \"bin\");\n  const target = join(binDir, binaryInstallFilename(name));\n  if (executable(target)) return target;\n\n  const { os, arch } = targetPlatform();\n  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":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/shared/binary-installer/installer.mjs#L160-L196","documentation":"ensureBinary() downloads checksums.txt plus its sigstore bundle (checksums.txt.keysig) and verifies the bundle's media type, the SHA-256 digest of the manifest, and an Ed25519 signature against the pinned BINARY_SIGNING_PUBKEY. If signedDigest() returns false for any reason, the installer refuses to proceed — no binary is downloaded from a release whose manifest provenance cannot be proven.","triggerScenarios":"A tampered, truncated, or regenerated checksums.txt whose signature no longer matches; a sigstore bundle with an unexpected mediaType or digest algorithm; the wrong CAVE_BINARY_RELEASE_BASE serving content signed by a different key; truncated downloads of either file.","commonSituations":"MITM or compromised mirrors repacking releases; internal mirrors that re-sign or rewrite assets; release pipeline key rotation not yet reflected in the installed client version; network corruption.","solutions":["Do not bypass: this is a deliberate tamper gate — treat the source as untrusted until verified","Unset CAVE_BINARY_RELEASE_BASE to consume the official release host and retry","Update the tool or installer to the current version (a newer release may pin the rotated signing key)","If it persists against the official host, capture checksums.txt plus .keysig and report it upstream as a potential security incident"],"exampleFix":"# before\nCAVE_BINARY_RELEASE_BASE=https://untrusted-mirror/rels …  # re-signed assets → failure\n\n# after\nunset CAVE_BINARY_RELEASE_BASE   # verify against the official, correctly-signed release","handlingStrategy":"validation","validationCode":"// before overriding the release base, confirm the official signature chain applies\nif (process.env.CAVE_BINARY_RELEASE_BASE) {\n  console.warn(\"custom release base in effect — its signature must match the pinned signing key or install will refuse\");\n}","typeGuard":null,"tryCatchPattern":"try { await ensureBinary({ name, envVar }); }\ncatch (e) {\n  if (/signature check failed for checksums/.test(String(e?.message))) {\n    // SECURITY GATE: do not retry or bypass — verify the source, then either\n    // use the official host (unset CAVE_BINARY_RELEASE_BASE) or report upstream\n    throw new Error(`possible supply-chain tampering: ${e.message}`);\n  }\n  throw e;\n}","preventionTips":["Never disable or work around the checksum-signature gate","Keep the installer updated so its pinned signing key matches current releases","Investigate persistent failures against the official host as security incidents"],"tags":["security","installer","signature","supply-chain"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}