Files and uploads Required
Rebuild every uploaded image
Decode each uploaded image, turn it upright, shrink it to a size limit and save it again as a new file without metadata. Never serve the original.
How to do it
Photos carry EXIF data, which often holds the GPS position of a person's home and the camera's serial number. Rebuilding the image drops it and any hidden payload. Refuse images over a pixel limit, such as 40 million pixels, before decoding, to stop decompression bombs. Read the size from the header and compare it yourself. Pillow's own MAX_IMAGE_PIXELS only warns until twice the limit. Apply the EXIF orientation first, then drop every tag. Cap the long side, such as at 2,048 pixels, and save as WebP or AVIF. For the sizes a page shows, see small, modern images.
Example
import sharp from 'sharp';
const MAX_INPUT_PIXELS = 40_000_000;
const MAX_SIDE = 2048;
export function rebuildPhoto(input) {
return sharp(input, { limitInputPixels: MAX_INPUT_PIXELS })
.rotate()
.resize({ width: MAX_SIDE, height: MAX_SIDE, fit: 'inside', withoutEnlargement: true })
.webp({ quality: 80 })
.toBuffer();
}
from io import BytesIO
from PIL import Image, ImageOps
MAX_INPUT_PIXELS = 40_000_000
MAX_SIDE = 2048
def rebuild_photo(data: bytes) -> bytes:
original = Image.open(BytesIO(data))
if original.width * original.height > MAX_INPUT_PIXELS:
raise ValueError("image has too many pixels")
upright = ImageOps.exif_transpose(original).convert("RGB")
upright.thumbnail((MAX_SIDE, MAX_SIDE))
clean = Image.new("RGB", upright.size)
clean.paste(upright)
out = BytesIO()
clean.save(out, "WEBP", quality=80)
return out.getvalue()
How to check
Upload a photo with GPS tags and a 90-degree orientation tag. A metadata reader such as ExifTool finds no GPS or EXIF tags in the served file, and it shows upright. A 20,000 by 20,000 pixel PNG is refused.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers api design playbook: https://agentwranglers.se/playbook/api-design/files-and-uploads/process-images.