mastra-ai/mastra · error

Invalid ciphertext payload

Error message

Invalid ciphertext payload

What it means

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.

Source

Thrown at channels/telegram/src/crypto.ts:40

  return value.startsWith(`${ALGO_PREFIX}:`);
}

/** Encrypt a UTF-8 string with a per-value random salt + IV. */
export function encrypt(plaintext: string, passphrase: string): string {
  const salt = randomBytes(16);
  const iv = randomBytes(12);
  const cipher = createCipheriv('aes-256-gcm', deriveKey(passphrase, salt), iv);
  const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  return `${ALGO_PREFIX}:${salt.toString('base64')}:${iv.toString('base64')}:${tag.toString('base64')}:${enc.toString('base64')}`;
}

/** Decrypt a value from {@link encrypt}. Plaintext (unprefixed) is returned unchanged. */
export function decrypt(value: string, passphrase: string): string {
  if (!isEncrypted(value)) return value;
  const [, saltB64, ivB64, tagB64, ctB64] = value.split(':');
  if (!saltB64 || !ivB64 || !tagB64 || ctB64 === undefined) {
    throw new Error('Invalid ciphertext payload');
  }
  const decipher = createDecipheriv(
    'aes-256-gcm',
    deriveKey(passphrase, Buffer.from(saltB64, 'base64')),
    Buffer.from(ivB64, 'base64'),
  );
  decipher.setAuthTag(Buffer.from(tagB64, 'base64'));
  return Buffer.concat([decipher.update(Buffer.from(ctB64, 'base64')), decipher.final()]).toString('utf8');
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. 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.
  2. Verify the stored string has exactly the format enc:salt:iv:tag:ciphertext with valid base64 segments and no truncation.
  3. Widen the storage column (e.g. TEXT) if a fixed-length column truncated the value.
  4. Confirm the value was produced by this library's encrypt() and not another tool with a similar prefix.

Example fix

// before (truncated value in DB)
await store.save({ botToken: 'v1:YWJj' }); // corrupt
// after
const botToken = encrypt(realToken, encryptionKey); // full enc:salt:iv:tag:ct
await store.save({ botToken });
Defensive patterns

Strategy: validation

Validate before calling

function looksLikeCorruptCiphertext(v: string): boolean {
  if (!v.startsWith('enc:')) return false; // adjust to actual prefix
  const parts = v.split(':');
  return parts.length < 5 || parts.slice(1, 4).some(p => !p) || parts[4] === undefined;
}

Try / catch

try {
  const token = store.getBotToken(recordId);
} catch (e) {
  if (e instanceof Error && e.message === 'Invalid ciphertext payload') {
    await reEncryptAndStore(recordId); // prompt re-install / re-save secret
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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