{"record":{"id":"4182abc6b0c264a3","repo":"Hmbown/CodeWhale","slug":"unparsed-rust-trusted-keys-entry","errorCode":null,"errorMessage":"unparsed Rust TRUSTED_KEYS entry","messagePattern":"unparsed Rust TRUSTED_KEYS entry","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/check-cloud-facts.mjs","lineNumber":26,"sourceCode":"const REPO_ROOT = resolve(WEB_ROOT, \"..\");\nexport { parseTsKeys };\n\nexport function parseRustKeys(text) {\n  const source = text.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\").replace(/^\\s*\\/\\/.*$/gm, \"\");\n  const tables = [...source.matchAll(/^\\s*pub\\s+const\\s+TRUSTED_KEYS\\s*:\\s*&\\s*\\[TrustedKey\\]\\s*=\\s*&\\s*\\[([\\s\\S]*?)\\]\\s*;/gm)];\n  if (tables.length !== 1) throw new Error(\"cannot parse exactly one Rust TRUSTED_KEYS table\");\n  const table = tables[0];\n  const body = table[1].replace(/^\\s*\\/\\/.*$/gm, \"\");\n  const keys = [];\n  const remainder = body.replace(/TrustedKey\\s*\\{\\s*key_id:\\s*\"([^\"]+)\",\\s*public_key:\\s*\\[([^\\]]*)\\],\\s*status:\\s*KeyStatus::(Active|Retired)\\s*,?\\s*\\}/g, (_, keyId, encoded, status) => {\n    const pieces = encoded.split(\",\").map((piece) => piece.trim()).filter(Boolean);\n    if (pieces.length !== 32 || pieces.some((piece) => !/^(?:\\d+|0x[0-9a-fA-F]+)$/.test(piece))) throw new Error(\"Rust public key must contain 32 literal bytes\");\n    const bytes = pieces.map(Number);\n    if (bytes.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 255)) throw new Error(\"Rust public key byte out of range\");\n    keys.push({ keyId, publicKey: Buffer.from(bytes).toString(\"base64\"), status: status.toLowerCase() });\n    return \"\";\n  });\n  if (remainder.replace(/[\\s,]/g, \"\")) throw new Error(\"unparsed Rust TRUSTED_KEYS entry\");\n  return validateTrustedKeys(keys);\n}\n\nfunction text(path) { return readBoundedFile(path).toString(\"utf8\"); }\nfunction json(path) { return JSON.parse(text(path)); }\n\nexport function checkCloudFacts() {\n  const failures = [];\n  const source = json(resolve(REPO_ROOT, \"docs/cloud-facts/stable.json\"));\n  for (const error of validateSource(source)) failures.push(`stable.json: ${error}`);\n  if (source.channel !== \"stable\") failures.push(\"stable.json: channel must be stable\");\n  const latest = json(resolve(WEB_ROOT, \"data/latest-published-release.json\"));\n  if (source.release?.latest !== latest.version) failures.push(\"stable.json release.latest differs from latest-published-release.json\");\n  if (source.release?.release_url && source.release.release_url !== latest.url) failures.push(\"stable.json release.release_url differs from latest-published-release.json\");\n  // An explicit empty table is valid and inert; parse failures are never empty.\n  const rustKeys = parseRustKeys(text(resolve(REPO_ROOT, \"crates/config/src/cloud_facts/keys.rs\")));\n  const tsKeys = parseTsKeys(text(resolve(WEB_ROOT, \"lib/cloud-facts/keys.ts\")));\n  if (JSON.stringify(rustKeys) !== JSON.stringify(tsKeys)) failures.push(\"Rust and web pinned key tables diverge\");","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/check-cloud-facts.mjs#L8-L44","documentation":"After regex-extracting all TrustedKey entries from the table body, the checker verifies nothing remains except whitespace and commas. If leftover non-whitespace text exists, the table contained a construct the parser does not understand, and it throws rather than silently skipping keys — a security-relevant check since an unparsed key would be invisible to the comparison.","triggerScenarios":"A TrustedKey entry formatted differently than the regex expects (extra fields, reordered fields, missing status, different brace style), a deprecated/retired key marked with an attribute like #[deprecated] or #[allow(...)] inside the table, or a non-TrustedKey item in the array.","commonSituations":"Adding a new field to TrustedKey (e.g. added_at) without updating the checker; using `KeyStatus::Revoked` (not Active|Retired) so the status alternation fails; multi-line formatting with attributes between entries.","solutions":["Look at what text remains unmatched — the error gives no offset, so diff the table against the regex: TrustedKey { key_id: \"..\", public_key: [..], status: KeyStatus::Active|Retired }.","Update the parser regex if TrustedKey gained legitimate fields.","Use only Active or Retired statuses in the table, or extend the alternation.","Remove non-TrustedKey items or attributes from the array."],"exampleFix":"// before (Rust)\n#[allow(dead_code)]\nTrustedKey { key_id: \"old\", ... },\n// after\nTrustedKey { key_id: \"old\", ... },","handlingStrategy":"validation","validationCode":"const stripped = tableBody.replace(/TrustedKey\\s*\\{[^}]*\\}/g, \"\").replace(/[\\s,]/g, \"\"); if (stripped) throw new Error(\"unrecognized content in TRUSTED_KEYS: \" + stripped.slice(0, 80));","typeGuard":null,"tryCatchPattern":"try { parseRustKeys(src); } catch (e) { if (e.message.includes(\"unparsed\")) console.error(\"TRUSTED_KEYS contains a construct the parser does not know\"); throw e; }","preventionTips":["Extend the parser regex whenever TrustedKey gains fields.","Restrict statuses to Active|Retired or update the alternation.","Keep attributes and foreign items out of the table.","Run the checker in CI on every change to the keys module."],"tags":["parsing","rust","validation","security"],"backgroundTag":"schema-validation-failed","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"}