{"record":{"id":"4be5e6d29cac1d60","repo":"paperclipai/paperclip","slug":"heif-decoded-image-exceeds-the-pixel-limit","errorCode":null,"errorMessage":"HEIF decoded image exceeds the pixel limit","messagePattern":"HEIF decoded image exceeds the pixel limit","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/photon/media.ts","lineNumber":56,"sourceCode":"        throw new Error(\"HEIF box exceeds file bounds\");\n      const content = at + header;\n      if (type === \"ftyp\") {\n        if (size < header + 8) throw new Error(\"HEIF file type is missing\");\n        const brands = body.toString(\"ascii\", content, at + size);\n        branded = /heic|heix|hevc|hevx|mif1|msf1/.test(brands);\n      } else if (type === \"ispe\") {\n        if (size !== header + 12)\n          throw new Error(\"Invalid HEIF image dimensions\");\n        const width = body.readUInt32BE(content + 4);\n        const height = body.readUInt32BE(content + 8);\n        if (\n          !width ||\n          !height ||\n          width > 16_384 ||\n          height > 16_384 ||\n          width * height > MAX_PIXELS\n        )\n          throw new Error(\"HEIF decoded image exceeds the pixel limit\");\n        totalPixels += width * height;\n        if (totalPixels > MAX_PIXELS * 3 || ++dimensions > 512)\n          throw new Error(\"HEIF image collection exceeds the pixel limit\");\n      } else if ([\"meta\", \"iprp\", \"ipco\"].includes(type)) {\n        visit(content + (type === \"meta\" ? 4 : 0), at + size, depth + 1);\n      }\n      at += size;\n    }\n  };\n  if (!body.length || body.length > MAX_ATTACHMENT_BYTES)\n    throw new Error(\"HEIF exceeds the attachment byte limit\");\n  visit(0, body.length, 0);\n  if (!branded || !dimensions)\n    throw new Error(\"HEIF dimensions could not be verified\");\n}\n\nexport async function validatePhotonImage(\n  body: Buffer,","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/photon/media.ts#L38-L74","documentation":"validateHeifDimensions walks the ISO-BMFF box structure of a HEIF file and reads each ispe (image spatial extents) box to get declared width/height before any native decoder runs. It throws this error when a single ispe box declares a zero dimension, a dimension above 16,384, or width*height above MAX_PIXELS (50,000,000). This is a decompression-bomb guard preventing huge allocations in the native HEIF converter.","triggerScenarios":"Calling validatePhotonImage or photonHeifPreview with a body whose content type is image/heic, image/heif, image/heic-sequence, or image/heif-sequence, where any ispe box declares width or height of 0 or >16384, or a single image over 50MP.","commonSituations":"Users upload high-resolution iPhone panorama/ProRAW HEIC exports (e.g. 48MP+ photos) or mislabeled files whose ispe metadata exceeds limits; also crafted bomb files attempting decoder DoS.","solutions":["Resize or re-export the image to at most 16,384x16,384 and under 50 megapixels (e.g. sips -Z 16384 or convert to JPEG) before uploading.","If legitimate images are being rejected and the product allows it, raise MAX_PIXELS or the 16,384 per-dimension cap in server/src/services/photon/media.ts.","Catch this specific Error message in ingestAttachments and reject the attachment with a clear user-facing 'image too large' error instead of a 500.","Pre-probe client-side: read the HEIC's ispe/metadata in the browser or client and warn before upload."],"exampleFix":"// before\nconst jpeg = await heifToJpeg(body, {quality:80});\n// after\nconst metadata = await sharp(body, {limitInputPixels: MAX_PIXELS}).metadata();\nif ((metadata.width ?? 0) * (metadata.height ?? 0) > MAX_PIXELS)\n  throw new Error(\"HEIF decoded image exceeds the pixel limit\");\nconst jpeg = await heifToJpeg(body, {quality:80});","handlingStrategy":"validation","validationCode":"import sharp from 'sharp';\nexport async function isHeifWithinPixelLimit(body: Buffer): Promise<boolean> {\n  if (!body.length) return false;\n  const meta = await sharp(body, { limitInputPixels: 50_000_000 }).metadata().catch(() => null);\n  if (!meta) return false; // HEIF: parse ispe boxes or pre-convert\n  const w = meta.width ?? 0, h = meta.height ?? 0;\n  return w > 0 && h > 0 && w <= 16_384 && h <= 16_384 && w * h <= 50_000_000;\n}","typeGuard":"function hasSafeHeifDimensions(d: { width?: number; height?: number }): boolean {\n  return typeof d.width === 'number' && typeof d.height === 'number' &&\n    d.width > 0 && d.height > 0 &&\n    d.width <= 16_384 && d.height <= 16_384 &&\n    d.width * d.height <= 50_000_000;\n}","tryCatchPattern":"try {\n  await validatePhotonImage(body, contentType);\n} catch (err) {\n  if (err instanceof Error && err.message === 'HEIF decoded image exceeds the pixel limit') {\n    return respond(413, 'image resolution exceeds the 50MP limit');\n  }\n  throw err;\n}","preventionTips":["Resize images client-side to <=16,384px per side before upload","Enforce a pixel-count check in the browser using an ImageDecoder or pre-converted preview","Warn users when ProRAW/panorama photos exceed 50MP","Keep MAX_PIXELS limits aligned between client preview and server validation"],"tags":["image","heif","validation","file-size-limit"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}