{"record":{"id":"ef10079b02a5037a","repo":"JuliusBrussee/caveman","slug":"label-contains-duplicate-key-id-decoded-info","errorCode":null,"errorMessage":"${label} contains duplicate key_id ${decoded.info.key_id}","messagePattern":"(.+?) contains duplicate key_id (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":17404,"sourceCode":"    prev = r;\n  }\n  return null;\n}\n\nfunction decodeReceiptKey(info: ReceiptPublicKey, label: string): DecodedReceiptKey {\n  if (!info || typeof info.key_id !== \"string\" || !info.key_id.trim()) throw new Error(`${label} key_id is required`);\n  if (info.alg !== \"Ed25519\") throw new Error(`${label} has unsupported algorithm ${String(info.alg)}`);\n  if (typeof info.key !== \"string\" || !info.key.trim()) throw new Error(`${label} key is required`);\n  const raw = Buffer.from(info.key, \"base64\");\n  if (raw.length !== 32 || raw.toString(\"base64\") !== info.key) throw new Error(`${label} must be a canonical base64 Ed25519 public key`);\n  return { info, raw, key: ed25519PublicKey(raw) };\n}\n\nfunction decodeUniqueKeyring(infos: ReceiptPublicKey[], label: string): Map<string, DecodedReceiptKey> {\n  const keys = new Map<string, DecodedReceiptKey>();\n  for (const [index, info] of infos.entries()) {\n    const decoded = decodeReceiptKey(info, `${label}[${index}]`);\n    if (keys.has(decoded.info.key_id)) throw new Error(`${label} contains duplicate key_id ${decoded.info.key_id}`);\n    keys.set(decoded.info.key_id, decoded);\n  }\n  return keys;\n}\n\nfunction embeddedReceiptKeys(bundle: ReceiptBundle): { current: DecodedReceiptKey; keys: Map<string, DecodedReceiptKey> } {\n  if (bundle.schema !== RECEIPT_BUNDLE_V1 && bundle.schema !== RECEIPT_BUNDLE_V2) throw new Error(`unsupported bundle schema ${String(bundle.schema)}`);\n  if (bundle.verification_coverage !== undefined && bundle.verification_coverage !== INCLUDED_RECEIPTS_ONLY) throw new Error(`unsupported unsigned verification coverage ${String(bundle.verification_coverage)}`);\n  if (bundle.completeness_attested === true) throw new Error(\"bundle completeness cannot be attested by unsigned export metadata\");\n  const current = decodeReceiptKey(bundle.public_key, \"public_key\");\n  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 };","sourceCodeStart":17386,"sourceCodeEnd":17422,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L17386-L17422","documentation":"While building the receipt rotation keyring, decodeUniqueKeyring maps each key_id to exactly one Ed25519 key. If two entries in bundle.public_keys (or in a --pubkey JSON keyring) declare the same key_id, the verifier throws, because a duplicate key_id makes signature attribution ambiguous.","triggerScenarios":"A bundle whose public_keys array contains two objects with the same key_id, or a --pubkey JSON file whose public_keys has a repeated key_id. Thrown from decodeUniqueKeyring during embeddedReceiptKeys/pinnedReceiptKeys, before any signature verification runs.","commonSituations":"A rotation script appends a new key but reuses the old key_id; keyrings from two environments are concatenated and overlap; a hand-edited --pubkey JSON duplicates an entry.","solutions":["Remove the duplicate entry so every key_id appears exactly once","If two different keys genuinely exist, mint a distinct key_id for the new key (each rotated key gets a fresh id) and re-export","Regenerate the bundle or --pubkey JSON from the source keyring instead of hand-merging","When merging keyrings, dedupe by key_id before writing the file"],"exampleFix":"// before (bundle.public_keys)\n[{\"key_id\":\"k1\",\"alg\":\"Ed25519\",\"key\":\"AAA...\"},{\"key_id\":\"k1\",\"alg\":\"Ed25519\",\"key\":\"BBB...\"}]\n\n// after\n[{\"key_id\":\"k1\",\"alg\":\"Ed25519\",\"key\":\"AAA...\"},{\"key_id\":\"k2\",\"alg\":\"Ed25519\",\"key\":\"BBB...\"}]","handlingStrategy":"validation","validationCode":"const ids = new Set<string>();\nfor (const [i, k] of (bundle.public_keys ?? []).entries()) {\n  if (typeof k?.key_id !== \"string\") throw new Error(`public_keys[${i}] missing key_id`);\n  if (ids.has(k.key_id)) throw new Error(`duplicate key_id ${k.key_id} at public_keys[${i}]`);\n  ids.add(k.key_id);\n}","typeGuard":"function hasUniqueKeyIds(infos: { key_id?: unknown }[]): boolean {\n  const seen = new Set<string>();\n  return infos.every((k) =>\n    typeof k?.key_id === \"string\" && !seen.has(k.key_id) && seen.add(k.key_id) === seen);\n}","tryCatchPattern":"try { execSync(`caveman receipts verify ${bundle} ${pubkey}`); }\ncatch (e) {\n  if (/duplicate key_id/.test(String((e as Error).message))) fail(\"keyring has reused key_id — rotation must mint a new id\");\n  throw e;\n}","preventionTips":["Mint a new key_id for every rotated key; never reuse ids across key material","Dedupe merged keyrings by key_id in the merge script before writing files","Assert keyring uniqueness in producer tests so bad exports fail at build time"],"tags":["receipts","keyring","ed25519","key-rotation","validation"],"backgroundTag":"duplicate-key-id","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"}