{"record":{"id":"9c89514226fb6b21","repo":"mastra-ai/mastra","slug":"factorysecretencryption-key-id-must-be-exac","errorCode":null,"errorMessage":"[FactorySecretEncryption] Key \"${id}\" must be exactly 32 bytes.","messagePattern":"\\[FactorySecretEncryption\\] Key \"(.+?)\" must be exactly 32 bytes\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/secret-encryption.ts","lineNumber":39,"sourceCode":"  encrypt<T>(value: T): Promise<string>;\n  decrypt<T>(value: unknown): Promise<DecryptedFactorySecret<T>>;\n}\n\nexport interface FactorySecretEncryptionKey {\n  id: string;\n  key: Uint8Array;\n}\n\nexport interface FactorySecretEncryptionConfig {\n  primary: FactorySecretEncryptionKey;\n  previous?: FactorySecretEncryptionKey[];\n}\n\nfunction validateKey({ id, key }: FactorySecretEncryptionKey): Buffer {\n  if (!id) throw new Error('[FactorySecretEncryption] Key id is required.');\n  const buffer = Buffer.from(key);\n  if (buffer.byteLength !== 32) {\n    throw new Error(`[FactorySecretEncryption] Key \"${id}\" must be exactly 32 bytes.`);\n  }\n  return buffer;\n}\n\nfunction parseEnvelope(value: string): SecretEnvelopeV1 {\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(Buffer.from(value.slice(ENVELOPE_PREFIX.length), 'base64url').toString('utf8'));\n  } catch {\n    throw new Error('[FactorySecretEncryption] Invalid encrypted value.');\n  }\n\n  if (\n    !parsed ||\n    typeof parsed !== 'object' ||\n    typeof (parsed as SecretEnvelopeV1).keyId !== 'string' ||\n    typeof (parsed as SecretEnvelopeV1).iv !== 'string' ||\n    typeof (parsed as SecretEnvelopeV1).ciphertext !== 'string' ||","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/secret-encryption.ts#L21-L57","documentation":"Encryption is AES-256-GCM, which requires a 256-bit (32-byte) key. validateKey() converts the provided Uint8Array key to a Buffer and throws unless byteLength is exactly 32, for the primary key and every entry in `previous`. A wrong length would make createCipheriv/createDecipheriv fail or silently weaken the setup, so the library fails fast.","triggerScenarios":"Passing a FactorySecretEncryptionKey whose key Uint8Array is not 32 bytes (e.g. 16-byte AES-128 key, 44-char base64 string decoded to 32+ chars, hex-encoded 64-char string interpreted as 64 raw bytes, or truncated/padded env var) to createFactorySecretEncryption or primaryKey.","commonSituations":"Supplying a hex string's characters instead of its decoded bytes; base64 keys with whitespace/newlines changing length; reusing an older AES-128 key after upgrade; env var gotcha where the raw key contains characters and was never decoded; copying a key and dropping bytes.","solutions":["Generate/use exactly 32 random bytes: randomBytes(32), or decode a stored encoding first (Buffer.from(base64Str, 'base64') or Buffer.from(hexStr, 'hex')).","Log/verify key.byteLength === 32 before constructing the encryption config.","If the stored key was 16 bytes (AES-128), re-encrypt secrets with a new 32-byte key rather than padding the old one.","Trim whitespace/newlines from env-provided keys before decoding."],"exampleFix":"// before\nconst key = Buffer.from(process.env.SECRET_KEY ?? '', 'utf8'); // arbitrary length\ncreateFactorySecretEncryption({ primary: { id: 'k1', key } });\n\n// after\nconst key = Buffer.from(process.env.SECRET_KEY_B64 ?? '', 'base64');\nif (key.byteLength !== 32) throw new Error(`Key must be 32 bytes, got ${key.byteLength}`);\ncreateFactorySecretEncryption({ primary: { id: 'k1', key } });","handlingStrategy":"validation","validationCode":"import { randomBytes } from 'node:crypto';\nfunction decodeKey32(raw: string): Uint8Array {\n  const buf = /^[0-9a-fA-F]{64}$/.test(raw.trim()) ? Buffer.from(raw.trim(), 'hex')\n    : Buffer.from(raw.trim(), 'base64');\n  if (buf.byteLength !== 32) throw new Error(`Key must be 32 bytes, got ${buf.byteLength}`);\n  return buf;\n}\n// or generate: randomBytes(32)","typeGuard":"function isAes256Key(key: unknown): key is Uint8Array {\n  return key instanceof Uint8Array && key.byteLength === 32;\n}","tryCatchPattern":"try {\n  const enc = createFactorySecretEncryption({ primary: { id, key } });\n} catch (err) {\n  if (err instanceof Error && err.message.includes('must be exactly 32 bytes')) {\n    console.error(`Key '${id}' has wrong length — decode hex/base64 first or generate randomBytes(32)`);\n  } else throw err;\n}","preventionTips":["Always decode encoded keys (hex/base64) to bytes before passing them; never pass the encoded string's characters.","Generate new keys with crypto.randomBytes(32) and store them base64-encoded.","Trim whitespace/newlines from env-provided key values before decoding.","Include a length assertion (byteLength === 32) wherever keys are loaded from config or env."],"tags":["encryption","aes-256-gcm","key-length","configuration"],"backgroundTag":"invalid-encryption-key","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}