{"record":{"id":"8d7e3b8d239d8748","repo":"Hmbown/CodeWhale","slug":"signing-key-must-be-ed25519","errorCode":null,"errorMessage":"signing key must be Ed25519","messagePattern":"signing key must be Ed25519","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":369,"sourceCode":"    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, \"\");\n  const tables = [...source.matchAll(/^\\s*export\\s+const\\s+TRUSTED_KEYS\\s*:\\s*readonly\\s+TrustedKey\\[\\]\\s*=\\s*\\[([\\s\\S]*?)\\]\\s*;/gm)];\n  if (tables.length !== 1) throw new Error(\"cannot parse exactly one TypeScript TRUSTED_KEYS table\");\n  const table = tables[0];","sourceCodeStart":351,"sourceCodeEnd":387,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L351-L387","documentation":"loadPrivateKeyFromEnv parses the PEM with createPrivateKey and then asserts the key type is Ed25519; any other algorithm (RSA, EC, etc.) throws this error. The signing scheme is fixed to Ed25519, so verifiers will only accept Ed25519 signatures.","triggerScenarios":"Setting CODEWHALE_FACTS_SIGNING_KEY(_FILE) to an RSA, EC, or PKCS8-encrypted PEM; the file/env containing a public key or certificate instead of a private key where asymmetricKeyType resolves to something else.","commonSituations":"Reusing an existing RSA/EC key pair instead of generating an Ed25519 one; pointing at a .pub file or a cert chain; older automation generating RSA keys by default.","solutions":["Generate an Ed25519 key: openssl genpkey -algorithm ed25519 -out key.pem","Point CODEWHALE_FACTS_SIGNING_KEY_FILE at the new private key","Verify the type: openssl pkey -in key.pem -text -noout | head -1","Publish/rotate the corresponding public key into the trusted-keys pin list"],"exampleFix":"// before\nexport CODEWHALE_FACTS_SIGNING_KEY=\"$(cat rsa-key.pem)\"   # RSA\n// after\nopenssl genpkey -algorithm ed25519 -out ed25519.pem\nexport CODEWHALE_FACTS_SIGNING_KEY=\"$(cat ed25519.pem)\"","handlingStrategy":"validation","validationCode":"import { createPrivateKey } from 'node:crypto';\nconst key = createPrivateKey({ key: pem, format: 'pem' });\nif (key.asymmetricKeyType !== 'ed25519') throw new Error(`need Ed25519 private key, got ${key.asymmetricKeyType}`);","typeGuard":"const isEd25519Pem = (s) => { try { return createPrivateKey({ key: s, format: 'pem' }).asymmetricKeyType === 'ed25519'; } catch { return false; } };","tryCatchPattern":"try { key = loadPrivateKeyFromEnv(); } catch (e) { if (e.message === 'signing key must be Ed25519') { console.error('Generate an Ed25519 key: openssl genpkey -algorithm ed25519'); process.exit(2); } throw e; }","preventionTips":["Generate keys explicitly with -algorithm ed25519","Never point the env/file at certificates or public keys","Verify key type after any rotation before publishing","Standardize on a key-generation script so algorithm drift cannot happen"],"tags":["configuration","signing","key-type"],"backgroundTag":"invalid-config-value","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"}