paperclipai/paperclip · error

HEIF metadata nesting is too deep

Error message

HEIF metadata nesting is too deep

What it means

validateHeifDimensions parses the ISO-BMFF box tree of a HEIF/HEIC buffer with a bounded recursive visit() before any native decoder runs. If recursion depth exceeds 8 (meta/iprp/ipco containers nested too deeply) it throws "HEIF metadata nesting is too deep". This is a hardening limit against maliciously crafted deeply nested files that could exhaust the parser or decoder.

Solutions

  1. Reject the upload — this limit is intentional; re-encode the image with standard tooling (e.g. sips, ImageMagick, libheif) to flatten metadata.
  2. Strip metadata from the HEIF before re-submitting (exiftool -all= or heif-convert/re-encode).
  3. Verify the file opens in a standard viewer; if not, the file is likely corrupt or malicious — do not process.
  4. If legitimate depth is truly required, raise the depth limit in media.ts with a code change and fresh security review.

Example fix

// shell re-encode before upload
// before: file.heic with 10-level nested meta boxes
// after: magick file.heic -strip flattened.heic
Defensive patterns

Strategy: validation

Validate before calling

// heuristic pre-check: reject implausibly large HEIF metadata before server-side validation
function plausibleHeifSize(buf: Buffer): boolean {
  return buf.length > 0 && buf.length <= MAX_ATTACHMENT_BYTES && buf.toString("ascii", 4, 8) === "ftyp";
}

Type guard

function looksLikeHeif(b: Buffer): b is Buffer {
  return b.length >= 12 && b.toString("ascii", 4, 8) === "ftyp";
}

Try / catch

try {
  validateHeifDimensions(body);
} catch (e) {
  if (e instanceof Error && e.message === "HEIF metadata nesting is too deep") {
    return rejectUpload("HEIF file has unsafe metadata nesting; re-encode the image");
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling validateHeifDimensions (directly or via validatePhotonImage/photonHeifPreview) on a HEIF buffer whose meta/iprp/ipco box nesting is deeper than 8 levels — interactions.ts-independent, media.ts:22.

Common situations: Uploading a crafted/fuzzed HEIC file; unusual but legitimate deeply nested HEIF metadata from exotic camera tooling; a file mislabeled as image/heic that happens to nest boxes deeply.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/8602d6b6d96f71f1. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/photon/media.ts:22

import { MAX_ATTACHMENT_BYTES } from "../../attachment-types.js";

const require = createRequire(import.meta.url);
const MAX_PIXELS = 50_000_000;
export const HEIF_CONTENT_TYPES = new Set([
  "image/heic",
  "image/heif",
  "image/heic-sequence",
  "image/heif-sequence",
]);

/** Validate bounded ISO-BMFF structure before invoking any native decoder. */
export function validateHeifDimensions(body: Buffer): void {
  let boxes = 0;
  let dimensions = 0;
  let totalPixels = 0;
  let branded = false;
  const visit = (start: number, end: number, depth: number) => {
    if (depth > 8) throw new Error("HEIF metadata nesting is too deep");
    for (let at = start; at < end; ) {
      if (++boxes > 4096 || end - at < 8)
        throw new Error("Invalid HEIF box structure");
      let size = body.readUInt32BE(at);
      const type = body.toString("ascii", at + 4, at + 8);
      let header = 8;
      if (size === 1) {
        if (end - at < 16) throw new Error("Invalid HEIF box length");
        const extended = body.readBigUInt64BE(at + 8);
        if (extended > BigInt(body.length))
          throw new Error("HEIF box exceeds file bounds");
        size = Number(extended);
        header = 16;
      } else if (size === 0) size = end - at;
      if (size < header || at + size > end)
        throw new Error("HEIF box exceeds file bounds");
      const content = at + header;
      if (type === "ftyp") {

View on GitHub (pinned to 3f1d897a7c)