{"record":{"id":"a59aca3c896a4b32","repo":"santifer/career-ops","slug":"unsupported-profile-photo-url-scheme-photo-spli","errorCode":null,"errorMessage":"Unsupported profile photo URL scheme: ${photo.split(':', 1)[0]}","messagePattern":"Unsupported profile photo URL scheme: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"build-cv-html.mjs","lineNumber":136,"sourceCode":"    c.photo = '';\n    return c;\n  }\n\n  if (photo.startsWith('data:')) {\n    if (!IMAGE_DATA_URL_RE.test(photo)) {\n      throw new Error('Unsupported profile photo data URL (expected base64 PNG, JPEG, WebP, or GIF)');\n    }\n    c.photo = photo;\n    return c;\n  }\n\n  if (/^https?:\\/\\//i.test(photo)) {\n    c.photo = photo;\n    return c;\n  }\n\n  if (/^[a-z][a-z0-9+.-]+:/i.test(photo)) {\n    throw new Error(`Unsupported profile photo URL scheme: ${photo.split(':', 1)[0]}`);\n  }\n\n  const photoPath = isAbsolute(photo) ? photo : resolve(__dirname, photo);\n  const mime = PHOTO_MIME_BY_EXT.get(extname(photoPath).toLowerCase());\n  if (!mime) {\n    throw new Error(`Unsupported profile photo format: ${photo} (expected PNG, JPEG, WebP, or GIF)`);\n  }\n\n  let bytes;\n  try {\n    bytes = await readFile(photoPath);\n  } catch (err) {\n    throw new Error(`Profile photo not found or unreadable: ${photo} (${err.code || err.message})`);\n  }\n  if (bytes.length === 0) {\n    throw new Error(`Profile photo is empty: ${photo}`);\n  }\n  c.photo = `data:${mime};base64,${bytes.toString('base64')}`;","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/build-cv-html.mjs#L118-L154","documentation":"Validation in `prepareCandidatePhoto`: when `photo` has an `other-scheme:` prefix (matches `/^[a-z][a-z0-9+.-]+:/i` but is not `http:`/`https:`, and is not a `data:` URL), the scheme is unsupported and the function throws with the extracted scheme name. Only `http(s)` remote URLs and local file paths are accepted beyond `data:`.","triggerScenarios":"Passing `photo: 'ftp://example.com/me.png'`, `'file:///Users/me/photo.png'`, `'ipfs://Qm...'`, `'blob:https://...'`, `'javascript:...'`, or any non-http URL-prefixed string.","commonSituations":"Browser `blob:` URL captured from a webcam UI; `file://` from a desktop uploader; IPFS/decentralized storage URL; copy-pasting an FTP link; security-sensitive schemes like `javascript:` (rejected for safety, not just style).","solutions":["Use an `http://` or `https://` URL for remote photos, or a local filesystem path (absolute or relative to the script) for local files.","For `blob:` URLs, fetch the bytes client-side and pass the resulting `data:` URL or save to a file first.","For `file://` paths, strip the scheme and pass the raw path instead.","For IPFS/other protocols, resolve to an https gateway URL or download the file locally.","Never pass user-controlled `javascript:` or unknown schemes — the rejection is a safety feature."],"exampleFix":"// before\nphoto: 'file:///home/me/avatar.png' // throws 'Unsupported profile photo URL scheme: file'\n// after\nphoto: '/home/me/avatar.png' // raw absolute path — function resolves and embeds","handlingStrategy":"validation","validationCode":"function normalizePhotoSource(p) {\n  const s = String(p ?? '').trim();\n  if (/^data:/i.test(s) || /^https?:\\/\\//i.test(s) || !/^[a-z][a-z0-9+.-]*:/i.test(s)) return s;\n  throw new Error(`Unsupported photo URL scheme: ${s.split(':', 1)[0]}. Use http(s) or a file path.`);\n}","typeGuard":"function isAcceptablePhotoSource(p) {\n  const s = String(p ?? '').trim();\n  if (/^data:/i.test(s) || /^https?:\\/\\//i.test(s)) return true;\n  // local path: no scheme at all\n  return !/^[a-z][a-z0-9+.-]*:/i.test(s);\n}","tryCatchPattern":null,"preventionTips":["Use http(s) URLs for remote photos or raw filesystem paths for local ones.","Resolve blob:/file:// URLs to bytes (data URL or saved file) upstream.","Treat rejection of javascript:/unknown schemes as a safety feature, never bypass it.","Test that ftp:, file:, blob: are rejected."],"tags":["validation","photo","url-scheme","build-cv-html","security"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}