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
- Re-encode the image bytes with standard base64 (Buffer.from(bytes).toString('base64')) and no whitespace.
- Strip newlines/whitespace and fix '=' padding to make length a multiple of 4.
- 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
- Use standard base64 (not base64url) when building data URLs.
- Strip whitespace/newlines from base64 strings before embedding.
- Re-encode from raw bytes rather than copying base64 out of logs.
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
- bad request: ${responseText}
- [FactorySecretEncryption] Invalid encrypted value.
- Unable to persist request context key "${key}": the value is
- Invalid agent-builder action: ${actionId}. Valid actions are
- Invalid agent-builder action: ${actionId}
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/f05c861528756bbf.
Report an issue: GitHub.