paperclipai/paperclip · error

Image bytes do not match the declared content type

Error message

Image bytes do not match the declared content type

What it means

validatePhotonImage compares sharp's detected metadata.format against an allowlist mapping each declared content type to its expected format(s) (jpeg/png/webp/gif). It throws when the decoded bytes' actual format is not in the list for the declared content type — i.e. the Content-Type header lies about the payload.

Solutions

  1. Detect the true type from magic bytes (e.g. file-type package) and call validatePhotonImage with the detected type instead of the client-supplied header.
  2. Have the client send the correct Content-Type (set from actual encoding, not filename extension).
  3. If the format should be supported (e.g. image/avif), add it to the formats map in media.ts: formats["image/avif"] = ["avif"].
  4. Reject unsupported/lying types with 415 Unsupported Media Type at the ingest boundary.

Example fix

// before
await validatePhotonImage(body, req.headers['content-type']); // header says image/png, bytes are jpeg
// after
const { fileTypeFromBuffer } = await import('file-type');
const detected = await fileTypeFromBuffer(body);
await validatePhotonImage(body, `image/${detected.ext}`);
Defensive patterns

Strategy: validation

Validate before calling

import { fileTypeFromBuffer } from 'file-type';
export async function contentTypeMatchesBytes(body: Buffer, declared: string): Promise<boolean> {
  const detected = await fileTypeFromBuffer(body);
  if (!detected) return false;
  const formats: Record<string, string[]> = {
    'image/jpeg': ['jpeg'], 'image/jpg': ['jpeg'], 'image/png': ['png'],
    'image/webp': ['webp'], 'image/gif': ['gif'],
  };
  return formats[declared]?.includes(detected.ext) ?? false;
}

Type guard

function isKnownRasterType(ct: string): ct is 'image/jpeg' | 'image/jpg' | 'image/png' | 'image/webp' | 'image/gif' {
  return ['image/jpeg','image/jpg','image/png','image/webp','image/gif'].includes(ct);
}

Try / catch

try {
  await validatePhotonImage(body, declaredType);
} catch (err) {
  if (err instanceof Error && err.message === 'Image bytes do not match the declared content type') {
    const detected = await fileTypeFromBuffer(body);
    if (detected) return validatePhotonImage(body, `image/${detected.ext}`);
    return respond(415, 'unsupported or mislabeled image type');
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling validatePhotonImage with e.g. contentType 'image/png' on bytes sharp detects as jpeg; declaring image/jpeg on WebP bytes; any declared type not present in the formats map (image/avif, image/tiff, image/svg+xml on non-HEIF path) so formats[contentType] is undefined.

Common situations: File-manager exports mislabeling types; upload clients deriving Content-Type from file extension; servers forwarding upstream Content-Type from third-party fetches; AVIF/ TIFF uploads declared as supported types.

Related errors


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

Appendix: source

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

  const metadata = await sharp(body, {
    limitInputPixels: MAX_PIXELS,
    failOn: "error",
  }).metadata();
  if (
    !metadata.width ||
    !metadata.height ||
    metadata.width * metadata.height * (metadata.pages ?? 1) > MAX_PIXELS
  )
    throw new Error("Decoded image exceeds the pixel limit");
  const formats: Record<string, string[]> = {
    "image/jpeg": ["jpeg"],
    "image/jpg": ["jpeg"],
    "image/png": ["png"],
    "image/webp": ["webp"],
    "image/gif": ["gif"],
  };
  if (!formats[contentType]?.includes(metadata.format ?? ""))
    throw new Error("Image bytes do not match the declared content type");
}

/** Isolate the native converter with a deadline and bounded input/output.
 * Native packages exist for macOS, Windows and Linux glibc x64/arm64.
 * Unsupported hosts retain the original and report an unavailable preview. */
export async function photonHeifPreview(body: Buffer): Promise<Buffer> {
  validateHeifDimensions(body);
  const modulePath = require.resolve("heif2jpeg");
  const script = `const {heifToJpeg}=require(process.argv[1]);const chunks=[];process.stdin.on('data',c=>chunks.push(c));process.stdin.on('end',async()=>{try{const jpeg=await heifToJpeg(Buffer.concat(chunks),{quality:80});process.stdout.end(jpeg);}catch{process.exitCode=1;}});`;
  const jpeg = await new Promise<Buffer>((resolve, reject) => {
    const child = spawn(
      process.execPath,
      ["--max-old-space-size=256", "--eval", script, modulePath],
      { stdio: ["pipe", "pipe", "ignore"], windowsHide: true },
    );
    let length = 0;
    const chunks: Buffer[] = [];
    const timer = setTimeout(() => {

View on GitHub (pinned to 3f1d897a7c)