JuliusBrussee/caveman · error
${label} contains duplicate key_id ${decoded.info.key_id}
Error message
${label} contains duplicate key_id ${decoded.info.key_id} What it means
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.
Source
Thrown at packages/cli/src/index.ts:17404
prev = r;
}
return null;
}
function decodeReceiptKey(info: ReceiptPublicKey, label: string): DecodedReceiptKey {
if (!info || typeof info.key_id !== "string" || !info.key_id.trim()) throw new Error(`${label} key_id is required`);
if (info.alg !== "Ed25519") throw new Error(`${label} has unsupported algorithm ${String(info.alg)}`);
if (typeof info.key !== "string" || !info.key.trim()) throw new Error(`${label} key is required`);
const raw = Buffer.from(info.key, "base64");
if (raw.length !== 32 || raw.toString("base64") !== info.key) throw new Error(`${label} must be a canonical base64 Ed25519 public key`);
return { info, raw, key: ed25519PublicKey(raw) };
}
function decodeUniqueKeyring(infos: ReceiptPublicKey[], label: string): Map<string, DecodedReceiptKey> {
const keys = new Map<string, DecodedReceiptKey>();
for (const [index, info] of infos.entries()) {
const decoded = decodeReceiptKey(info, `${label}[${index}]`);
if (keys.has(decoded.info.key_id)) throw new Error(`${label} contains duplicate key_id ${decoded.info.key_id}`);
keys.set(decoded.info.key_id, decoded);
}
return keys;
}
function embeddedReceiptKeys(bundle: ReceiptBundle): { current: DecodedReceiptKey; keys: Map<string, DecodedReceiptKey> } {
if (bundle.schema !== RECEIPT_BUNDLE_V1 && bundle.schema !== RECEIPT_BUNDLE_V2) throw new Error(`unsupported bundle schema ${String(bundle.schema)}`);
if (bundle.verification_coverage !== undefined && bundle.verification_coverage !== INCLUDED_RECEIPTS_ONLY) throw new Error(`unsupported unsigned verification coverage ${String(bundle.verification_coverage)}`);
if (bundle.completeness_attested === true) throw new Error("bundle completeness cannot be attested by unsigned export metadata");
const current = decodeReceiptKey(bundle.public_key, "public_key");
if (bundle.public_keys !== undefined && !Array.isArray(bundle.public_keys)) throw new Error("public_keys must be an array");
if (bundle.schema === RECEIPT_BUNDLE_V2 && (!Array.isArray(bundle.public_keys) || bundle.public_keys.length === 0)) throw new Error("v2 bundle requires public_keys");
const keys = decodeUniqueKeyring(bundle.public_keys ?? [], "public_keys");
const currentInRing = keys.get(current.info.key_id);
if (currentInRing && !currentInRing.raw.equals(current.raw)) throw new Error(`public_key conflicts with public_keys entry ${current.info.key_id}`);
if (bundle.schema === RECEIPT_BUNDLE_V2 && !currentInRing) throw new Error("v2 public_keys must include public_key");
if (!currentInRing) keys.set(current.info.key_id, current);
return { current, keys };View on GitHub (pinned to 5184b3d11a)
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
Example fix
// before (bundle.public_keys)
[{"key_id":"k1","alg":"Ed25519","key":"AAA..."},{"key_id":"k1","alg":"Ed25519","key":"BBB..."}]
// after
[{"key_id":"k1","alg":"Ed25519","key":"AAA..."},{"key_id":"k2","alg":"Ed25519","key":"BBB..."}] Defensive patterns
Strategy: validation
Validate before calling
const ids = new Set<string>();
for (const [i, k] of (bundle.public_keys ?? []).entries()) {
if (typeof k?.key_id !== "string") throw new Error(`public_keys[${i}] missing key_id`);
if (ids.has(k.key_id)) throw new Error(`duplicate key_id ${k.key_id} at public_keys[${i}]`);
ids.add(k.key_id);
} Type guard
function hasUniqueKeyIds(infos: { key_id?: unknown }[]): boolean {
const seen = new Set<string>();
return infos.every((k) =>
typeof k?.key_id === "string" && !seen.has(k.key_id) && seen.add(k.key_id) === seen);
} Try / catch
try { execSync(`caveman receipts verify ${bundle} ${pubkey}`); }
catch (e) {
if (/duplicate key_id/.test(String((e as Error).message))) fail("keyring has reused key_id — rotation must mint a new id");
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- public_key conflicts with public_keys entry ${current.info.k
- ${label} key_id is required
- ${label} key is required
- public_keys must be an array
- v2 bundle requires public_keys
AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-08-18).
Data as JSON: /api/errors/ef10079b02a5037a.
Report an issue: GitHub.