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
- 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.
- Have the client send the correct Content-Type (set from actual encoding, not filename extension).
- If the format should be supported (e.g. image/avif), add it to the formats map in media.ts: formats["image/avif"] = ["avif"].
- 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
- Never trust client-supplied Content-Type; sniff magic bytes server-side
- Set Content-Type from actual encoding in upload clients, not filename extension
- Only advertise formats present in the formats map (jpeg/png/webp/gif plus HEIF set)
- Return 415 for genuinely unsupported formats instead of routing them through the raster validator
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
- Decoded image exceeds the pixel limit
- HEIF dimensions could not be verified
- HEIF decoded image exceeds the pixel limit
- HEIF image collection exceeds the pixel limit
- A full lowercase source SHA is required.
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)