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.
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)
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
- Rule metadata must contain finite numbers.
- Rule metadata is not bounded JSON.
- Rule metadata must not contain cycles.
- Rule metadata contains too many entries.
- Rule metadata must use plain objects.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/eaf76d0e45c69fde.
Report an issue: GitHub.