{"record":{"id":"f72643b87da947e9","repo":"JuliusBrussee/caveman","slug":"pubkey-json-is-malformed","errorCode":null,"errorMessage":"--pubkey JSON is malformed","messagePattern":"--pubkey JSON is malformed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":17433,"sourceCode":"  if (bundle.public_keys !== undefined && !Array.isArray(bundle.public_keys)) throw new Error(\"public_keys must be an array\");\n  if (bundle.schema === RECEIPT_BUNDLE_V2 && (!Array.isArray(bundle.public_keys) || bundle.public_keys.length === 0)) throw new Error(\"v2 bundle requires public_keys\");\n  const keys = decodeUniqueKeyring(bundle.public_keys ?? [], \"public_keys\");\n  const currentInRing = keys.get(current.info.key_id);\n  if (currentInRing && !currentInRing.raw.equals(current.raw)) throw new Error(`public_key conflicts with public_keys entry ${current.info.key_id}`);\n  if (bundle.schema === RECEIPT_BUNDLE_V2 && !currentInRing) throw new Error(\"v2 public_keys must include public_key\");\n  if (!currentInRing) keys.set(current.info.key_id, current);\n  return { current, keys };\n}\n\nasync function pinnedReceiptKeys(file: string, current: DecodedReceiptKey): Promise<{ keys: Map<string, DecodedReceiptKey>; trust: string }> {\n  const source = (await readFile(file, \"utf8\")).trim();\n  if (!source.startsWith(\"{\")) {\n    const pinned = decodeReceiptKey({ ...current.info, key: source }, \"--pubkey\");\n    if (!pinned.raw.equals(current.raw)) throw new Error(\"bundle public key does not match the published --pubkey\");\n    return { keys: new Map([[current.info.key_id, pinned]]), trust: \"pinned_public_key\" };\n  }\n  let parsed: { public_key?: ReceiptPublicKey; public_keys?: ReceiptPublicKey[] };\n  try { parsed = JSON.parse(source); } catch { throw new Error(\"--pubkey JSON is malformed\"); }\n  const infos = Array.isArray(parsed.public_keys) ? parsed.public_keys : parsed.public_key ? [parsed.public_key] : [];\n  if (infos.length === 0) throw new Error(\"--pubkey JSON must contain public_key or public_keys\");\n  const keys = decodeUniqueKeyring(infos, \"--pubkey public_keys\");\n  const pinnedCurrent = keys.get(current.info.key_id);\n  if (!pinnedCurrent || !pinnedCurrent.raw.equals(current.raw)) throw new Error(\"trusted --pubkey keyring does not contain the bundle public key\");\n  return { keys, trust: \"pinned_keyring\" };\n}\n\n// receiptsVerify validates a signed receipt bundle offline (no network). A raw\n// --pubkey pins the current key; JSON may independently pin a full rotation\n// keyring. Without either, embedded keys prove self-consistency, not publisher\n// authenticity. Exits non-zero on any included content, signature, or\n// scope-chain break. Tail/scope omission needs separately trusted head manifest;\n// bundle output states completeness is not attested.\n//   caveman receipts verify <bundle.json> [--pubkey <file>]\nasync function receiptsVerify(argv: string[]) {\n  const file = positionalAfterOptions(argv.slice(1), new Set([\"--pubkey\"]));\n  if (!file) throw new Error(`usage: ${invokedCommand(\"receipts\")} verify <bundle.json> [--pubkey <file>]`);","sourceCodeStart":17415,"sourceCodeEnd":17451,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L17415-L17451","documentation":"A --pubkey file whose first character is '{' is parsed as JSON; if JSON.parse throws, the CLI reports \"--pubkey JSON is malformed\". Common JSON syntax faults: trailing commas, comments, single-quoted strings, or two objects concatenated.","triggerScenarios":"Passing a --pubkey file that starts with '{' but is not valid JSON — e.g. hand-assembled key files, JSON with trailing commas, or a log line prefixed before the object.","commonSituations":"Hand-editing a keyring and leaving a trailing comma; concatenating two JSON documents; copying pretty-printed JSON with smart quotes from a chat/doc.","solutions":["Validate the file first: jq empty pubkey.json (or node -e 'JSON.parse(require(\"fs\").readFileSync(\"pubkey.json\",\"utf8\"))')","Fix the syntax fault the parser reports","Ensure the file contains exactly one JSON object"],"exampleFix":"// before\n{ \"public_key\": { \"key_id\": \"k1\", \"alg\": \"Ed25519\", \"key\": \"AAA...\", }, }\n\n// after\n{ \"public_key\": { \"key_id\": \"k1\", \"alg\": \"Ed25519\", \"key\": \"AAA...\" } }","handlingStrategy":"validation","validationCode":"const src = await readFile(pubkeyFile, \"utf8\");\nif (src.trim().startsWith(\"{\")) {\n  try { JSON.parse(src); } catch (e) {\n    throw new Error(`--pubkey file is not valid JSON: ${(e as Error).message}`);\n  }\n}","typeGuard":"function parsesAsJsonObject(s: string): boolean {\n  try { return typeof JSON.parse(s) === \"object\" && JSON.parse(s) !== null; } catch { return false; }\n}","tryCatchPattern":"try { execSync(`caveman receipts verify ${bundle} --pubkey ${pubkey}`); }\ncatch (e) {\n  if (/--pubkey JSON is malformed/.test(String((e as Error).message))) {\n    fail(`fix pubkey JSON syntax (jq empty ${pubkey} shows the error)`);\n  }\n  throw e;\n}","preventionTips":["Generate key files programmatically (JSON.stringify), never by hand","Run `jq empty` on key files in CI preflight","Keep exactly one JSON object per file"],"tags":["receipts","pubkey","json","syntax"],"backgroundTag":"malformed-json","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}