JuliusBrussee/caveman · error

CAVEMAN_BINARY_SIGNING_PRIVATE_KEY_PEM is required

Error message

CAVEMAN_BINARY_SIGNING_PRIVATE_KEY_PEM is required

What it means

Thrown by scripts/sign-binary-checksums.mjs when the environment variable CAVEMAN_BINARY_SIGNING_PRIVATE_KEY_PEM is unset or empty. The script takes the public key from the command line but the private key only from the environment, so the secret never appears in shell history or process arguments. This check runs after argument validation and before any file is read.

Source

Thrown at scripts/sign-binary-checksums.mjs:45

      "sha256",
      checksums,
      createPublicKey(publicKeyPEM),
      Buffer.from(bundle?.messageSignature?.signature ?? "", "base64"),
    );
}

function normalizePublicKey(value) {
  return createPublicKey(value).export({ type: "spki", format: "pem" }).toString();
}

if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
  try {
    const [checksumsPath, outputPath, publicKeyPath] = process.argv.slice(2);
    const privateKeyPEM = process.env.CAVEMAN_BINARY_SIGNING_PRIVATE_KEY_PEM;
    if (!checksumsPath || !outputPath || !publicKeyPath) {
      throw new Error("usage: sign-binary-checksums.mjs <checksums.txt> <output.keysig> <public-key.pem>");
    }
    if (!privateKeyPEM) throw new Error("CAVEMAN_BINARY_SIGNING_PRIVATE_KEY_PEM is required");
    const checksums = readFileSync(checksumsPath);
    const publicKeyPEM = readFileSync(publicKeyPath, "utf8");
    if (normalizePublicKey(privateKeyPEM) !== normalizePublicKey(publicKeyPEM)) {
      throw new Error("binary signing private key does not match committed public key");
    }
    const bundle = checksumSignatureBundle(checksums, privateKeyPEM);
    if (!verifyChecksumSignatureBundle(checksums, bundle, publicKeyPEM)) {
      throw new Error("generated checksum signature failed local verification");
    }
    writeFileSync(outputPath, `${JSON.stringify(bundle)}\n`, { mode: 0o600 });
  } catch (error) {
    process.stderr.write(`${error.message}\n`);
    process.exit(1);
  }
}

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Provide the key via the environment: `CAVEMAN_BINARY_SIGNING_PRIVATE_KEY_PEM="$(cat signing-key.pem)" node scripts/sign-binary-checksums.mjs ...`.
  2. In CI, add CAVEMAN_BINARY_SIGNING_PRIVATE_KEY_PEM as a secret on the signing job.
  3. Keep the private key out of files and arguments — if it lives only on another machine, copy it securely first.

Example fix

# before
node scripts/sign-binary-checksums.mjs dist/checksums.txt dist/checksums.keysig keys/binary-public.pem
# after
CAVEMAN_BINARY_SIGNING_PRIVATE_KEY_PEM="$(cat ~/.secure/binary-signing.pem)" \
  node scripts/sign-binary-checksums.mjs dist/checksums.txt dist/checksums.keysig keys/binary-public.pem
Defensive patterns

Strategy: validation

Validate before calling

#!/usr/bin/env bash
set -euo pipefail
: "${CAVEMAN_BINARY_SIGNING_PRIVATE_KEY_PEM:?CAVEMAN_BINARY_SIGNING_PRIVATE_KEY_PEM is required}"

Prevention

When it happens

Trigger: Running the signer in a shell or CI job where CAVEMAN_BINARY_SIGNING_PRIVATE_KEY_PEM was never exported, was exported empty, or was lost when switching agents/machines.

Common situations: New release machine or CI runner without the secret configured; signing from a fresh shell after the key was only `export`ed in another session; secret name typo.

Related errors


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