{"record":{"id":"15004f0f0fc575af","repo":"JuliusBrussee/caveman","slug":"trusted-pubkey-keyring-does-not-contain-the-bund","errorCode":null,"errorMessage":"trusted --pubkey keyring does not contain the bundle public key","messagePattern":"trusted --pubkey keyring does not contain the bundle public key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":17438,"sourceCode":"  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>]`);\n  let bundle: ReceiptBundle;\n  try { bundle = JSON.parse(await readFile(file, \"utf8\")) as ReceiptBundle; } catch (e) { return fail(`invalid bundle JSON: ${(e as Error).message}`); }\n  if (!Array.isArray(bundle.receipts)) return fail(\"bundle receipts must be an array\");\n\n  try {","sourceCodeStart":17420,"sourceCodeEnd":17456,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L17420-L17456","documentation":"The --pubkey JSON keyring parsed successfully, but it does not contain the bundle's current key: either no entry carries the bundle's key_id, or the entry with that id has different raw key bytes. The bundle was signed by a key your trust anchor does not know, so the pinned keyring cannot establish authenticity.","triggerScenarios":"The publisher rotated to a new key that is not yet in your pinned keyring file; or you are verifying a bundle from a different publisher than the one your keyring belongs to.","commonSituations":"Rotation happens between when you pinned the keyring and when the bundle was produced; teams pin a keyring snapshot and forget to refresh it after rotation; cross-publisher mix-ups in CI caches.","solutions":["Refresh the pinned keyring from the publisher so it contains the new key (keep old keys for older bundles)","Or pin the new current key directly as a raw base64 --pubkey file","If the new key is unexpected, treat the bundle as untrusted and investigate before trusting any new key material"],"exampleFix":"// before: pinned keyring only has the pre-rotation key\n{ \"public_keys\": [ { \"key_id\": \"k1\", \"alg\": \"Ed25519\", \"key\": \"OLD...\" } ] }\n\n// after: rotation-aware keyring\n{ \"public_keys\": [ { \"key_id\": \"k1\", \"alg\": \"Ed25519\", \"key\": \"OLD...\" }, { \"key_id\": \"k2\", \"alg\": \"Ed25519\", \"key\": \"NEW...\" } ] }","handlingStrategy":"validation","validationCode":"const doc = JSON.parse(await readFile(pubkeyFile, \"utf8\"));\nconst infos = Array.isArray(doc.public_keys) ? doc.public_keys : doc.public_key ? [doc.public_key] : [];\nconst current = infos.find((k) => k.key_id === bundle.public_key.key_id);\nif (!current || current.key !== bundle.public_key.key) {\n  throw new Error(\"pinned keyring does not cover the bundle's signing key — refresh after rotation\");\n}","typeGuard":"function keyringCoversKey(infos: { key_id: string; key: string }[], keyId: string, keyB64: string): boolean {\n  const hit = infos.find((k) => k.key_id === keyId);\n  return !!hit && hit.key === keyB64;\n}","tryCatchPattern":"try { execSync(`caveman receipts verify ${bundle} --pubkey ${pubkey}`); }\ncatch (e) {\n  if (/keyring does not contain the bundle public key/.test(String((e as Error).message))) {\n    fail(\"unknown signing key: refresh the pinned keyring or treat bundle as untrusted\");\n  }\n  throw e;\n}","preventionTips":["Subscribe to publisher key rotations and refresh pinned keyrings promptly","Keep historical keys in the pinned keyring so older bundles still verify","Treat an unknown signing key as a security event, not a routine failure"],"tags":["receipts","keyring","key-rotation","trust","verification"],"backgroundTag":"unknown-signing-key","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}