mastra-ai/mastra · error

[FactorySecretEncryption] Unknown key id "${envelope.keyId}"

Error message

[FactorySecretEncryption] Unknown key id "${envelope.keyId}".

What it means

decrypt() looks up envelope.keyId in the configured key map (primary + previous). This error is thrown when the envelope is well-formed but its keyId does not match any configured key, so the ciphertext cannot be decrypted. The library refuses to guess or fall back to brute-force key selection.

Source

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

      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>> {
      if (typeof value !== 'string' || !value.startsWith(ENVELOPE_PREFIX)) {
        return { value: structuredClone(value) as T, needsReencryption: true };
      }

      const envelope = parseEnvelope(value);
      const key = keys.get(envelope.keyId);
      if (!key) throw new Error(`[FactorySecretEncryption] Unknown key id "${envelope.keyId}".`);

      try {
        const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(envelope.iv, 'base64url'));
        decipher.setAuthTag(Buffer.from(envelope.tag, 'base64url'));
        const plaintext = Buffer.concat([
          decipher.update(Buffer.from(envelope.ciphertext, 'base64url')),
          decipher.final(),
        ]).toString('utf8');
        return {
          value: JSON.parse(plaintext) as T,
          needsReencryption: envelope.keyId !== config.primary.id,
        };
      } catch {
        throw new Error('[FactorySecretEncryption] Unable to decrypt encrypted value.');
      }
    },
  };
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Re-add the missing key (with its exact id and material) to `previous` in createFactorySecretEncryption so the value can be decrypted and re-encrypted under the primary key.
  2. Rotate the data: if the key material is truly lost, the values are unrecoverable — reset/regenerate the affected secrets and store fresh encryptions.
  3. Confirm the environment is using the same key config that encrypted the data (check env vars/config for the deployment).
  4. Verify the keyId in stored envelopes (decode the envelope) and reconcile with your key inventory before deployment.

Example fix

// before (old key dropped during rotation)
createFactorySecretEncryption({ primary: { id: 'k2', key } });
// after (keep old key decrypt-only until rotated)
createFactorySecretEncryption({
  primary: { id: 'k2', key },
  previous: [{ id: 'k1', key: oldKey }],
});
Defensive patterns

Strategy: validation

Validate before calling

const keyIds = new Set([config.primary.id, ...(config.previous ?? []).map(k => k.id)]);
function keyIdIsConfigured(envelopeKeyIds: string[]): boolean {
  return envelopeKeyIds.every(id => keyIds.has(id));
}
// e.g. scan stored envelopes and list unknown ids before deploying a rotation

Try / catch

try {
  return await encryption.decrypt(stored);
} catch (err) {
  if (err instanceof Error && err.message.includes('Unknown key id')) {
    const keyId = err.message.match(/"([^"]+)"/)?.[1];
    logger.error('Secret encrypted with unconfigured key', { keyId });
    return null; // or fall back to prompting for the secret
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling decrypt() on a value encrypted with a key whose id is absent from the current config — e.g. the value's keyId was rotated out of `previous`, a different environment's key produced the value, or the keyId string differs by case/whitespace.

Common situations: Key rotation where an old key was removed from `previous` before all stored values were re-encrypted; promoting a staging build against a production database (different factory keys per env); restoring a backup encrypted with keys no longer in config; shared database across services with different key sets.

Related errors


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