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

  1. Re-encrypt the value with the same library version's encrypt() so it produces a fresh valid envelope; the corrupted value cannot be repaired.
  2. 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.
  3. Check for transport/storage corruption: compare the stored string byte-for-byte with what encrypt() returned (length, truncation, whitespace trimming).
  4. 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

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


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)