{"record":{"id":"de98bb5e6c356c96","repo":"mastra-ai/mastra","slug":"invalid-ciphertext-format","errorCode":null,"errorMessage":"Invalid ciphertext format","messagePattern":"Invalid ciphertext format","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"channels/slack/src/crypto.ts","lineNumber":81,"sourceCode":"export function encrypt(plaintext: string, key: string): string {\n  const salt = randomBytes(16);\n  const derived = Buffer.from(hkdfSync('sha256', key, salt, 'mastra-slack-encryption', 32));\n  const iv = randomBytes(12);\n  const cipher = createCipheriv('aes-256-gcm', derived, iv);\n\n  const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n  const authTag = cipher.getAuthTag();\n\n  return `${ALGO_PREFIX}:${salt.toString('base64')}:${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted.toString('base64')}`;\n}\n\n/**\n * Decrypt data produced by encrypt().\n */\nexport function decrypt(ciphertext: string, key: string): string {\n  const colonIdx = ciphertext.indexOf(':');\n  if (colonIdx === -1) {\n    throw new Error('Invalid ciphertext format');\n  }\n\n  const prefix = ciphertext.slice(0, colonIdx);\n  if (prefix !== ALGO_PREFIX) {\n    throw new Error(`Unsupported encryption algorithm: ${prefix}`);\n  }\n\n  const payload = ciphertext.slice(colonIdx + 1);\n  const [saltB64, ivB64, authTagB64, encryptedB64] = payload.split(':');\n  if (!saltB64 || !ivB64 || !authTagB64 || encryptedB64 === undefined) {\n    throw new Error('Invalid ciphertext payload');\n  }\n\n  const salt = Buffer.from(saltB64, 'base64');\n  const derived = Buffer.from(hkdfSync('sha256', key, salt, 'mastra-slack-encryption', 32));\n  const iv = Buffer.from(ivB64, 'base64');\n  const authTag = Buffer.from(authTagB64, 'base64');\n  const encrypted = Buffer.from(encryptedB64, 'base64');","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/channels/slack/src/crypto.ts#L63-L99","documentation":"decrypt() expects ciphertext in the format '<ALGO_PREFIX>:<...>' produced by encrypt(). The algorithm prefix is read before the first colon; if the string contains no colon at all it cannot be a valid encrypted payload, so the function throws immediately.","triggerScenarios":"Calling decrypt (directly or via #decryptPendingInstallation, #decryptInstallation, #decryptConfigTokens) with a value that has no ':' separator — e.g. a plaintext token stored unencrypted, an empty string, or a value written by a different/older storage format.","commonSituations":"Switching encryption on after plaintext values were already persisted; manually inserting tokens into the DB; restoring data encrypted with a different scheme; passing the raw key or wrong field as the ciphertext argument.","solutions":["Re-save the affected values through the library's encrypt path (or reinstall/reconnect the channel) so plaintext/stale values are replaced with properly encrypted ciphertext","Verify you are passing the ciphertext value, not the key or another field, to decrypt","Check for data written before encryption was enabled or by an older version, and migrate it (encrypt on read-and-replace)"],"exampleFix":"// before\nawait db.set('slack_tokens', JSON.stringify(tokens)); // plaintext, later decrypt fails\n// after\nawait db.set('slack_tokens', encrypt(JSON.stringify(tokens), key)); // '<ALGO_PREFIX>:...'","handlingStrategy":"validation","validationCode":"function looksEncrypted(v: string): boolean {\n  return typeof v === 'string' && v.includes(':') && /^[a-z0-9_-]+:/i.test(v);\n}\nconst stored = await storage.get('slack_config');\nif (stored && !looksEncrypted(stored)) await reEncrypt(stored); // migrate plaintext before decrypt is attempted","typeGuard":"function isCiphertext(v: unknown): v is string {\n  return typeof v === 'string' && v.indexOf(':') !== -1;\n}","tryCatchPattern":"try {\n  const plain = decrypt(cipher, key);\n} catch (e) {\n  if ((e as Error).message === 'Invalid ciphertext format') {\n    // value is plaintext or legacy — re-encrypt via encrypt() and persist, or reinstall channel\n  } else throw e;\n}","preventionTips":["Always write secrets through encrypt(); never insert plaintext tokens directly into storage","Migrate legacy plaintext rows (read, encrypt, write back) before enabling decryption paths","Ensure you pass the ciphertext, not the key, to decrypt()","Keep encryption settings (key, algorithm) stable across deployments"],"tags":["crypto","decryption","data-format"],"backgroundTag":"invalid-ciphertext-format","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}