booru-viewer/tests/core/test_pil_safety.py
pax 2bb6352141 security: fix #8 — install MAX_IMAGE_PIXELS cap in core/__init__.py
PIL's decompression-bomb cap previously lived as a side effect of
importing core/cache.py. Any future code path that touched core/images
(or any other core submodule) without first importing cache would
silently revert to PIL's default 89M-pixel *warning* (not an error),
re-opening the bomb surface.

Moves the cap into core/__init__.py so any import of any
booru_viewer.core.* submodule installs it first. The duplicate set
in cache.py is left in place by this commit and removed in the next
one — both writes are idempotent so this commit is bisect-safe.

Audit-Ref: SECURITY_AUDIT.md finding #8
Severity: Low
2026-04-11 16:21:32 -05:00

58 lines
1.6 KiB
Python

"""Tests for the project-wide PIL decompression-bomb cap (audit #8).
The cap lives in `booru_viewer/core/__init__.py` so any import of
any `booru_viewer.core.*` submodule installs it first — independent
of whether `core.cache` is on the import path. Both checks are run
in a fresh subprocess so the assertion isn't masked by some other
test's previous import.
"""
from __future__ import annotations
import subprocess
import sys
EXPECTED = 256 * 1024 * 1024
def _run(code: str) -> str:
result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
def test_core_package_import_installs_cap():
"""Importing the core package alone must set MAX_IMAGE_PIXELS."""
out = _run(
"import booru_viewer.core; "
"from PIL import Image; "
"print(Image.MAX_IMAGE_PIXELS)"
)
assert int(out) == EXPECTED
def test_core_images_import_installs_cap():
"""The original audit concern: importing core.images without first
importing core.cache must still set the cap."""
out = _run(
"from booru_viewer.core import images; "
"from PIL import Image; "
"print(Image.MAX_IMAGE_PIXELS)"
)
assert int(out) == EXPECTED
def test_core_cache_import_still_installs_cap():
"""Regression: the old code path (importing cache first) must keep
working after the move."""
out = _run(
"from booru_viewer.core import cache; "
"from PIL import Image; "
"print(Image.MAX_IMAGE_PIXELS)"
)
assert int(out) == EXPECTED