stackchain-dashboard/src/image_sanitizer.py
timmy 966028e0a6
All checks were successful
CI / lint (pull_request) Successful in 2m11s
CI / build-release (pull_request) Successful in 5s
CI / browser-journey (pull_request) Successful in 1m11s
CI / release-candidate (pull_request) Has been skipped
security: sanitize image evidence server-side (Closes #983)
2026-08-16 19:33:46 +00:00

39 lines
1.4 KiB
Python

"""Authoritative image decoding and metadata removal for evidence uploads."""
from __future__ import annotations
import io
from PIL import Image, ImageOps
MAX_IMAGE_PIXELS = 12 * 1024 * 1024
def sanitize_image(content_type: str, content: bytes) -> bytes:
"""Apply orientation and return metadata-free bytes in the claimed format."""
format_names = {
"image/png": "PNG",
"image/jpeg": "JPEG",
"image/webp": "WEBP",
}
try:
with Image.open(io.BytesIO(content)) as source:
claimed_format = format_names[content_type]
if source.format != claimed_format:
raise ValueError("image content does not match the selected image type")
if getattr(source, "n_frames", 1) != 1:
raise ValueError("animated image evidence is not supported")
if source.width * source.height > MAX_IMAGE_PIXELS:
raise ValueError("attachment must not exceed 12 megapixels")
image = ImageOps.exif_transpose(source)
image.load()
output = io.BytesIO()
save_options = {"quality": 90} if claimed_format in {"JPEG", "WEBP"} else {}
image.save(output, format=claimed_format, **save_options)
return output.getvalue()
except ValueError:
raise
except (KeyError, OSError) as exc:
raise ValueError("attachment must be a valid static image") from exc