mastra-ai/mastra · error · HTTPException

metadata.avatarUrl contains invalid base64

Error message

metadata.avatarUrl contains invalid base64

What it means

HTTP 400 from validateMetadataAvatarUrl: the base64 payload inside the data URL must be strict base64 — non-zero length, multiple of 4 characters, valid charset, and correct padding. Buffer.from is lenient, so the server validates explicitly and rejects sloppy encodings.

Source

Thrown at packages/server/src/server/handlers/validate-avatar.ts:34

  const dataUrl = metadata.avatarUrl;
  const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
  if (!match) {
    throw new HTTPException(400, {
      message: 'metadata.avatarUrl must be a valid data URL (data:<mime>;base64,<data>)',
    });
  }

  // `Buffer.from(..., 'base64')` decodes leniently — it silently ignores
  // invalid characters and never throws. Validate the payload format strictly
  // before measuring its byte length so malformed input is rejected.
  const base64Payload = match[2]!;
  const isStrictBase64 =
    base64Payload.length > 0 &&
    base64Payload.length % 4 === 0 &&
    /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(base64Payload);
  if (!isStrictBase64) {
    throw new HTTPException(400, { message: 'metadata.avatarUrl contains invalid base64' });
  }
  const byteLength = Buffer.from(base64Payload, 'base64').byteLength;

  if (byteLength === 0) {
    throw new HTTPException(400, { message: 'metadata.avatarUrl is empty' });
  }

  if (byteLength > AVATAR_MAX_BYTES) {
    throw new HTTPException(413, {
      message: `metadata.avatarUrl exceeds ${AVATAR_MAX_BYTES}-byte limit (got ${byteLength})`,
    });
  }
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Re-encode the image bytes with standard base64 (Buffer.from(bytes).toString('base64')) and no whitespace.
  2. Strip newlines/whitespace and fix '=' padding to make length a multiple of 4.
  3. Replace URL-safe base64 (-, _) with standard characters (+, /).

Example fix

// before
const avatarUrl = 'data:image/png;base64,' + btoa(bytes).replace(/\+/g, '-').replace(/\//g, '_');
// after
const avatarUrl = 'data:image/png;base64,' + Buffer.from(bytes).toString('base64');
Defensive patterns

Strategy: validation

Validate before calling

const B64_RE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
const payload = metadata.avatarUrl?.split(',')[1] ?? '';
if (!payload.length || payload.length % 4 !== 0 || !B64_RE.test(payload)) {
  throw new Error('avatarUrl base64 payload is invalid');
}

Type guard

function isStrictBase64(s: string): boolean {
  return s.length > 0 && s.length % 4 === 0 && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(s);
}

Try / catch

try {
  await saveAgent({ metadata });
} catch (e) {
  if (e instanceof HTTPException && e.status === 400 && e.message.includes('invalid base64')) {
    metadata.avatarUrl = reEncodeStandardBase64(metadata.rawAvatarBytes);
    await saveAgent({ metadata });
  } else throw e;
}

Prevention

When it happens

Trigger: Truncated base64 strings, strings with whitespace/newlines or URL-safe characters (-, _), payloads with wrong '=' padding, or empty payloads after the mime segment.

Common situations: Copy-paste from logs introducing line breaks; using base64url encoding from JWT-style tooling; truncating very large payloads; hand-concatenating data URLs.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/f05c861528756bbf. Report an issue: GitHub.