JuliusBrussee/caveman · critical · Error

signature check failed for checksums.txt — refusing to insta

Error message

signature check failed for checksums.txt — refusing to install

What it means

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.

Source

Thrown at packages/shared/binary-installer/installer.mjs:178

  }
  const found = onPath(name);
  if (found) return found;
  const binDir = join(process.env.CAVEMAN_HOME ?? join(homedir(), ".caveman"), "bin");
  const target = join(binDir, binaryInstallFilename(name));
  if (executable(target)) return target;

  const { os, arch } = targetPlatform();
  const artifact = `${name}_${os}_${arch}`;
  const base = (process.env.CAVE_BINARY_RELEASE_BASE ?? BINARY_RELEASE_BASE_DEFAULT).replace(/\/+$/, "");
  const release = `${base}/${BINARY_RELEASE}`;
  const timeout = timeoutMs();
  const [checksumsResponse, signatureResponse] = await Promise.all([
    asset(`${release}/checksums.txt`, timeout),
    asset(`${release}/checksums.txt.keysig`, timeout),
  ]);
  const [checksums, signature] = await Promise.all([checksumsResponse.text(), signatureResponse.text()]);
  if (!signedDigest(checksums, signature)) {
    throw new Error("signature check failed for checksums.txt — refusing to install");
  }
  const expected = expectedDigest(checksums, artifact);
  mkdirSync(binDir, { recursive: true });
  const part = `${target}.part`;
  cleanup(part);
  try {
    const actual = await download(`${release}/${artifact}`, part, timeout);
    if (actual !== expected) throw new Error(`signature check failed for ${artifact} — partial download deleted`);
    chmodSync(part, 0o755);
    renameSync(part, target);
  } catch (error) {
    cleanup(part);
    throw error;
  }
  process.stderr.write(`${name}  ${os}/${arch}  checksum verified\n`);
  return target;
}

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Do not bypass: this is a deliberate tamper gate — treat the source as untrusted until verified
  2. Unset CAVE_BINARY_RELEASE_BASE to consume the official release host and retry
  3. Update the tool or installer to the current version (a newer release may pin the rotated signing key)
  4. If it persists against the official host, capture checksums.txt plus .keysig and report it upstream as a potential security incident

Example fix

# before
CAVE_BINARY_RELEASE_BASE=https://untrusted-mirror/rels …  # re-signed assets → failure

# after
unset CAVE_BINARY_RELEASE_BASE   # verify against the official, correctly-signed release
Defensive patterns

Strategy: validation

Validate before calling

// before overriding the release base, confirm the official signature chain applies
if (process.env.CAVE_BINARY_RELEASE_BASE) {
  console.warn("custom release base in effect — its signature must match the pinned signing key or install will refuse");
}

Try / catch

try { await ensureBinary({ name, envVar }); }
catch (e) {
  if (/signature check failed for checksums/.test(String(e?.message))) {
    // SECURITY GATE: do not retry or bypass — verify the source, then either
    // use the official host (unset CAVE_BINARY_RELEASE_BASE) or report upstream
    throw new Error(`possible supply-chain tampering: ${e.message}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/c648d9ed22326c07. Report an issue: GitHub.