mastra-ai/mastra · critical
FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS must be a JSON o
Error message
FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS must be a JSON object of key ids to base64 keys.
What it means
credentialEncryption parses FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS as a JSON object mapping key ids to base64-encoded 32-byte keys used for decrypting credentials encrypted under older primary keys. If the raw env var is set but is not a JSON object (or parses to null/an array), it throws 'FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS must be a JSON object of key ids to base64 keys.'
Source
Thrown at mastracode/web/src/mastra/index.ts:75
return key;
}
function credentialEncryption() {
const encodedKey = process.env.FACTORY_CREDENTIAL_ENCRYPTION_KEY?.trim();
if (!encodedKey) {
console.warn(
'[factory] FACTORY_CREDENTIAL_ENCRYPTION_KEY is not set. Stored model-provider keys, custom-provider ' +
'API keys, and integration secrets will be persisted as plaintext. Generate a key with ' +
'`openssl rand -base64 32` and set FACTORY_CREDENTIAL_ENCRYPTION_KEY to encrypt them at rest.',
);
return undefined;
}
const previousKeys: Record<string, unknown> = process.env.FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS
? JSON.parse(process.env.FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS)
: {};
if (!previousKeys || Array.isArray(previousKeys) || typeof previousKeys !== 'object') {
throw new Error('FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS must be a JSON object of key ids to base64 keys.');
}
return createFactorySecretEncryption({
primary: {
id: process.env.FACTORY_CREDENTIAL_ENCRYPTION_KEY_ID?.trim() || 'v1',
key: decodeCredentialEncryptionKey('FACTORY_CREDENTIAL_ENCRYPTION_KEY', encodedKey),
},
previous: Object.entries(previousKeys).map(([id, value]) => {
if (typeof value !== 'string') {
throw new Error('FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS values must be base64 strings.');
}
return { id, key: decodeCredentialEncryptionKey('FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS', value) };
}),
});
}
function investigateIntakeIssue(context: FactoryStageRuleContext) {
return {View on GitHub (pinned to 75dd419e61)
Solutions
- Set the variable to a valid JSON object: FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS='{"v1":"<base64-32-byte-key>"}'
- Single-quote the value in shell/env files so JSON double quotes survive
- Ensure each value is itself a base64-encoded 32-byte key (validated later by decodeCredentialEncryptionKey)
- If no previous keys exist, unset the variable entirely (it defaults to {})
- Validate with: node -e "const v=JSON.parse(process.env.FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS); if(!v||Array.isArray(v)||typeof v!=='object') throw 0"
Example fix
// before
FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS=[abc123, def456]
// after
FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS='{"v1":"3q2+7wBASE64KEY Exactly32BytesPad=="}' Defensive patterns
Strategy: validation
Validate before calling
const raw = process.env.FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS;
if (raw) {
let parsed: unknown;
try { parsed = JSON.parse(raw); } catch { throw new Error('PREVIOUS_KEYS is not valid JSON'); }
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') throw new Error('PREVIOUS_KEYS must be a JSON object of id -> base64 key');
} Type guard
function isPreviousKeysMap(v: unknown): v is Record<string, string> {
return !!v && !Array.isArray(v) && typeof v === 'object' && Object.values(v).every(x => typeof x === 'string');
} Try / catch
try {
startServer();
} catch (e) {
if (String(e.message).includes('FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS')) {
console.error('Fix FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS: must be a JSON object like {"v1":"<base64 key>"}');
process.exit(1);
}
} Prevention
- Single-quote JSON values in shell/env files to preserve double quotes
- Use an id->key object, never an array or bare key list
- Unset the variable when there are no previous keys
- Validate the JSON and key lengths in deploy preflight checks
When it happens
Trigger: Setting FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS to invalid JSON (unquoted, trailing commas), a JSON array, a JSON string/number, or a value that JSON.parse turns into null — e.g. a bare list of keys or a quoted-but-malformed string in the env file.
Common situations: Pasting a JSON object without shell-safe quoting (spaces/quotes stripped by dotenv or shell), supplying an array of keys instead of an id->key map, or hand-editing the env var and breaking JSON syntax.
Related errors
- ${name} must contain base64-encoded 32-byte keys.
- Clerk JWKS URI, secret key and publishable key are required,
- Cookie password must be at least 32 characters for SSO. Set
- Google client ID is required. Provide it in the options or s
- Cookie password must be at least 32 characters for SSO. Set
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/ae48bf6a57c319c9.
Report an issue: GitHub.