mastra-ai/mastra · error
Invalid ciphertext format
Error message
Invalid ciphertext format
What it means
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.
Source
Thrown at channels/slack/src/crypto.ts:81
export function encrypt(plaintext: string, key: string): string {
const salt = randomBytes(16);
const derived = Buffer.from(hkdfSync('sha256', key, salt, 'mastra-slack-encryption', 32));
const iv = randomBytes(12);
const cipher = createCipheriv('aes-256-gcm', derived, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
const authTag = cipher.getAuthTag();
return `${ALGO_PREFIX}:${salt.toString('base64')}:${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted.toString('base64')}`;
}
/**
* 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');View on GitHub (pinned to 75dd419e61)
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)
Example fix
// before
await db.set('slack_tokens', JSON.stringify(tokens)); // plaintext, later decrypt fails
// after
await db.set('slack_tokens', encrypt(JSON.stringify(tokens), key)); // '<ALGO_PREFIX>:...' Defensive patterns
Strategy: validation
Validate before calling
function looksEncrypted(v: string): boolean {
return typeof v === 'string' && v.includes(':') && /^[a-z0-9_-]+:/i.test(v);
}
const stored = await storage.get('slack_config');
if (stored && !looksEncrypted(stored)) await reEncrypt(stored); // migrate plaintext before decrypt is attempted Type guard
function isCiphertext(v: unknown): v is string {
return typeof v === 'string' && v.indexOf(':') !== -1;
} Try / catch
try {
const plain = decrypt(cipher, key);
} catch (e) {
if ((e as Error).message === 'Invalid ciphertext format') {
// value is plaintext or legacy — re-encrypt via encrypt() and persist, or reinstall channel
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Unsupported encryption algorithm: ${prefix}
- Invalid ciphertext payload
- Google service account private key signing failed (${(err as
- Expected a PEM-encoded public key or certificate string for
- OpenAI Codex OAuth is only available in Node.js environments
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/de98bb5e6c356c96.
Report an issue: GitHub.