mastra-ai/mastra · error

[FactorySecretEncryption] Duplicate key id "${previous.id}".

Error message

[FactorySecretEncryption] Duplicate key id "${previous.id}".

What it means

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.

Source

Thrown at mastracode/factory/src/secret-encryption.ts:73

    typeof (parsed as SecretEnvelopeV1).keyId !== 'string' ||
    typeof (parsed as SecretEnvelopeV1).iv !== 'string' ||
    typeof (parsed as SecretEnvelopeV1).ciphertext !== 'string' ||
    typeof (parsed as SecretEnvelopeV1).tag !== 'string'
  ) {
    throw new Error('[FactorySecretEncryption] Invalid encrypted value.');
  }
  return parsed as SecretEnvelopeV1;
}

/**
 * Creates a versioned AES-256-GCM encryptor. The primary key is used for new
 * writes; previous keys remain decrypt-only until stored values are rotated.
 */
export function createFactorySecretEncryption(config: FactorySecretEncryptionConfig): FactorySecretEncryption {
  const primaryKey = validateKey(config.primary);
  const keys = new Map<string, Buffer>([[config.primary.id, primaryKey]]);
  for (const previous of config.previous ?? []) {
    if (keys.has(previous.id)) throw new Error(`[FactorySecretEncryption] Duplicate key id "${previous.id}".`);
    keys.set(previous.id, validateKey(previous));
  }

  return {
    async encrypt<T>(value: T): Promise<string> {
      const iv = randomBytes(IV_BYTES);
      const cipher = createCipheriv(ALGORITHM, primaryKey, iv);
      const ciphertext = Buffer.concat([cipher.update(JSON.stringify(value), 'utf8'), cipher.final()]);
      const envelope: SecretEnvelopeV1 = {
        keyId: config.primary.id,
        iv: iv.toString('base64url'),
        ciphertext: ciphertext.toString('base64url'),
        tag: cipher.getAuthTag().toString('base64url'),
      };
      return `${ENVELOPE_PREFIX}${Buffer.from(JSON.stringify(envelope), 'utf8').toString('base64url')}`;
    },

    async decrypt<T>(value: unknown): Promise<DecryptedFactorySecret<T>> {

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Assign a unique id to each key in config (primary plus every previous key) and redeploy.
  2. Audit how config is assembled (env vars, config files, DI) to find where the same key/id is injected twice; deduplicate before construction.
  3. If the same key material genuinely appears twice under different purposes, keep one entry — duplicate material does not need two ids.
  4. 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.

Example fix

// before
createFactorySecretEncryption({
  primary: { id: 'k1', key: primaryKey },
  previous: [{ id: 'k1', key: oldKey }], // duplicate id
});
// after
createFactorySecretEncryption({
  primary: { id: 'k1', key: primaryKey },
  previous: [{ id: 'k0', key: oldKey }],
});
Defensive patterns

Strategy: validation

Validate before calling

function assertUniqueKeyIds(config: { primary: { id: string }; previous?: { id: string }[] }) {
  const ids = [config.primary.id, ...(config.previous ?? []).map(k => k.id)];
  const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
  if (dupes.length) throw new Error(`Duplicate secret key ids: ${dupes.join(', ')}`);
}

Try / catch

let encryption: FactorySecretEncryption;
try {
  encryption = createFactorySecretEncryption(keyConfig);
} catch (err) {
  if (err instanceof Error && err.message.includes('Duplicate key id')) {
    throw new Error(`Key configuration error: ${err.message} — check env var merging for secret keys`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling createFactorySecretEncryption({ primary, previous }) where primary.id equals some previous[n].id, or two entries in previous share the same id string.

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/6cd6c0cfdc2dd162. Report an issue: GitHub.