mastra-ai/mastra · error

Invalid ciphertext payload

Error message

Invalid ciphertext payload

What it means

After the algorithm prefix matches, decrypt() splits the remainder on ':' and requires exactly the four base64 segments salt, iv, authTag and encrypted data. If any of the first three is empty or the encrypted segment is missing, the ciphertext is malformed and cannot be decrypted.

Source

Thrown at channels/slack/src/crypto.ts:92

/**
 * Decrypt data produced by encrypt().
 */
export function decrypt(ciphertext: string, key: string): string {
  const colonIdx = ciphertext.indexOf(':');
  if (colonIdx === -1) {
    throw new Error('Invalid ciphertext format');
  }

  const prefix = ciphertext.slice(0, colonIdx);
  if (prefix !== ALGO_PREFIX) {
    throw new Error(`Unsupported encryption algorithm: ${prefix}`);
  }

  const payload = ciphertext.slice(colonIdx + 1);
  const [saltB64, ivB64, authTagB64, encryptedB64] = payload.split(':');
  if (!saltB64 || !ivB64 || !authTagB64 || encryptedB64 === undefined) {
    throw new Error('Invalid ciphertext payload');
  }

  const salt = Buffer.from(saltB64, 'base64');
  const derived = Buffer.from(hkdfSync('sha256', key, salt, 'mastra-slack-encryption', 32));
  const iv = Buffer.from(ivB64, 'base64');
  const authTag = Buffer.from(authTagB64, 'base64');
  const encrypted = Buffer.from(encryptedB64, 'base64');

  const decipher = createDecipheriv('aes-256-gcm', derived, iv);
  decipher.setAuthTag(authTag);
  return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Re-store the value using encrypt() so it is a complete 'prefix:salt:iv:authTag:encrypted' string.
  2. Check the storage column size/type and ensure the full ciphertext was persisted (no truncation).
  3. Delete the corrupt record and re-run the Slack connect/installation flow to regenerate it.

Example fix

// before (truncated column)
CREATE TABLE installs (bot_token TEXT(50));
// after
CREATE TABLE installs (bot_token TEXT);
Defensive patterns

Strategy: validation

Validate before calling

function isWellFormedCiphertext(v) {
  if (typeof v !== 'string') return false;
  const parts = v.split(':');
  return parts.length === 5 && parts.slice(1, 4).every(Boolean) && parts[4] !== undefined;
}
if (!isWellFormedCiphertext(stored)) throw new Error('stored ciphertext is truncated or malformed');

Type guard

function hasFiveSegments(v: unknown): v is string {
  return typeof v === 'string' && v.split(':').length === 5;
}

Try / catch

try {
  const token = await decrypt(stored);
} catch (err) {
  if (err instanceof Error && err.message === 'Invalid ciphertext payload') {
    logger.error('corrupt ciphertext in storage, re-running installation');
    await reRunSlackInstallation(agentId);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling decrypt (via #decryptPendingInstallation, #decryptInstallation, #decryptConfigTokens) with a string like 'prefix:' (empty payload), 'prefix:salt:iv' (truncated), or otherwise corrupted/truncated ciphertext missing one of the four ':'-separated base64 parts.

Common situations: Database column truncation cutting off the tail of the value; manual edits to stored records; copy/paste dropping characters; a value stored by an incompatible writer.

Related errors


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