{"record":{"id":"a1d9fa6d5d3ee5c2","repo":"Hmbown/CodeWhale","slug":"invalid-base64-facts-publish","errorCode":null,"errorMessage":"invalid base64","messagePattern":"invalid base64","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":90,"sourceCode":"  // Ed25519 SPKI DER is a fixed 12-byte prefix followed by the 32-byte key.\n  return spki.subarray(spki.length - 32);\n}\n\nexport function publicKeyObjectFromRaw(rawB64) {\n  const raw = strictBase64(rawB64, 32);\n  if (raw.length !== 32) throw new Error(\"public key must decode to 32 bytes\");\n  const prefix = Buffer.from(\"302a300506032b6570032100\", \"hex\");\n  return createPublicKey({ key: Buffer.concat([prefix, raw]), type: \"spki\", format: \"der\" });\n}\n\nexport function signPayload(privateKey, keyId, payloadBytes) {\n  return sign(null, signingMessage(keyId, payloadBytes), privateKey);\n}\n\n/** Canonical base64 is checked before decoding to bound allocation. */\nexport function strictBase64(value, maxBytes) {\n  if (typeof value !== \"string\" || !value.length || value.length > 4 * Math.ceil(maxBytes / 3) ||\n      (value.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value))) throw new Error(\"invalid base64\");\n  const bytes = Buffer.from(value, \"base64\");\n  if (bytes.length > maxBytes || bytes.toString(\"base64\") !== value) throw new Error(\"invalid base64\");\n  return bytes;\n}\n\nexport function utcTime(value) {\n  if (typeof value !== \"string\" || !/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,3})?Z$/.test(value)) return null;\n  const time = Date.parse(value);\n  return Number.isFinite(time) && new Date(time).toISOString().slice(0, 19) === value.slice(0, 19) ? time : null;\n}\n\nexport function verifyEnvelope(envelope, publicKeyB64) {\n  const errors = [];\n  if (!isPlainObject(envelope)) return { ok: false, errors: [\"envelope must be an object\"] };\n  if (envelope.envelope !== ENVELOPE_VERSION) errors.push(\"unsupported envelope version\");\n  if (envelope.alg !== \"ed25519\") errors.push(\"unsupported signature algorithm\");\n  if (typeof envelope.key_id !== \"string\" || !KEY_ID_RE.test(envelope.key_id)) errors.push(\"bad key_id\");\n  if (envelope.schema_version !== SCHEMA_VERSION) errors.push(\"unsupported schema version\");","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L72-L108","documentation":"strictBase64 validates a string as canonical standard base64 before decoding: non-empty, length multiple of 4, only A-Za-z0-9+/ with up to two trailing '=', and length within the bound implied by maxBytes. The first throw happens when the input fails these format checks — this bounds allocation and rejects ambiguous encodings before Buffer decoding.","triggerScenarios":"Passing a value that is not a string, an empty string, a string containing '-'/'_' (base64url alphabet), whitespace/newlines, or a length that is not a multiple of 4.","commonSituations":"Pasting URL-safe base64 (JWT segments) where standard base64 is required, copying a key with line breaks or a 'base64:' prefix, or passing undefined/null.","solutions":["Convert base64url to standard base64: replace '-'→'+' and '_'→'/', pad with '=' to a multiple of 4","Strip whitespace and any scheme prefix from the value","Check that the string length is a multiple of 4 and the charset matches /^[A-Za-z0-9+/]*={0,2}$/"],"exampleFix":"// before\nstrictBase64('a-b_c=', 32); // base64url chars\n// after\nconst std = ('a-b_c='.replace(/-/g, '+').replace(/_/g, '/') + '==').slice(0, Math.ceil('a-b_c='.length / 4) * 4);\nstrictBase64(std, 32);","handlingStrategy":"validation","validationCode":"const looksLikeBase64 = (s) => typeof s === 'string' && s.length > 0 && s.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(s);\nif (!looksLikeBase64(value)) throw new Error('not canonical base64');","typeGuard":"const isCanonicalB64Shape = (v) => typeof v === 'string' && v.length > 0 && v.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(v);","tryCatchPattern":"try { key = strictBase64(value, 32); } catch (e) { if (e.message === 'invalid base64') { value = normalizeToStdBase64(value); key = strictBase64(value, 32); } else throw e; }","preventionTips":["Convert base64url (JWT segments) to standard base64 before use","Strip whitespace/line-wrapping from copied keys","Ensure length is a multiple of 4 with correct padding"],"tags":["base64","encoding","validation"],"backgroundTag":"invalid-argument-format","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}