{"record":{"id":"2a96fe6375f777f9","repo":"slopus/happy","slug":"unsupported-image-format","errorCode":null,"errorMessage":"Unsupported image format","messagePattern":"Unsupported image format","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/happy-server/sources/storage/processImage.ts","lineNumber":11,"sourceCode":"import { thumbhash } from \"./thumbhash\";\n\nexport async function processImage(src: Buffer) {\n    const sharp = (await import(\"sharp\")).default;\n\n    // Read image\n    let meta = await sharp(src).metadata();\n    let width = meta.width!;\n    let height = meta.height!;\n    if (meta.format !== 'png' && meta.format !== 'jpeg') {\n        throw new Error('Unsupported image format');\n    }\n\n    // Resize\n    let targetWidth = 100;\n    let targetHeight = 100;\n    if (width > height) {\n        targetHeight = Math.round(height * targetWidth / width);\n    } else if (height > width) {\n        targetWidth = Math.round(width * targetHeight / height);\n    }\n\n    // Resize image\n    const { data, info } = await sharp(src).resize(targetWidth, targetHeight).ensureAlpha().raw().toBuffer({ resolveWithObject: true });\n\n    // Thumbhash\n    const binaryThumbHash = thumbhash(info.width, info.height, data);\n    const thumbhashStr = Buffer.from(binaryThumbHash).toString('base64');\n","sourceCodeStart":1,"sourceCodeEnd":29,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-server/sources/storage/processImage.ts#L1-L29","documentation":"processImage uses sharp to read an image's metadata and only accepts PNG and JPEG formats. Any other format (webp, gif, heic, avif, svg, etc.) makes it throw 'Unsupported image format'.","triggerScenarios":"Calling processImage on a source image whose sharp-detected meta.format is neither 'png' nor 'jpeg' — e.g. a WebP screenshot, HEIC iPhone photo, or animated GIF passed to the thumbnailing pipeline.","commonSituations":"Users upload photos from mobile devices (HEIC), modern web exports (WebP/AVIF), or files with wrong extensions that don't match actual content.","solutions":["Convert the input image to JPEG or PNG before uploading/processing (e.g. sips -s format jpeg on macOS, ImageMagick convert)","Extend processImage to allow more formats or to convert via sharp(src).toFormat('jpeg') instead of throwing","Sanitize uploads server-side: reject or transcode non-png/jpeg formats early with a clear user-facing message"],"exampleFix":"// before\nif (meta.format !== 'png' && meta.format !== 'jpeg') {\n    throw new Error('Unsupported image format');\n}\n// after\nif (meta.format !== 'png' && meta.format !== 'jpeg') {\n    buf = await sharp(src).toFormat('jpeg').toBuffer();\n    meta = await sharp(buf).metadata();\n}","handlingStrategy":"validation","validationCode":"const meta = await sharp(src).metadata();\nif (meta.format !== 'png' && meta.format !== 'jpeg') {\n  throw new Error(`Unsupported image format: ${meta.format}; convert to png/jpeg first`);\n}","typeGuard":"function isSupportedImage(m: sharp.Metadata): boolean {\n  return m.format === 'png' || m.format === 'jpeg';\n}","tryCatchPattern":"try {\n  const out = await processImage(src, dest);\n} catch (e) {\n  if (e.message === 'Unsupported image format') {\n    await sharp(src).toFormat('jpeg').toFile(tempJpeg);\n    await processImage(tempJpeg, dest);\n  } else throw e;\n}","preventionTips":["Transcode uploads to JPEG/PNG at ingest time","Validate magic bytes, not just file extensions, on upload","Whitelist content types in your upload endpoint","Test the pipeline with HEIC/WebP/AVIF samples"],"tags":["image-processing","sharp","validation"],"backgroundTag":"unsupported-image-format","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}