{"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":17860,"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":17842,"sourceCodeEnd":17878,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L17842-L17878","documentation":"Thrown by decodeUniqueKeyring when two or more entries in a public_keys array share the same key_id. The keyring is keyed by key_id so receipts can select the right verifying key; duplicates would make that mapping ambiguous, so the verifier refuses the bundle.","triggerScenarios":"Verifying a v2 receipt bundle whose public_keys array contains two entries with identical key_id values (with otherwise possibly different key material or a plain accidental repeat).","commonSituations":"Merging bundles by concatenating public_keys arrays; copying an entry to update it instead of replacing it; re-adding the same key twice during key rotation tooling.","solutions":["Deduplicate the public_keys array so each key_id appears once.","If the duplicate has different key material, assign the new key a distinct key_id.","Regenerate the bundle with the official export command, which builds a unique keyring."],"exampleFix":"// before\npublic_keys: [k(\\\"2026-q3\\\", A), k(\\\"2026-q3\\\", B)]\n// after\npublic_keys: [k(\\\"2026-q3\\\", A), k(\\\"2026-q4\\\", B)]","handlingStrategy":"validation","validationCode":"const ids = bundle.public_keys.map(k => k.key_id);\nif (new Set(ids).size !== ids.length) throw new Error('duplicate key_id in public_keys');","typeGuard":"function hasUniqueKeyIds(keys: { key_id: string }[]): boolean {\n  return new Set(keys.map(k => k.key_id)).size === keys.length;\n}","tryCatchPattern":"try {\n  verifyBundle(bundle);\n} catch (e) {\n  if ((e as Error).message.includes('duplicate key_id')) console.error('deduplicate public_keys before verifying');\n  throw e;\n}","preventionTips":["Deduplicate by key_id when merging bundles.","Assign a fresh key_id on every key rotation instead of reusing one.","Validate keyring uniqueness at bundle-build time."],"tags":["validation","crypto","duplicate"],"backgroundTag":"invalid-config-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}