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
- Re-store the value using encrypt() so it is a complete 'prefix:salt:iv:authTag:encrypted' string.
- Check the storage column size/type and ensure the full ciphertext was persisted (no truncation).
- 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
- Use TEXT (unbounded) columns for ciphertext to avoid truncation.
- Round-trip test: decrypt(encrypt(v)) === v in CI.
- Back up storage before manual edits to encrypted records.
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
- Invalid ciphertext format
- Unsupported encryption algorithm: ${prefix}
- Google service account private key signing failed (${(err as
- Invalid ciphertext payload
- Expected a PEM-encoded public key or certificate string for
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/0193bf796cc01b57.
Report an issue: GitHub.