{"record":{"id":"0e8ca0eb8c0120cf","repo":"Hmbown/CodeWhale","slug":"primary-signing-key-is-not-pinned-and-active-refusing","errorCode":null,"errorMessage":"primary signing key is not pinned and active; refusing publication","messagePattern":"primary signing key is not pinned and active; refusing publication","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":405,"sourceCode":"  const table = tables[0];\n  const body = table[1].replace(/^\\s*\\/\\/.*$/gm, \"\");\n  const keys = [];\n  const remainder = body.replace(/\\{\\s*keyId:\\s*\"([^\"]+)\",\\s*publicKey:\\s*\"([^\"]+)\",\\s*status:\\s*\"([^\"]+)\"\\s*,?\\s*\\}/g, (_, keyId, publicKey, status) => {\n    keys.push({ keyId, publicKey, status });\n    return \"\";\n  });\n  if (remainder.replace(/[\\s,]/g, \"\")) throw new Error(\"unparsed TypeScript TRUSTED_KEYS entry\");\n  return validateTrustedKeys(keys);\n}\n\nfunction loadTrustedKeysFromRepo() {\n  const keys = parseTsKeys(readBoundedFile(resolve(WEB_ROOT, \"lib/cloud-facts/keys.ts\"), 64 * 1024).toString(\"utf8\"));\n  return new Map(keys.map((key) => [key.keyId, key]));\n}\n\nexport function activePublishingKey(envelope, keys, now = Date.now()) {\n  const key = validateTrustedKeys(keys).find((key) => key.keyId === envelope.key_id && key.status === \"active\");\n  if (!key) throw new Error(\"primary signing key is not pinned and active; refusing publication\");\n  const check = verifyEnvelope(envelope, key.publicKey);\n  if (!check.ok) throw new Error(`envelope does not verify: ${check.errors.join(\"; \")}`);\n  if (!Number.isFinite(now) || utcTime(check.payload.published_at) > now + 300_000 ||\n      (check.payload.not_after != null && utcTime(check.payload.not_after) <= now)) throw new Error(\"publication timestamp is future or expired\");\n  return { key, check };\n}\n\nfunction refuseUnderCi() {\n  for (const marker of CI_MARKERS) {\n    if (process.env[marker] && !/^(0|false|no|off)$/i.test(process.env[marker])) {\n      throw new Error(`refusing to run with a secret under CI (${marker} is set); publish from the founder's machine`);\n    }\n  }\n}\n\nfunction sqlLiteral(value) {\n  if (value === null || value === undefined) return \"null\";\n  return `'${String(value).replace(/'/g, \"''\")}'`;","sourceCodeStart":387,"sourceCodeEnd":423,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L387-L423","documentation":"activePublishingKey looks up the envelope's key_id among the repo-pinned TRUSTED_KEYS and requires that key to exist with status \"active\" before verifying the signature. If no pinned active key matches the envelope's key_id, publication is refused to prevent signing facts with an unpinned, revoked, or retired key.","triggerScenarios":"Calling activePublishingKey with an envelope whose key_id is absent from the keys list, matches a key whose status is not \"active\" (e.g. \"revoked\"/\"retired\"), or when the keys argument itself is empty or unvalidated.","commonSituations":"The signer generated a new Ed25519 key but never added it to lib/cloud-facts/keys.ts; the key was rotated and the old one marked revoked while the local build still signs with it; the wrong keys file/map was passed in.","solutions":["Add the envelope's key_id and its base64 public key to TRUSTED_KEYS in web/lib/cloud-facts/keys.ts with status \"active\" (commit and land that change first)","If the key was intentionally rotated, regenerate the envelope with the currently active key instead of resurrecting the revoked one","Confirm the keys argument passed to activePublishingKey is the map built by loadTrustedKeysFromRepo, not an empty or stale list"],"exampleFix":"// before (keys.ts)\n{ keyId: \"k1\", publicKey: \"abc\", status: \"revoked\" }\n// after (new key pinned)\n{ keyId: \"k2\", publicKey: \"def\", status: \"active\" }","handlingStrategy":"validation","validationCode":"import { loadTrustedKeysFromRepo } from \"./facts-publish.mjs\";\nconst keys = await loadTrustedKeysFromRepo();\nif (!keys.has(envelope.key_id)) throw new Error(`key ${envelope.key_id} is not pinned in keys.ts — pin it before signing/publishing`);\nif (keys.get(envelope.key_id).status !== \"active\") throw new Error(`key ${envelope.key_id} is ${keys.get(envelope.key_id).status}, not active`);","typeGuard":null,"tryCatchPattern":"try {\n  await activePublishingKey(envelope, keys);\n} catch (err) {\n  if (err.message.includes(\"not pinned and active\")) {\n    console.error(`Key ${envelope.key_id} is missing from TRUSTED_KEYS or not active — pin it or re-sign with the active key`);\n    process.exit(1);\n  }\n  throw err;\n}","preventionTips":["Pin new keys in lib/cloud-facts/keys.ts (status \"active\") before signing envelopes with them","After a rotation, regenerate envelopes with the new active key instead of reusing old ones","Confirm the signing key_id equals a pinned active key_id before invoking the publish script"],"tags":["security","key-rotation","publishing-gate"],"backgroundTag":"resource-not-found","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"}