[P1] photo-upload буферизует всё тело в RAM до проверки лимита (OOM-риск при mem_limit 768m) #2233

Closed
opened 2026-07-02 21:31:55 +00:00 by lekss361 · 1 comment
Owner

Из аудита 2026-07-02 (Фаза 4 ремедиации, verified на main 2026-07-03).

Находка: tradein-mvp/backend/app/api/v1/trade_in.py:521content = await file.read() читает ВСЁ тело запроса в память, и только потом len(content) > _MAX_PHOTO_BYTES (10MB). Злоумышленник/случайный клиент с multi-GB файлом заставит backend буферизовать гигабайты. Актуальность выросла: с #2214 backend получил mem_limit: 768m + запрет свапа → большой аплоад = OOM-kill контейнера (restart спасает, но in-flight запросы падают).

Acceptance:

  • Чтение чанками с жёстким капом: прервать чтение по превышению 10MB (while chunk := await file.read(64*1024) + счётчик → 413 сразу), НЕ читать остаток.
  • Существующий sanitize-путь (Pillow re-encode) не задет.
  • Тест: стрим больше лимита → 413 без полного чтения (mock file с бесконечным read).
  • Бонус: проверить, есть ли Caddy request_body max_size на маршруте — добавить как первый рубеж.

Scope: tradein-mvp/backend/app/api/v1/trade_in.py, tests.

Из аудита 2026-07-02 (Фаза 4 ремедиации, verified на main 2026-07-03). **Находка:** `tradein-mvp/backend/app/api/v1/trade_in.py:521` — `content = await file.read()` читает ВСЁ тело запроса в память, и только потом `len(content) > _MAX_PHOTO_BYTES` (10MB). Злоумышленник/случайный клиент с multi-GB файлом заставит backend буферизовать гигабайты. **Актуальность выросла:** с #2214 backend получил `mem_limit: 768m` + запрет свапа → большой аплоад = OOM-kill контейнера (restart спасает, но in-flight запросы падают). **Acceptance:** - [ ] Чтение чанками с жёстким капом: прервать чтение по превышению 10MB (`while chunk := await file.read(64*1024)` + счётчик → 413 сразу), НЕ читать остаток. - [ ] Существующий sanitize-путь (Pillow re-encode) не задет. - [ ] Тест: стрим больше лимита → 413 без полного чтения (mock file с бесконечным read). - [ ] Бонус: проверить, есть ли Caddy `request_body max_size` на маршруте — добавить как первый рубеж. Scope: `tradein-mvp/backend/app/api/v1/trade_in.py`, tests.
lekss361 added the
priority/p1
scope/backend
security
tradein
labels 2026-07-02 21:33:28 +00:00
Author
Owner

Задеплоено и проверено (2026-07-03 06:2x UTC).

  • PR #2258 смержен, deploy success (sha 910bfed5).
  • Смок: backend health 200; request_body { max_size 12MB } присутствует в задеплоенном Caddy-фрагменте на боксе (/opt/gendesign/tradein-mvp/deploy/Caddyfile.tradein-fragment), Caddy перечитал конфиг в рамках deploy-шага.
  • App-уровень: чанковое чтение с капом 10 MiB → 413 сразу, инвариант «не дочитывает тело» закрыт тестом со счётчиком read() (161 вызов = кап/64KB+1). Полный pytest 3021 passed, ревью APPROVE.

Границы сохранены: пустой файл → 400, ровно 10 MiB проходит, sanitize-путь (Pillow) получает идентичный вход. OOM-вектор при mem_limit: 768m (#2214) закрыт двумя рубежами (Caddy 12MB → backend 10 MiB).

**Задеплоено и проверено (2026-07-03 06:2x UTC).** - PR #2258 смержен, deploy success (sha 910bfed5). - Смок: backend health 200; `request_body { max_size 12MB }` присутствует в задеплоенном Caddy-фрагменте на боксе (`/opt/gendesign/tradein-mvp/deploy/Caddyfile.tradein-fragment`), Caddy перечитал конфиг в рамках deploy-шага. - App-уровень: чанковое чтение с капом 10 MiB → 413 сразу, инвариант «не дочитывает тело» закрыт тестом со счётчиком read() (161 вызов = кап/64KB+1). Полный pytest 3021 passed, ревью ✅ APPROVE. Границы сохранены: пустой файл → 400, ровно 10 MiB проходит, sanitize-путь (Pillow) получает идентичный вход. OOM-вектор при `mem_limit: 768m` (#2214) закрыт двумя рубежами (Caddy 12MB → backend 10 MiB).
Sign in to join this conversation.
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#2233
No description provided.