{"record":{"id":"6cd6c0cfdc2dd162","repo":"mastra-ai/mastra","slug":"factorysecretencryption-duplicate-key-id-prev","errorCode":null,"errorMessage":"[FactorySecretEncryption] Duplicate key id \"${previous.id}\".","messagePattern":"\\[FactorySecretEncryption\\] Duplicate key id \"(.+?)\"\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/secret-encryption.ts","lineNumber":73,"sourceCode":"    typeof (parsed as SecretEnvelopeV1).keyId !== 'string' ||\n    typeof (parsed as SecretEnvelopeV1).iv !== 'string' ||\n    typeof (parsed as SecretEnvelopeV1).ciphertext !== 'string' ||\n    typeof (parsed as SecretEnvelopeV1).tag !== 'string'\n  ) {\n    throw new Error('[FactorySecretEncryption] Invalid encrypted value.');\n  }\n  return parsed as SecretEnvelopeV1;\n}\n\n/**\n * Creates a versioned AES-256-GCM encryptor. The primary key is used for new\n * writes; previous keys remain decrypt-only until stored values are rotated.\n */\nexport function createFactorySecretEncryption(config: FactorySecretEncryptionConfig): FactorySecretEncryption {\n  const primaryKey = validateKey(config.primary);\n  const keys = new Map<string, Buffer>([[config.primary.id, primaryKey]]);\n  for (const previous of config.previous ?? []) {\n    if (keys.has(previous.id)) throw new Error(`[FactorySecretEncryption] Duplicate key id \"${previous.id}\".`);\n    keys.set(previous.id, validateKey(previous));\n  }\n\n  return {\n    async encrypt<T>(value: T): Promise<string> {\n      const iv = randomBytes(IV_BYTES);\n      const cipher = createCipheriv(ALGORITHM, primaryKey, iv);\n      const ciphertext = Buffer.concat([cipher.update(JSON.stringify(value), 'utf8'), cipher.final()]);\n      const envelope: SecretEnvelopeV1 = {\n        keyId: config.primary.id,\n        iv: iv.toString('base64url'),\n        ciphertext: ciphertext.toString('base64url'),\n        tag: cipher.getAuthTag().toString('base64url'),\n      };\n      return `${ENVELOPE_PREFIX}${Buffer.from(JSON.stringify(envelope), 'utf8').toString('base64url')}`;\n    },\n\n    async decrypt<T>(value: unknown): Promise<DecryptedFactorySecret<T>> {","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/secret-encryption.ts#L55-L91","documentation":"createFactorySecretEncryption builds a Map of key id -> key material, seeded with the primary key, then inserts each previous key. This error is thrown at construction time when two keys in the config share the same id, because a duplicate id would silently overwrite one key's material and make values encrypted under it undecryptable.","triggerScenarios":"Calling createFactorySecretEncryption({ primary, previous }) where primary.id equals some previous[n].id, or two entries in previous share the same id string.","commonSituations":"Copy-pasting a key entry and forgetting to change its id, generating a previous key from the same config object as primary, environment config merging two sources that both define the same key id, or a typo reusing an old rotation key's id.","solutions":["Assign a unique id to each key in config (primary plus every previous key) and redeploy.","Audit how config is assembled (env vars, config files, DI) to find where the same key/id is injected twice; deduplicate before construction.","If the same key material genuinely appears twice under different purposes, keep one entry — duplicate material does not need two ids.","List the ids before constructing (e.g. [primary, ...previous].map(k => k.id) and check for duplicates) to fail with a clearer message in your own config layer."],"exampleFix":"// before\ncreateFactorySecretEncryption({\n  primary: { id: 'k1', key: primaryKey },\n  previous: [{ id: 'k1', key: oldKey }], // duplicate id\n});\n// after\ncreateFactorySecretEncryption({\n  primary: { id: 'k1', key: primaryKey },\n  previous: [{ id: 'k0', key: oldKey }],\n});","handlingStrategy":"validation","validationCode":"function assertUniqueKeyIds(config: { primary: { id: string }; previous?: { id: string }[] }) {\n  const ids = [config.primary.id, ...(config.previous ?? []).map(k => k.id)];\n  const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);\n  if (dupes.length) throw new Error(`Duplicate secret key ids: ${dupes.join(', ')}`);\n}","typeGuard":null,"tryCatchPattern":"let encryption: FactorySecretEncryption;\ntry {\n  encryption = createFactorySecretEncryption(keyConfig);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('Duplicate key id')) {\n    throw new Error(`Key configuration error: ${err.message} — check env var merging for secret keys`);\n  }\n  throw err;\n}","preventionTips":["Generate a fresh unique id (e.g. uuid or monotonic k1/k2...) for every key at creation time.","Deduplicate and validate key config at the configuration-loading layer before constructing the encryptor.","Add a unit test that constructs the encryptor from your real config shape to catch id collisions in CI.","During rotation, append new keys to `previous` with their original ids — never rename old keys."],"tags":["configuration","key-rotation","duplicate-key","validation"],"backgroundTag":"duplicate-key-id","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}