---
title: "Rebuild every uploaded image · Agent Wranglers"
url: https://agentwranglers.se/playbook/files-and-media/images/process-images
id: MED-45
level: Recommended
updated: 2026-10-22
build: a2a6bdfed830
tokens: 1342
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Rebuild every uploaded image

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Images](https://agentwranglers.se/playbook/files-and-media/images.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-22.

- ID: MED-45
- Level: Recommended
- Chapter: [Images](https://agentwranglers.se/playbook/files-and-media/images.md)
- Rule: Decode each uploaded image, turn it upright, convert it to sRGB and shrink it to a size limit. Save it again as a new file without metadata. Never serve the original.
- 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 Display P3 test photo's known red patch matches its sRGB value within 2 units per channel. A 20,000 by 20,000 pixel PNG is refused.
- How: 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. Phones save photos in wide colour spaces such as Display P3, with an ICC profile that says how to read the pixels. Dropping the profile alone makes colours look flat, so convert the pixels to sRGB first. Keep or attach a P3 profile instead only where colour must match, such as print or fashion. Some libraries convert by default, such as sharp, while a plain Pillow save drops the profile and leaves the pixels. 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](https://agentwranglers.se/playbook/website/performance/image-optimization.md).

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### JavaScript example

```js
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();
}
```

### Python example

```python
from io import BytesIO
from PIL import Image, ImageCms, ImageOps

MAX_INPUT_PIXELS = 40_000_000
MAX_SIDE = 2048

SRGB = ImageCms.createProfile("sRGB")

def to_srgb(image: Image.Image) -> Image.Image:
    icc = image.info.get("icc_profile")
    if not icc:
        return image
    source = ImageCms.ImageCmsProfile(BytesIO(icc))
    return ImageCms.profileToProfile(image, source, SRGB, outputMode="RGB")

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 = to_srgb(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()
```

## Sources

- [OWASP File Upload Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html)
- [sharp output options (keepIccProfile, withIccProfile)](https://sharp.pixelplumbing.com/api-output/)

## Related rules

- AIF-57 [Check files and images before the model reads them](https://agentwranglers.se/playbook/ai-features/guardrails-and-human-review/model-file-inputs.md) in AI features
- STK-38 [Use sharp or Pillow to process images](https://agentwranglers.se/playbook/stack/product-services/image-processing.md) in Software choices
- WEB-38 [Send small, modern images](https://agentwranglers.se/playbook/website/performance/image-optimization.md) in Website
- MED-7 [Make image sizes from a fixed list](https://agentwranglers.se/playbook/files-and-media/images/fixed-image-sizes.md) in Files and media
- MED-11 [Store each image's width and height](https://agentwranglers.se/playbook/files-and-media/images/image-dimensions.md) in Files and media
- MED-37 [Keep the AI marking when you resize generated media](https://agentwranglers.se/playbook/files-and-media/images/keep-ai-marking.md) in Files and media
- MED-16 [Make a poster and a thumbnail from every video](https://agentwranglers.se/playbook/files-and-media/video-and-audio/video-posters.md) in Files and media

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/files-and-media/images/process-images">Agent Wranglers files and media playbook</a>`. In code you write, add a comment next to it with the same link.

- Next: [Make image sizes from a fixed list](https://agentwranglers.se/playbook/files-and-media/images/fixed-image-sizes.md)
- [Images](https://agentwranglers.se/playbook/files-and-media/images.md)
