mastra-ai/mastra · critical
${name} must contain base64-encoded 32-byte keys.
Error message
${name} must contain base64-encoded 32-byte keys. What it means
decodeCredentialEncryptionKey expects FACTORY_CREDENTIAL_ENCRYPTION_KEY (or a previous key) to be a base64 string that decodes to exactly 32 bytes, suitable for AES-256. If Buffer.from(encodedKey, 'base64') yields any other length, it throws '<name> must contain base64-encoded 32-byte keys.' This guards against silently using a weak or malformed encryption key for stored credentials.
Source
Thrown at mastracode/web/src/mastra/index.ts:56
import { SlackIntegration } from '@mastra/factory/integrations/slack/integration';
import type { IMastraAuthProvider } from '@mastra/core/server';
/**
* Parse a positive-integer env knob; anything else means "use the default".
* Fractional values are rejected rather than floored — flooring `0.5` to `0`
* would silently disable a capacity knob or turn an idle window into
* immediate expiry.
*/
function positiveInt(raw: string | undefined): number | undefined {
if (!raw) return undefined;
const parsed = Number(raw);
if (!Number.isSafeInteger(parsed) || parsed <= 0) return undefined;
return parsed;
}
function decodeCredentialEncryptionKey(name: string, encodedKey: string): Buffer {
const key = Buffer.from(encodedKey, 'base64');
if (key.byteLength !== 32) throw new Error(`${name} must contain base64-encoded 32-byte keys.`);
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') {View on GitHub (pinned to 75dd419e61)
Solutions
- Regenerate the key as exactly 32 bytes and base64 it: `openssl rand -base64 32`
- Verify with `node -e "console.log(Buffer.from(process.env.FACTORY_CREDENTIAL_ENCRYPTION_KEY,'base64').byteLength)"` that it prints 32
- If a hex key was used, convert: Buffer.from(hexKey,'hex').toString('base64')
- Move the old unusable key into FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS only if it also satisfies the 32-byte rule, otherwise decrypt-and-reencrypt credentials with a valid key
- Fix quoting in the env file so the base64 value (which may contain +/=) is preserved exactly
Example fix
// before FACTORY_CREDENTIAL_ENCRYPTION_KEY=my-secret-passphrase // after FACTORY_CREDENTIAL_ENCRYPTION_KEY=$(openssl rand -base64 32) # 44 chars, decodes to 32 bytes
Defensive patterns
Strategy: validation
Validate before calling
const key = process.env.FACTORY_CREDENTIAL_ENCRYPTION_KEY;
if (!key || Buffer.from(key.trim(), 'base64').byteLength !== 32) {
throw new Error('FACTORY_CREDENTIAL_ENCRYPTION_KEY must be base64 of exactly 32 bytes (generate: openssl rand -base64 32)');
} Type guard
function isEncryptionKey(v: unknown): v is string {
return typeof v === 'string' && v.trim().length > 0 && Buffer.from(v.trim(), 'base64').byteLength === 32;
} Try / catch
try {
startServer();
} catch (e) {
if (String(e.message).includes('must contain base64-encoded 32-byte keys')) {
console.error('Bad encryption key; regenerate with: openssl rand -base64 32');
process.exit(1);
}
} Prevention
- Generate keys only with `openssl rand -base64 32`
- Add a CI/startup check that decodes key env vars to 32 bytes
- Never paste passphrases or hex strings as base64 keys
- Quote env values containing + or / in dotenv files
When it happens
Trigger: Starting the mastra web server with FACTORY_CREDENTIAL_ENCRYPTION_KEY set to a value that is not valid base64 or does not decode to 32 bytes — e.g. a raw passphrase, a hex string, a truncated key, or a base64 of a 16- or 64-byte secret.
Common situations: Generating the key with something other than 32 random bytes (e.g. `openssl rand -hex 32`, which yields 64 base64-decoded bytes, or `head -c 16`), copying the key with padding/newline issues, or pasting a password instead of a generated key.
Related errors
- Cookie password must be at least 32 characters for SSO. Set
- [MastraAuthGoogle] GOOGLE_COOKIE_PASSWORD is required for Go
- FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS must be a JSON o
- Clerk JWKS URI, secret key and publishable key are required,
- 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/107f2e5848f0efb6.
Report an issue: GitHub.