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

  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.