{"record":{"id":"250ec60ded3e9688","repo":"mastra-ai/mastra","slug":"invalid-ciphertext-payload-250ec6","errorCode":null,"errorMessage":"Invalid ciphertext payload","messagePattern":"Invalid ciphertext payload","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"channels/telegram/src/crypto.ts","lineNumber":40,"sourceCode":"  return value.startsWith(`${ALGO_PREFIX}:`);\n}\n\n/** Encrypt a UTF-8 string with a per-value random salt + IV. */\nexport function encrypt(plaintext: string, passphrase: string): string {\n  const salt = randomBytes(16);\n  const iv = randomBytes(12);\n  const cipher = createCipheriv('aes-256-gcm', deriveKey(passphrase, salt), iv);\n  const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n  const tag = cipher.getAuthTag();\n  return `${ALGO_PREFIX}:${salt.toString('base64')}:${iv.toString('base64')}:${tag.toString('base64')}:${enc.toString('base64')}`;\n}\n\n/** Decrypt a value from {@link encrypt}. Plaintext (unprefixed) is returned unchanged. */\nexport function decrypt(value: string, passphrase: string): string {\n  if (!isEncrypted(value)) return value;\n  const [, saltB64, ivB64, tagB64, ctB64] = value.split(':');\n  if (!saltB64 || !ivB64 || !tagB64 || ctB64 === undefined) {\n    throw new Error('Invalid ciphertext payload');\n  }\n  const decipher = createDecipheriv(\n    'aes-256-gcm',\n    deriveKey(passphrase, Buffer.from(saltB64, 'base64')),\n    Buffer.from(ivB64, 'base64'),\n  );\n  decipher.setAuthTag(Buffer.from(tagB64, 'base64'));\n  return Buffer.concat([decipher.update(Buffer.from(ctB64, 'base64')), decipher.final()]).toString('utf8');\n}\n","sourceCodeStart":22,"sourceCodeEnd":50,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/channels/telegram/src/crypto.ts#L22-L50","documentation":"decrypt() in channels/telegram recognizes an encrypted value by its 'salt:iv:tag:ciphertext' base64 prefix format. If the value carries the encrypted marker but splitting yields empty salt/iv/tag or missing ciphertext, the payload is structurally corrupt and cannot be AES-256-GCM decrypted, so the function throws. Note plaintext (unprefixed) values pass through unchanged by design.","triggerScenarios":"Calling decrypt() (indirectly via TelegramInstallationStore #dec) on a stored secret that is prefixed as encrypted but has truncated/malformed segments — e.g. a value truncated by a fixed-length DB column, hand-edited, or encrypted by a different/incompatible format version.","commonSituations":"DB migration truncating the stored ciphertext; someone pasting a partially copied encrypted string into config; mixing outputs from different encryption implementations that share the prefix but differ in layout.","solutions":["Re-encrypt and store the secret: delete the corrupt record and re-save the bot token (or re-run the install flow) so a fresh valid ciphertext is written.","Verify the stored string has exactly the format enc:salt:iv:tag:ciphertext with valid base64 segments and no truncation.","Widen the storage column (e.g. TEXT) if a fixed-length column truncated the value.","Confirm the value was produced by this library's encrypt() and not another tool with a similar prefix."],"exampleFix":"// before (truncated value in DB)\nawait store.save({ botToken: 'v1:YWJj' }); // corrupt\n// after\nconst botToken = encrypt(realToken, encryptionKey); // full enc:salt:iv:tag:ct\nawait store.save({ botToken });","handlingStrategy":"validation","validationCode":"function looksLikeCorruptCiphertext(v: string): boolean {\n  if (!v.startsWith('enc:')) return false; // adjust to actual prefix\n  const parts = v.split(':');\n  return parts.length < 5 || parts.slice(1, 4).some(p => !p) || parts[4] === undefined;\n}","typeGuard":null,"tryCatchPattern":"try {\n  const token = store.getBotToken(recordId);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Invalid ciphertext payload') {\n    await reEncryptAndStore(recordId); // prompt re-install / re-save secret\n  } else throw e;\n}","preventionTips":["Store encrypted secrets in a TEXT/bytea column sized for the full ciphertext.","Never hand-edit or partially copy encrypted values.","Re-encrypt records when upgrading encryption formats or libraries."],"tags":["telegram","encryption","data-corruption"],"backgroundTag":"invalid-ciphertext","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}