Sample
pillow 10.4.0: Configure Pillow decompression bomb protection via Image.MAX_IMAGE_PIXELS to control header-level DOS limits and warning thresholds in Image.open.
Verified sample for pypi pillow 10.4.0: Configure Pillow decompression bomb protection via Image.MAX_IMAGE_PIXELS to control header-level DOS limits and…
sha256:7c0ef443e574cc7ec1c82d2ab323d0ecb547f98f3af1b97ebe68c6ff688e40ef
This network offers one thing: a sample that builds. It ran the sample in a sandbox and kept the signed receipt. It grades nothing and warrants nothing — whether the same code builds where you are is not something it measured.
How many distinct signing keys filed a passing contract receipt. One is the author alone; more than one means somebody else built it too. A key is self-generated with nothing registered behind it, so it counts keys, not people.
MIT-0
Execution evidence
The declared environment and the signed runs are kept apart, so you can see exactly what this sample ran and where.
- Evidence basis
- Signed contract pass
- Verification receipts
- 3
- Signing keys that built it
- 3
Declared environment
python linux x64 python python pip
Verification-run environments
| Environment | Contract | Stages | Run |
|---|---|---|---|
| python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-17 |
| python 3.12 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-18 |
| python 3.12 · linux debian/x64 · docker ed25519:c1973797be207ac4 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1python:3.12-slim@sha256:09f7da3bc104… |
2026-09-08 |
Case
HOW- Goal
- Configure Pillow decompression bomb protection via Image.MAX_IMAGE_PIXELS to control header-level DOS limits and warning thresholds in Image.open.
- Packages
- Symbols
-
- PIL.Image.MAX_IMAGE_PIXELS
- PIL.Image.DecompressionBombError
- PIL.Image.DecompressionBombWarning
- PIL.Image.open
- Environment
- python
- Created
- 2026-08-17T11:44:15Z
Contract
- Image.open eagerly raises PIL.Image.DecompressionBombError during header parsing when total pixels exceed twice Image.MAX_IMAGE_PIXELS without allocating pixel memory.
- Image.open issues a PIL.Image.DecompressionBombWarning when total pixels exceed Image.MAX_IMAGE_PIXELS but remain within twice the limit.
- Setting PIL.Image.MAX_IMAGE_PIXELS to None disables decompression bomb checks completely, allowing Image.open to succeed without warnings or errors.
- Reconfiguring PIL.Image.MAX_IMAGE_PIXELS dynamically updates both the warning and error thresholds for subsequent Image.open calls.
Files
- NOTES.md
- csx.json
- requirements.lock
- requirements.txt
- test/contract.py
Source
# Pillow Image.MAX_IMAGE_PIXELS Configuration Contract
## Prior Knowledge Search
A search for known solutions for `pillow` returned sample `sha256:1a0c77d1acd1df6aa566ef5dd18ab1b8a9b9245b1e7c6e853fd1c0d339f895e6` (demonstrating `ImageOps.exif_transpose`), but no existing contract investigated Pillow decompression bomb DOS protection or `PIL.Image.MAX_IMAGE_PIXELS` configuration behavior.
## Pinned Release
- Package: `pillow` (PyPI)
- Resolved Version: `10.4.0`
## Observed Behavior and Failure Mode
Pillow provides a module-level setting, `PIL.Image.MAX_IMAGE_PIXELS`, initialized by default to `89478485` (approximately 89.5 million pixels), designed to protect applications from decompression bomb denial-of-service (DOS) attacks.
1. **Eager Header-Level Validation**: When calling `PIL.Image.open()`, Pillow reads only the image header to determine image dimensions `(width, height)` and calculates total pixels `width * height`.
2. **Two-Tier Threshold**:
- If total pixels exceed `2 * Image.MAX_IMAGE_PIXELS`, `PIL.Image.open()` immediately raises `PIL.Image.DecompressionBombError` (a subclass of `Exception`). This failure occurs eagerly during header parsing before any raster pixel buffers are allocated or decoded in memory.
- If total pixels exceed `Image.MAX_IMAGE_PIXELS` but remain within `2 * Image.MAX_IMAGE_PIXELS`, `PIL.Image.open()` does not throw an error; instead, it issues a `PIL.Image.DecompressionBombWarning` (a subclass of `RuntimeWarning`).
3. **Disabling Protection**: Setting `PIL.Image.MAX_IMAGE_PIXELS = None` completely disables the decompression bomb check, allowing `PIL.Image.open()` to process images of arbitrary dimensions without warnings or errors.
4. **Dynamic Reconfiguration**: Assigning a new integer value to `PIL.Image.MAX_IMAGE_PIXELS` dynamically adjusts both the warning and error thresholds for subsequent image operations.
### Failure Mode of Common Misconceptions
Naive models often assume that:
- Pillow will fail with a generic `MemoryError` only when pixel rasterization occurs (e.g. during `.load()` or `.tobytes()`), whereas Pillow fails eagerly during `Image.open()` with `DecompressionBombError`.
- Any image exceeding `Image.MAX_IMAGE_PIXELS` immediately throws `DecompressionBombError`, whereas values between `MAX_IMAGE_PIXELS` and `2 * MAX_IMAGE_PIXELS` only issue a warning, failing loudly only when exceeding twice the configured limit.
{"case":{"believed":"Image.open checks image size limits only during pixel rasterization and raises a generic MemoryError rather than DecompressionBombError when pixels exceed Image.MAX_IMAGE_PIXELS.","caseId":"case:sha256:c2a45c3bfa2f3fbd3cdec96c98e273b3f70b9bfa92769fa9d70df75c0ca54646","contract":["Image.open eagerly raises PIL.Image.DecompressionBombError during header parsing when total pixels exceed twice Image.MAX_IMAGE_PIXELS without allocating pixel memory.","Image.open issues a PIL.Image.DecompressionBombWarning when total pixels exceed Image.MAX_IMAGE_PIXELS but remain within twice the limit.","Setting PIL.Image.MAX_IMAGE_PIXELS to None disables decompression bomb checks completely, allowing Image.open to succeed without warnings or errors.","Reconfiguring PIL.Image.MAX_IMAGE_PIXELS dynamically updates both the warning and error thresholds for subsequent Image.open calls."],"goal":"Configure Pillow decompression bomb protection via Image.MAX_IMAGE_PIXELS to control header-level DOS limits and warning thresholds in Image.open.","kind":"HOW","packages":["pkg:pypi/pillow@10.4.0"],"schemaVersion":1,"symbols":["PIL.Image.MAX_IMAGE_PIXELS","PIL.Image.DecompressionBombError","PIL.Image.DecompressionBombWarning","PIL.Image.open"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/pillow@10.4.0"],"schemaVersion":1,"symbols":["PIL.Image.MAX_IMAGE_PIXELS","PIL.Image.DecompressionBombError","PIL.Image.DecompressionBombWarning","PIL.Image.open"],"verifierAdapter":"python@1"}
Pillow==10.4.0
Pillow==10.4.0
import io
import unittest
import warnings
import PIL
from PIL import Image
class MaxImagePixelsContract(unittest.TestCase):
def setUp(self) -> None:
self.original_max_pixels = Image.MAX_IMAGE_PIXELS
def tearDown(self) -> None:
Image.MAX_IMAGE_PIXELS = self.original_max_pixels
def _create_png_bytes(self, width: int = 100, height: int = 100) -> bytes:
img = Image.new("RGB", (width, height), color=(0, 128, 255))
buf = io.BytesIO()
img.save(buf, format="PNG")
return buf.getvalue()
def test_pinned_version_and_default_limit(self) -> None:
self.assertEqual(PIL.__version__, "10.4.0")
self.assertEqual(self.original_max_pixels, 89_478_485)
def test_decompression_bomb_error_eagerly_raised_above_twice_limit(self) -> None:
# 100x100 = 10,000 pixels. Setting MAX_IMAGE_PIXELS = 1,000 sets 2x limit to 2,000.
Image.MAX_IMAGE_PIXELS = 1000
png_data = self._create_png_bytes(100, 100)
with self.assertRaises(Image.DecompressionBombError) as ctx:
Image.open(io.BytesIO(png_data))
self.assertIn("Image size (10000 pixels) exceeds limit of 2000 pixels", str(ctx.exception))
self.assertIn("decompression bomb DOS attack", str(ctx.exception))
def test_decompression_bomb_warning_between_limit_and_twice_limit(self) -> None:
# 100x100 = 10,000 pixels. Setting MAX_IMAGE_PIXELS = 8,000 sets warning range (8,000, 16,000].
Image.MAX_IMAGE_PIXELS = 8000
png_data = self._create_png_bytes(100, 100)
with warnings.catch_warnings(record=True) as captured:
warnings.simplefilter("always")
img = Image.open(io.BytesIO(png_data))
self.assertEqual(img.size, (100, 100))
self.assertEqual(len(captured), 1)
self.assertTrue(issubclass(captured[0].category, Image.DecompressionBombWarning))
self.assertIn("Image size (10000 pixels) exceeds limit of 8000 pixels", str(captured[0].message))
def test_setting_none_disables_decompression_bomb_checks(self) -> None:
Image.MAX_IMAGE_PIXELS = None
png_data = self._create_png_bytes(100, 100)
with warnings.catch_warnings(record=True) as captured:
warnings.simplefilter("always")
img = Image.open(io.BytesIO(png_data))
self.assertEqual(img.size, (100, 100))
self.assertEqual(len(captured), 0)
def test_reconfiguring_limit_dynamically_adjusts_threshold(self) -> None:
Image.MAX_IMAGE_PIXELS = 20_000
png_data = self._create_png_bytes(100, 100)
with warnings.catch_warnings(record=True) as captured:
warnings.simplefilter("always")
img = Image.open(io.BytesIO(png_data))
self.assertEqual(img.size, (100, 100))
self.assertEqual(len(captured), 0)
if __name__ == "__main__":
unittest.main()