{"record":{"id":"ae48bf6a57c319c9","repo":"mastra-ai/mastra","slug":"factory-credential-encryption-previous-keys-must-b","errorCode":null,"errorMessage":"FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS must be a JSON object of key ids to base64 keys.","messagePattern":"FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS must be a JSON object of key ids to base64 keys\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"mastracode/web/src/mastra/index.ts","lineNumber":75,"sourceCode":"  return key;\n}\n\nfunction credentialEncryption() {\n  const encodedKey = process.env.FACTORY_CREDENTIAL_ENCRYPTION_KEY?.trim();\n  if (!encodedKey) {\n    console.warn(\n      '[factory] FACTORY_CREDENTIAL_ENCRYPTION_KEY is not set. Stored model-provider keys, custom-provider ' +\n        'API keys, and integration secrets will be persisted as plaintext. Generate a key with ' +\n        '`openssl rand -base64 32` and set FACTORY_CREDENTIAL_ENCRYPTION_KEY to encrypt them at rest.',\n    );\n    return undefined;\n  }\n\n  const previousKeys: Record<string, unknown> = process.env.FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS\n    ? JSON.parse(process.env.FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS)\n    : {};\n  if (!previousKeys || Array.isArray(previousKeys) || typeof previousKeys !== 'object') {\n    throw new Error('FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS must be a JSON object of key ids to base64 keys.');\n  }\n\n  return createFactorySecretEncryption({\n    primary: {\n      id: process.env.FACTORY_CREDENTIAL_ENCRYPTION_KEY_ID?.trim() || 'v1',\n      key: decodeCredentialEncryptionKey('FACTORY_CREDENTIAL_ENCRYPTION_KEY', encodedKey),\n    },\n    previous: Object.entries(previousKeys).map(([id, value]) => {\n      if (typeof value !== 'string') {\n        throw new Error('FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS values must be base64 strings.');\n      }\n      return { id, key: decodeCredentialEncryptionKey('FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS', value) };\n    }),\n  });\n}\n\nfunction investigateIntakeIssue(context: FactoryStageRuleContext) {\n  return {","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/web/src/mastra/index.ts#L57-L93","documentation":"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.'","triggerScenarios":"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.","commonSituations":"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.","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\""],"exampleFix":"// before\nFACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS=[abc123, def456]\n// after\nFACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS='{\"v1\":\"3q2+7wBASE64KEY Exactly32BytesPad==\"}'","handlingStrategy":"validation","validationCode":"const raw = process.env.FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS;\nif (raw) {\n  let parsed: unknown;\n  try { parsed = JSON.parse(raw); } catch { throw new Error('PREVIOUS_KEYS is not valid JSON'); }\n  if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') throw new Error('PREVIOUS_KEYS must be a JSON object of id -> base64 key');\n}","typeGuard":"function isPreviousKeysMap(v: unknown): v is Record<string, string> {\n  return !!v && !Array.isArray(v) && typeof v === 'object' && Object.values(v).every(x => typeof x === 'string');\n}","tryCatchPattern":"try {\n  startServer();\n} catch (e) {\n  if (String(e.message).includes('FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS')) {\n    console.error('Fix FACTORY_CREDENTIAL_ENCRYPTION_PREVIOUS_KEYS: must be a JSON object like {\"v1\":\"<base64 key>\"}');\n    process.exit(1);\n  }\n}","preventionTips":["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"],"tags":["configuration","encryption","env-var","json"],"backgroundTag":"invalid-env-var-format","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}