stackchain-dashboard/tests/test_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

64 lines
2.0 KiB
Python

import io
from pathlib import Path
import pytest
from PIL import Image
from src.image_sanitizer import MAX_IMAGE_PIXELS, sanitize_image
def test_runtime_dependencies_install_the_image_decoder():
requirements = (Path(__file__).parents[1] / "requirements.txt").read_text().splitlines()
assert any(line.startswith("Pillow==") for line in requirements)
def encoded_image(format_name, size=(3, 2), *, exif=None):
image = Image.new("RGB", size)
image.putdata(
[(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (0, 255, 255), (255, 0, 255)]
)
output = io.BytesIO()
image.save(output, format=format_name, exif=exif or b"")
return output.getvalue()
def test_sanitize_image_applies_orientation_and_removes_jpeg_metadata():
exif = Image.Exif()
exif[274] = 6
exif[ GPS_TAG := 34853 ] = {1: "N", 2: (1, 1, 1)}
source = encoded_image("JPEG", exif=exif)
sanitized = sanitize_image("image/jpeg", source)
with Image.open(io.BytesIO(sanitized)) as result:
assert result.size == (2, 3)
assert not result.getexif()
assert GPS_TAG not in result.info
assert result.format == "JPEG"
def test_sanitize_image_rejects_bytes_that_do_not_match_claimed_type():
source = encoded_image("PNG")
with pytest.raises(ValueError, match="does not match"):
sanitize_image("image/jpeg", source)
def test_sanitize_image_rejects_over_twelve_megapixels_before_decoding():
image = Image.new("RGB", (4096, 3073))
output = io.BytesIO()
image.save(output, format="PNG")
with pytest.raises(ValueError, match="12 megapixels"):
sanitize_image("image/png", output.getvalue())
def test_sanitize_image_rejects_animated_webp():
first = Image.new("RGB", (2, 2), "red")
second = Image.new("RGB", (2, 2), "blue")
output = io.BytesIO()
first.save(output, format="WEBP", save_all=True, append_images=[second], duration=100)
with pytest.raises(ValueError, match="animated"):
sanitize_image("image/webp", output.getvalue())