santifer/career-ops · error · Error

Unsupported profile photo data URL (expected base64 PNG, JPE

Error message

Unsupported profile photo data URL (expected base64 PNG, JPEG, WebP, or GIF)

What it means

Validation in `prepareCandidatePhoto`: when `photo` starts with `data:` it must fully match `IMAGE_DATA_URL_RE` = `/^data:image\/(?:png|jpeg|webp|gif);base64,[a-z0-9+\/=\s]+$/i`. This requires the `image/<mime>` subtype, explicit `;base64`, and valid base64 body. Malformed data URLs are rejected to avoid embedding broken/unsafe content in the CV HTML.

Source

Thrown at build-cv-html.mjs:124

}

async function prepareCandidatePhoto(candidate) {
  const c = candidate && typeof candidate === 'object' ? { ...candidate } : {};
  const photo = typeof c.photo === 'string' ? c.photo.trim() : '';
  const style = c.photo_style || c.photoStyle || 'rounded';

  if (!PHOTO_STYLES.has(style)) {
    throw new Error(`Unsupported profile photo style: ${style} (expected rounded, circle, or square)`);
  }
  c.photo_style = style;
  if (!photo) {
    c.photo = '';
    return c;
  }

  if (photo.startsWith('data:')) {
    if (!IMAGE_DATA_URL_RE.test(photo)) {
      throw new Error('Unsupported profile photo data URL (expected base64 PNG, JPEG, WebP, or GIF)');
    }
    c.photo = photo;
    return c;
  }

  if (/^https?:\/\//i.test(photo)) {
    c.photo = photo;
    return c;
  }

  if (/^[a-z][a-z0-9+.-]+:/i.test(photo)) {
    throw new Error(`Unsupported profile photo URL scheme: ${photo.split(':', 1)[0]}`);
  }

  const photoPath = isAbsolute(photo) ? photo : resolve(__dirname, photo);
  const mime = PHOTO_MIME_BY_EXT.get(extname(photoPath).toLowerCase());
  if (!mime) {
    throw new Error(`Unsupported profile photo format: ${photo} (expected PNG, JPEG, WebP, or GIF)`);

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Use one of the four allowed mimes: `image/png`, `image/jpeg`, `image/webp`, `image/gif`, with `;base64,` and a valid base64 body.
  2. For SVG, rasterize first (PNG/WEBP) or extend `IMAGE_DATA_URL_RE` and add the mime intentionally.
  3. Use `image/jpeg` (not `image/jpg`).
  4. Strip any internal whitespace/newlines from the base64 body before embedding, or rely on the regex's tolerated `\s`.
  5. If you have a raw file path instead, pass the path (the function resolves and embeds it).

Example fix

// before
photo: 'data:image/svg+xml;base64,PHN2Zz4=' // throws
// after — rasterize the SVG to PNG first, then
photo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUg==...'
Defensive patterns

Strategy: validation

Validate before calling

const RE = /^data:image\/(?:png|jpeg|webp|gif);base64,[a-z0-9+\/=\s]+$/i;
function validDataUrl(u) {
  if (!String(u).startsWith('data:')) return true; // not a data url — other path
  if (!RE.test(u)) throw new Error('Use data:image/(png|jpeg|webp|gif);base64,<body>');
  return u;
}

Type guard

function isAcceptableDataUrl(u) {
  return /^data:image\/(?:png|jpeg|webp|gif);base64,[a-z0-9+\/=\s]+$/i.test(String(u));
}

Prevention

When it happens

Trigger: Passing `data:image/svg+xml;base64,...` (svg not allowed), `data:image/png;base64,` (empty body), a data URL with `;charset=` or other params interleaved, missing `;base64`, a URL-encoded (`%2F`) data URL, or a truncated/corrupted base64 string with invalid chars.

Common situations: User uploads SVG (common for logos/avatars); tool emits a URL-encoded data URL; base64 string accidentally truncated in transit; mime labels like `image/jpg` instead of `image/jpeg`; data URL with extra whitespace/newlines outside the allowed set.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/b8329af0d8b69168. Report an issue: GitHub.