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
- 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.
- Rotate the data: if the key material is truly lost, the values are unrecoverable — reset/regenerate the affected secrets and store fresh encryptions.
- Confirm the environment is using the same key config that encrypted the data (check env vars/config for the deployment).
- 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
- During rotation, keep every retired key in `previous` until a migration has re-encrypted all stored values.
- Use environment-specific key sets only with environment-specific data; never share a database across envs with different keys.
- Back up key material (e.g. in a KMS) so restored data always has its decrypting keys available.
- Before deploying, decode stored envelopes and assert every keyId exists in the new config.
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
- Cookie password must be at least 32 characters for SSO. Set
- Cookie password must be at least 32 characters for SSO. Set
- Telegram installation secrets are encrypted at rest, but no
- [FactorySecretEncryption] Key id is required.
- [FactorySecretEncryption] Key "${id}" must be exactly 32 byt
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/2e27949710cee9d7.
Report an issue: GitHub.