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

  1. Set the variable to a valid JSON object: FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS='{"v1":"<base64-32-byte-key>"}'
  2. Single-quote the value in shell/env files so JSON double quotes survive
  3. Ensure each value is itself a base64-encoded 32-byte key (validated later by decodeCredentialEncryptionKey)
  4. If no previous keys exist, unset the variable entirely (it defaults to {})
  5. 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

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


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