mastra-ai/mastra · error
[FactorySecretEncryption] Invalid encrypted value.
Error message
[FactorySecretEncryption] Invalid encrypted value.
What it means
parseEnvelope decodes the base64url payload after the envelope prefix and JSON-parses it. This error is thrown when the value either is not valid base64url, or does not decode into a UTF-8 string that is valid JSON. The library throws it to fail fast on encrypted values that are structurally unreadable before any key lookup or decryption is attempted.
Solutions
- Re-encrypt the value with the same library version's encrypt() so it produces a fresh valid envelope; the corrupted value cannot be repaired.
- Verify the stored value retains the exact envelope prefix and only base64url characters (A-Z a-z 0-9 - _), re-encoding with Buffer.from(x).toString('base64url') if it was stored as standard base64.
- Check for transport/storage corruption: compare the stored string byte-for-byte with what encrypt() returned (length, truncation, whitespace trimming).
- Wrap decrypt() in try/catch and fall back to treating the value as plaintext (e.g. needsReencryption flow) if the value may predate encryption.
Example fix
// before (standard base64 stored value passed as-is)
decrypt(valueFromDb);
// after (normalize encoding before decrypt)
const normalized = Buffer.from(valueFromDb, 'base64').toString('base64url');
await encryption.decrypt(normalized); Defensive patterns
Strategy: validation
Validate before calling
const PREFIX = 'enc:v1:'; // match ENVELOPE_PREFIX
function looksEncrypted(value: unknown): value is string {
if (typeof value !== 'string' || !value.startsWith(PREFIX)) return false;
const rest = value.slice(PREFIX.length);
return /^[A-Za-z0-9_-]+$/.test(rest) && rest.length > 0;
} Type guard
function isBase64urlJson(value: string): boolean {
try {
JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
return true;
} catch {
return false;
}
} Try / catch
try {
const secret = await encryption.decrypt(stored);
} catch (err) {
if (err instanceof Error && err.message.includes('Invalid encrypted value')) {
// treat as legacy plaintext and re-encrypt, or surface a data-corruption report
} else throw err;
} Prevention
- Always store the exact string returned by encrypt() — never re-encode it as standard base64 or trim it.
- Add a startup check that decrypts a known canary value to verify storage round-trips base64url untouched.
- Validate encrypted columns/fields are stored with a text type large enough and no transformers that alter content.
- Keep plaintext values out of fields designated for envelopes; use the explicit plaintext compatibility path instead.
When it happens
Trigger: Calling decrypt() (via envelope) with a string that carries the ENVELOPE_PREFIX but whose remainder is not valid base64url (e.g. contains '+', '/', or '=' from a different base64 encoding), or decodes to non-JSON text such as a raw secret, truncated value, or swapped-in plaintext.
Common situations: Values mangled by copy/paste or URL encoding, secrets stored with standard base64 instead of base64url, a value truncated by a column size limit or log sanitization, or someone pasting a plaintext/other-format value where an encrypted envelope was expected.
Related errors
- JSON chunking requires maxSize to be specified
- metadata.avatarUrl contains invalid base64
- must be JSON-safe.
- must contain only finite numbers.
- must not contain cycles.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/eaf76d0e45c69fde.
Report an issue: GitHub.
Appendix: source
Thrown at mastracode/factory/src/secret-encryption.ts:49
primary: FactorySecretEncryptionKey;
previous?: FactorySecretEncryptionKey[];
}
function validateKey({ id, key }: FactorySecretEncryptionKey): Buffer {
if (!id) throw new Error('[FactorySecretEncryption] Key id is required.');
const buffer = Buffer.from(key);
if (buffer.byteLength !== 32) {
throw new Error(`[FactorySecretEncryption] Key "${id}" must be exactly 32 bytes.`);
}
return buffer;
}
function parseEnvelope(value: string): SecretEnvelopeV1 {
let parsed: unknown;
try {
parsed = JSON.parse(Buffer.from(value.slice(ENVELOPE_PREFIX.length), 'base64url').toString('utf8'));
} catch {
throw new Error('[FactorySecretEncryption] Invalid encrypted value.');
}
if (
!parsed ||
typeof parsed !== 'object' ||
typeof (parsed as SecretEnvelopeV1).keyId !== 'string' ||
typeof (parsed as SecretEnvelopeV1).iv !== 'string' ||
typeof (parsed as SecretEnvelopeV1).ciphertext !== 'string' ||
typeof (parsed as SecretEnvelopeV1).tag !== 'string'
) {
throw new Error('[FactorySecretEncryption] Invalid encrypted value.');
}
return parsed as SecretEnvelopeV1;
}
/**
* Creates a versioned AES-256-GCM encryptor. The primary key is used for new
* writes; previous keys remain decrypt-only until stored values are rotated.View on GitHub (pinned to 75dd419e61)