fix(tradein): image_sanitizer — reject decompression-bomb до декода (R2 security) #2495

Merged
bot-backend merged 1 commit from fix/tradein-image-dos into main 2026-07-12 19:19:40 +00:00
Collaborator

R2 MEDIUM (security) — image_sanitizer decompression-bomb / pixel-flood DoS

sanitize_image декодировал (img.load()) до resize, а MAX_IMAGE_PIXELS был дефолтный (~89MP). Pilot грузит ≤10МБ сильно-сжатое изображение с заявленными ~89-178MP → декод в сотни МБ RGB → OOM бэкенда (768МБ), ×12 слотов.

Fix (defense-in-depth): _MAX_PIXELS=40MP cap + guard img.size (из хедера, БЕЗ декода) до img.load() → reject oversized; Image.MAX_IMAGE_PIXELS=40MP (Pillow сам бросит DecompressionBombError на любом декод-пути); байтовый backstop 10МиБ. Контракт caller не тронут (bad image → ImageSanitizationError → HTTP 400, не 500). 7 тестов (вкл. pixel-flood reject с img.load.assert_not_called()).

## R2 MEDIUM (security) — image_sanitizer decompression-bomb / pixel-flood DoS `sanitize_image` декодировал (`img.load()`) до resize, а `MAX_IMAGE_PIXELS` был дефолтный (~89MP). Pilot грузит ≤10МБ сильно-сжатое изображение с заявленными ~89-178MP → декод в сотни МБ RGB → OOM бэкенда (768МБ), ×12 слотов. Fix (defense-in-depth): `_MAX_PIXELS=40MP` cap + guard `img.size` (из хедера, БЕЗ декода) до `img.load()` → reject oversized; `Image.MAX_IMAGE_PIXELS=40MP` (Pillow сам бросит DecompressionBombError на любом декод-пути); байтовый backstop 10МиБ. Контракт caller не тронут (bad image → `ImageSanitizationError` → HTTP 400, не 500). 7 тестов (вкл. pixel-flood reject с `img.load.assert_not_called()`).
bot-backend added 1 commit 2026-07-12 19:13:38 +00:00
fix(tradein/photos): защита от pixel-flood DoS в image_sanitizer
All checks were successful
CI / changes (pull_request) Successful in 7s
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 51s
1c9f0e1161
sanitize_image декодировал изображение целиком (img.load) ДО ресайза,
а Pillow MAX_IMAGE_PIXELS оставался дефолтным (~89 MP). Хорошо сжимаемый
≤10 MB аплоад с заявленными ~89-178 MP разворачивался в raw RGB буфер на
сотни MB до thumbnail — OOM-kill backend'а (768 MiB, #2214), усиление ×12
слотов на estimate.

Defense in depth:
- _MAX_PIXELS = 40 MP: проверка img.size (из хедера, без декода) ДО img.load;
  превышение → ImageSanitizationError без загрузки полного буфера.
- Image.MAX_IMAGE_PIXELS понижен до 40 MP — Pillow сам бросает
  DecompressionBombError на любом декод-пути в обход явной проверки.
- Явный except Image.DecompressionBombError + re-raise ImageSanitizationError
  (byte/pixel cap) до широкого except — грациозный reject, не 500.
- _MAX_BYTES backstop (endpoint уже режет 10 MB → 413, но сервис standalone).

Контракт caller'а не меняется: reject → ImageSanitizationError → HTTP 400.
Тест tests/services/test_image_sanitizer.py: happy-path, resize, pixel-flood
без декода (load не вызывается), byte-backstop, граница cap, garbage bytes.
bot-backend merged commit 032fd95a7e into main 2026-07-12 19:19:40 +00:00
bot-backend deleted branch fix/tradein-image-dos 2026-07-12 19:19:40 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2495
No description provided.