{"record":{"id":"923402f904e46c84","repo":"Hmbown/CodeWhale","slug":"set-codewhale-facts-signing-key-pem-or-codewhale-facts","errorCode":null,"errorMessage":"set CODEWHALE_FACTS_SIGNING_KEY (PEM) or CODEWHALE_FACTS_SIGNING_KEY_FILE","messagePattern":"set CODEWHALE_FACTS_SIGNING_KEY \\(PEM\\) or CODEWHALE_FACTS_SIGNING_KEY_FILE","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":366,"sourceCode":"    if (!stat.isFile() || stat.nlink !== 1 || stat.size > maxBytes || stat.ino !== before.ino || stat.dev !== before.dev) throw new Error(\"file is not a bounded regular single-link file\");\n    const bytes = Buffer.alloc(maxBytes + 1);\n    let size = 0;\n    while (size <= maxBytes) {\n      const count = readSync(fd, bytes, size, maxBytes + 1 - size, null);\n      if (!count) break;\n      size += count;\n    }\n    if (size > maxBytes) throw new Error(\"file exceeds size limit\");\n    return bytes.subarray(0, size);\n  } finally { closeSync(fd); }\n}\n\nfunction loadPrivateKeyFromEnv() {\n  refuseUnderCi();\n  let pem = process.env.CODEWHALE_FACTS_SIGNING_KEY;\n  const file = process.env.CODEWHALE_FACTS_SIGNING_KEY_FILE;\n  if (!pem && file) pem = readBoundedFile(file, 16 * 1024).toString(\"utf8\");\n  if (!pem) throw new Error(\"set CODEWHALE_FACTS_SIGNING_KEY (PEM) or CODEWHALE_FACTS_SIGNING_KEY_FILE\");\n  if (Buffer.byteLength(pem) > 16 * 1024) throw new Error(\"signing key exceeds size limit\");\n  const key = createPrivateKey({ key: pem, format: \"pem\" });\n  if (key.asymmetricKeyType !== \"ed25519\") throw new Error(\"signing key must be Ed25519\");\n  return key;\n}\n\nexport function validateTrustedKeys(keys) {\n  const seen = new Set();\n  for (const key of keys) {\n    if (!KEY_ID_RE.test(key.keyId) || seen.has(key.keyId) || ![\"active\", \"retired\"].includes(key.status) || strictBase64(key.publicKey, 32).length !== 32) throw new Error(\"invalid or duplicated pinned key\");\n    seen.add(key.keyId);\n  }\n  return keys;\n}\n\n/** Deliberately narrow syntax: a changed/unparseable table must fail the gate. */\nexport function parseTsKeys(text) {\n  const source = text.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\").replace(/^\\s*\\/\\/.*$/gm, \"\");","sourceCodeStart":348,"sourceCodeEnd":384,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L348-L384","documentation":"loadPrivateKeyFromEnv resolves the signing key from CODEWHALE_FACTS_SIGNING_KEY (PEM text) or CODEWHALE_FACTS_SIGNING_KEY_FILE (path). If neither variable yields a non-empty PEM string, this error is thrown before any signing can happen. It is a configuration-missing error, not a key-format error.","triggerScenarios":"Calling buildEnvelope/publish paths without CODEWHALE_FACTS_SIGNING_KEY set, or with CODEWHALE_FACTS_SIGNING_KEY_FILE set to an empty/unreadable-by-this-path file, or both unset; also when the env var is set but empty ('').","commonSituations":"Running the publish script in a fresh shell/container where the env vars were never exported; CI job where refuseUnderCi deliberately forbids keys; exporting the variable in one terminal but running the script in another.","solutions":["Export CODEWHALE_FACTS_SIGNING_KEY with the PEM contents: export CODEWHALE_FACTS_SIGNING_KEY=\"$(cat key.pem)\"","Or set CODEWHALE_FACTS_SIGNING_KEY_FILE to a regular single-link file containing one PEM key","Verify with `printenv CODEWHALE_FACTS_SIGNING_KEY | head -1` that it is non-empty","If running under CI, this is intentional — sign locally and publish the artifact instead"],"exampleFix":"// before\nnode web/scripts/facts-publish.mjs publish payload.json\n// after\nexport CODEWHALE_FACTS_SIGNING_KEY=\"$(cat ~/.codewhale/ed25519.pem)\"\nnode web/scripts/facts-publish.mjs publish payload.json","handlingStrategy":"validation","validationCode":"const pem = process.env.CODEWHALE_FACTS_SIGNING_KEY;\nconst file = process.env.CODEWHALE_FACTS_SIGNING_KEY_FILE;\nif (!pem && !file) throw new Error('set CODEWHALE_FACTS_SIGNING_KEY (PEM) or CODEWHALE_FACTS_SIGNING_KEY_FILE');","typeGuard":"const hasSigningKeyEnv = () => Boolean(process.env.CODEWHALE_FACTS_SIGNING_KEY || process.env.CODEWHALE_FACTS_SIGNING_KEY_FILE);","tryCatchPattern":"try { key = loadPrivateKeyFromEnv(); } catch (e) { if (e.message.startsWith('set CODEWHALE_FACTS_SIGNING_KEY')) { console.error('Export CODEWHALE_FACTS_SIGNING_KEY or CODEWHALE_FACTS_SIGNING_KEY_FILE and retry'); process.exit(2); } throw e; }","preventionTips":["Export signing env vars in the same shell/profile that runs the publish script","Use direnv or CI secrets to keep the variables present per-environment","Remember this script refuses under CI — sign locally by design","Verify non-emptiness: empty-string env values count as missing"],"tags":["configuration","env-var","signing"],"backgroundTag":"missing-env-var","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}