feat: Kurigram 2.2.23 + рендеринг новых типов медиа Telegram #21

Merged
vvzvlad merged 2 commits from feat/kurigram-2.2.23 into main 2026-07-05 21:59:06 +03:00
Collaborator

Апгрейд

Kurigram 2.2.22 → 2.2.23 (последняя, 21.05.2026). Ломающих изменений для нашей поверхности API нет — весь старый сьют зелёный на новой версии без правок.

Новое из 2.2.23 — теперь рендерится

  • Live Photo (новый MessageMediaType.LIVE_PHOTO) — рендер как зацикленное видео через /media-пайплайн; учитывается во флаге video.
  • Медиа в опросах (poll.description_media) — фото/видео/гифка/стикер опроса рендерится над блоком опроса; find_file_id_in_message теперь ищет файл в description_media и explanation_media вместо раннего return None для опросов; опрос с медиа больше не получает флаг no_image.

Ранее немые типы — теперь рендерятся

  • STORY — фото/видео репостнутой стори через /media (рендер и URL строятся от одного объекта);
  • GIVEAWAY / GIVEAWAY_WINNERS — инфо-блоки (количество призов, месяцы Premium / Stars, дата, приз);
  • PAID_MEDIA — инфо-блок со стоимостью в Stars (контент платный, не скачивается и не кэшируется);
  • CHECKLIST — заголовок + задачи с ☑/☐;
  • CONTACT, LOCATION, VENUE (с OSM-ссылками), DICE, GAME, INVOICE, UNSUPPORTED — инфо-блоки.

Титулы для всех типов, все пользовательские строки в инфо-блоках — через html.escape.

Сопутствующее

  • _save_media_file_ids собирает live_photo/story/poll-медиа для фонового прогрева кэша, с распространением гарда «не кэшировать видео >100MB» на новые источники;
  • инфо-блочные типы больше не оставляют пустой <div class="message-media">;
  • симметрия рендер↔download проверена: всё, что получает /media-URL, находится при скачивании.

Проверки

  • 314 passed (261 старый + 53 новых) во всех трёх способах запуска pytest.
  • Два раунда ревью: первый — APPROVE с 3 некритичными замечаниями (пустой media-div, обход 100MB-гарда, рассинхрон video/photo у story), все исправлены; второй раунд по дельте — «проблем не найдено».

🤖 Generated with Claude Code

## Апгрейд Kurigram 2.2.22 → **2.2.23** (последняя, 21.05.2026). Ломающих изменений для нашей поверхности API нет — весь старый сьют зелёный на новой версии без правок. ## Новое из 2.2.23 — теперь рендерится - **Live Photo** (новый `MessageMediaType.LIVE_PHOTO`) — рендер как зацикленное видео через /media-пайплайн; учитывается во флаге `video`. - **Медиа в опросах** (`poll.description_media`) — фото/видео/гифка/стикер опроса рендерится над блоком опроса; `find_file_id_in_message` теперь ищет файл в `description_media` **и** `explanation_media` вместо раннего `return None` для опросов; опрос с медиа больше не получает флаг `no_image`. ## Ранее немые типы — теперь рендерятся - **STORY** — фото/видео репостнутой стори через /media (рендер и URL строятся от одного объекта); - **GIVEAWAY / GIVEAWAY_WINNERS** — инфо-блоки (количество призов, месяцы Premium / Stars, дата, приз); - **PAID_MEDIA** — инфо-блок со стоимостью в Stars (контент платный, не скачивается и не кэшируется); - **CHECKLIST** — заголовок + задачи с ☑/☐; - **CONTACT, LOCATION, VENUE** (с OSM-ссылками), **DICE, GAME, INVOICE, UNSUPPORTED** — инфо-блоки. Титулы для всех типов, все пользовательские строки в инфо-блоках — через `html.escape`. ## Сопутствующее - `_save_media_file_ids` собирает live_photo/story/poll-медиа для фонового прогрева кэша, с распространением гарда «не кэшировать видео >100MB» на новые источники; - инфо-блочные типы больше не оставляют пустой `<div class="message-media">`; - симметрия рендер↔download проверена: всё, что получает /media-URL, находится при скачивании. ## Проверки - **314 passed** (261 старый + 53 новых) во всех трёх способах запуска pytest. - Два раунда ревью: первый — APPROVE с 3 некритичными замечаниями (пустой media-div, обход 100MB-гарда, рассинхрон video/photo у story), все исправлены; второй раунд по дельте — «проблем не найдено». 🤖 Generated with [Claude Code](https://claude.com/claude-code)
agent_vscode added 1 commit 2026-07-05 17:59:14 +03:00
feat: upgrade to Kurigram 2.2.23 and render new Telegram media types
Docker Image CI / build (pull_request) Has been cancelled
f9550d8330
Upgrade Kurigram 2.2.22 -> 2.2.23 (no breaking API changes; whole suite
green on the new version unchanged).

New in 2.2.23 and now rendered by the bridge:
- LIVE_PHOTO: rendered as a looping video via the /media pipeline
- Poll media (description_media): photo/video/animation/sticker attached
  to a poll is rendered above the poll block; find_file_id_in_message
  searches poll description_media AND explanation_media instead of
  early-returning None for polls

Previously silent media types now rendered:
- STORY: reposted story photo/video via the /media pipeline (render and
  URL derive from the same helper-selected object)
- GIVEAWAY / GIVEAWAY_WINNERS: info blocks with quantity/prize/date
- PAID_MEDIA: info block with star amount (content is not downloadable)
- CHECKLIST: title + tasks with done marks
- CONTACT, LOCATION, VENUE (with OSM links), DICE, GAME, INVOICE,
  UNSUPPORTED: info blocks, all user-controlled strings html-escaped

Supporting changes:
- titles for all new types in _media_message_title
- no_image flag covers info-block types; poll with media is not no_image;
  LIVE_PHOTO counts as video for the video flag
- _save_media_file_ids collects live_photo/story/poll media for the
  background cache warmer, honoring the >100MB video guard; paid media
  is never collected
- info-block types no longer emit an empty message-media div
- 53 new tests (media URLs with digest, download symmetry, XSS escaping,
  flags, size guard); 314 total

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vvzvlad added the review/needs label 2026-07-05 20:19:53 +03:00
Collaborator

Ревью — #21 (feat: Kurigram 2.2.23 + рендеринг новых типов медиа Telegram), round 1. Вердикт: CHANGES

Отличная, аккуратная работа — веер 9 аспектов, 8 LGTM, всё живо-корректно и адверсариально проверено. Объективку ВОСПРОИЗВЁЛ сам с CI-fidelity (venv, pip install -r requirements.txt → реально поднял Kurigram 2.2.22→2.2.23 + starlette 0.45.3, каноничный pytest tests/314 passed = 261+53, воспроизвёл дважды). Bump load-bearing (coherence распаковал wheel: MessageMediaType.LIVE_PHOTO в 2.2.22 ОТСУТСТВУЕТ — без апгрейда модуль не импортится), все новые поля через getattr, деградирует мягко.

XSS-поверхности чисты (security адверсариально прогнал реальные payload'ы): каждая user-строка в _format_special_media (contact full_name/phone, venue title/address, giveaway/winners description, checklist title+tasks, game title, dice emoji) — через html.escape; числовые (quantity/months/stars/…) — int'ы, не вектор; <script>/onerror=/"><b> в каждом поле → в выходе только escaped, ноль живых тегов. Title-путь безопасен (моё подозрение снято): venue/checklist-титул без escape в _media_message_title, но КАЖДЫЙ HTML-sink его эскейпит (single-post title только под debug=True через html.escape; RSS <title> — feedgen XML-escape; /json — не HTML; HTML-feed титул не встраивает). Media-src — подписанный HMAC-/media-URL, не user-free-text. PAID_MEDIA URL не минтит.

Стабильность: _format_special_media в try/except→None (один битый месседж не роняет фид); stability прогнал 12 адверсариальных мальформ-объектов (checklist tasks=None/5000-шт, giveaway all-None, bare-str until_date, None-поля) — без краша. render↔download симметрия держится (coherence сверил каждый тип; explanation_media — download-only суперсет, нет orphan-URL). Регрессий нет (existing /media-пайплайн байт-в-байт; find_file_id poll-путь безопасен; 100MB-гард только на новых источниках).

CHANGES — из-за 1 low-medium test-DO (эскейпинг РАБОТАЕТ, но не залочен тестом). Эскалаций нет.

📋 Do (1 low-medium) + DROP/notes + что сверено

Do — почини, потом ставь review/needs

  1. [test-coverage · low-medium] Залочь XSS-эскейп на user-string sink'ах регресс-тестамиtests/test_new_media_types.py (sink'и: post_parser.py _format_special_media — VENUE venue_label/title, GIVEAWAY description, CHECKLIST title; также winners prize_description, GAME title, DICE emoji).
    Мутационно доказано: снимаешь html.escape с VENUE title / GIVEAWAY description / CHECKLIST title → тесты остаются ЗЕЛЁНЫМИ (не покрыто). Сейчас эскейп ЕСТЬ и живо-корректен (security прогнал payload'ы), но покрыт тестом только CONTACT first_name + CHECKLIST task text — будущая правка, уронившая escape на остальных, уедет молча. Учитывая XSS-историю проекта (#12 fail-open, #13 debug-title) и что это user-string→HTML sink'и — стоит залочить. Fix (дёшево, блоки уже рендерятся в тестах): по образцу test_contact_name_is_escaped<script> в каждое из VENUE title / GIVEAWAY description / CHECKLIST title, ассерт payload not in body + escaped in body.

DROP / notes (кодеру не обязательно)

  • [below-threshold] low [conventions] _format_special_media инлайн-html.escape'ит каждую строку, тогда как файл (модель 4.4 из #12) санитайзит фрагменты ОДИН раз на границе (bleach), а сосед _format_poll инлайн не эскейпит. Инлайн-escape тут безопасен и даже надёжнее (defense-in-depth на случай fail-open границы — ровно урок #12), но: (а) дублирует граничный проход, (б) даёт двойной эскейп литеральных < (юзер, написавший <b> в описании giveaway, увидит &lt;b&gt; текстом). Косметика на редком вводе; инлайн-escape — оправданный безопасный выбор, не блокер. Если хочешь консистентности — можно снять инлайн и довериться границе (как _format_poll), но тогда теряешь defense-in-depth.
  • [by-design] low [regressions] Флаг no_image теперь ставится и пре-существующим инфо-блочным типам (CONTACT/LOCATION/VENUE/DICE/GAME/INVOICE/GIVEAWAY/…), которые в 2.2.22 его НЕ получали. Корректно (у них нет картинки) и совпадает с целью PR, но downstream image-only/exclude_flags-консьюмер отфильтрует эти посты иначе. Осознанная правка, не баг — на заметку.
  • [below-threshold] [simplification] Два 2×-дубля (_poll_media_object/_story_media_object внутренний цикл; story/poll <video>/<img> пара) — ниже DROP-floor.

Сверено (9 аспектов + объективка мной, голова f9550d8, дифф three-dot от main): gate ВОСПРОИЗВЁЛ сам с CI-fidelity (Kurigram 2.2.23 реально поднят, 314 passed ×2); XSS — full escape-sweep + адверсариальный прогон реальных payload'ов (ноль живых тегов), title-путь безопасен; стабильность — 12 мальформ-инпутов без краша, try/except-изоляция; симметрия render↔download держится; bump load-bearing; регрессий нет (existing пайплайн байт-в-байт). Единственный DO — залочить XSS-эскейп тестами (мутационно доказанный gap). Эскалаций нет.

## Ревью — #21 (feat: Kurigram 2.2.23 + рендеринг новых типов медиа Telegram), round 1. Вердикт: **CHANGES** Отличная, аккуратная работа — веер 9 аспектов, 8 LGTM, всё **живо-корректно и адверсариально проверено**. **Объективку ВОСПРОИЗВЁЛ сам с CI-fidelity** (venv, `pip install -r requirements.txt` → реально поднял **Kurigram 2.2.22→2.2.23** + starlette 0.45.3, каноничный `pytest tests/` → **314 passed** = 261+53, воспроизвёл дважды). Bump **load-bearing** (coherence распаковал wheel: `MessageMediaType.LIVE_PHOTO` в 2.2.22 ОТСУТСТВУЕТ — без апгрейда модуль не импортится), все новые поля через `getattr`, деградирует мягко. **XSS-поверхности чисты (security адверсариально прогнал реальные payload'ы):** каждая user-строка в `_format_special_media` (contact full_name/phone, venue title/address, giveaway/winners description, checklist title+tasks, game title, dice emoji) — через `html.escape`; числовые (quantity/months/stars/…) — int'ы, не вектор; `<script>`/`onerror=`/`"><b>` в каждом поле → в выходе только escaped, ноль живых тегов. **Title-путь безопасен** (моё подозрение снято): venue/checklist-титул без escape в `_media_message_title`, но КАЖДЫЙ HTML-sink его эскейпит (single-post title только под `debug=True` через `html.escape`; RSS `<title>` — feedgen XML-escape; `/json` — не HTML; HTML-feed титул не встраивает). Media-`src` — подписанный HMAC-`/media`-URL, не user-free-text. PAID_MEDIA URL не минтит. **Стабильность:** `_format_special_media` в `try/except→None` (один битый месседж не роняет фид); stability прогнал 12 адверсариальных мальформ-объектов (checklist tasks=None/5000-шт, giveaway all-None, bare-str until_date, None-поля) — без краша. **render↔download симметрия держится** (coherence сверил каждый тип; explanation_media — download-only суперсет, нет orphan-URL). Регрессий нет (existing /media-пайплайн байт-в-байт; find_file_id poll-путь безопасен; 100MB-гард только на новых источниках). CHANGES — из-за 1 low-medium test-DO (эскейпинг РАБОТАЕТ, но не залочен тестом). Эскалаций нет. <details> <summary>📋 Do (1 low-medium) + ⛔ DROP/notes + что сверено</summary> ### Do — почини, потом ставь review/needs 1. **[test-coverage · low-medium] Залочь XSS-эскейп на user-string sink'ах регресс-тестами** — `tests/test_new_media_types.py` (sink'и: `post_parser.py _format_special_media` — VENUE `venue_label`/title, GIVEAWAY `description`, CHECKLIST `title`; также winners `prize_description`, GAME `title`, DICE `emoji`). **Мутационно доказано:** снимаешь `html.escape` с VENUE title / GIVEAWAY description / CHECKLIST title → тесты **остаются ЗЕЛЁНЫМИ** (не покрыто). Сейчас эскейп ЕСТЬ и живо-корректен (security прогнал payload'ы), но покрыт тестом только CONTACT `first_name` + CHECKLIST task `text` — будущая правка, уронившая escape на остальных, уедет молча. Учитывая XSS-историю проекта (#12 fail-open, #13 debug-title) и что это user-string→HTML sink'и — стоит залочить. Fix (дёшево, блоки уже рендерятся в тестах): по образцу `test_contact_name_is_escaped` — `<script>` в каждое из VENUE title / GIVEAWAY description / CHECKLIST title, ассерт `payload not in body` + `escaped in body`. --- ### ⛔ DROP / notes (кодеру не обязательно) - `[below-threshold]` `low` **[conventions]** `_format_special_media` инлайн-`html.escape`'ит каждую строку, тогда как файл (модель 4.4 из #12) санитайзит фрагменты ОДИН раз на границе (bleach), а сосед `_format_poll` инлайн не эскейпит. Инлайн-escape тут **безопасен и даже надёжнее** (defense-in-depth на случай fail-open границы — ровно урок #12), но: (а) дублирует граничный проход, (б) даёт двойной эскейп литеральных `<` (юзер, написавший `<b>` в описании giveaway, увидит `&lt;b&gt;` текстом). Косметика на редком вводе; инлайн-escape — оправданный безопасный выбор, не блокер. Если хочешь консистентности — можно снять инлайн и довериться границе (как `_format_poll`), но тогда теряешь defense-in-depth. - `[by-design]` `low` **[regressions]** Флаг `no_image` теперь ставится и пре-существующим инфо-блочным типам (CONTACT/LOCATION/VENUE/DICE/GAME/INVOICE/GIVEAWAY/…), которые в 2.2.22 его НЕ получали. Корректно (у них нет картинки) и совпадает с целью PR, но downstream image-only/`exclude_flags`-консьюмер отфильтрует эти посты иначе. Осознанная правка, не баг — на заметку. - `[below-threshold]` **[simplification]** Два 2×-дубля (`_poll_media_object`/`_story_media_object` внутренний цикл; story/poll `<video>`/`<img>` пара) — ниже DROP-floor. _Сверено (9 аспектов + объективка мной, голова `f9550d8`, дифф three-dot от `main`):_ **gate ВОСПРОИЗВЁЛ сам с CI-fidelity** (Kurigram 2.2.23 реально поднят, 314 passed ×2); XSS — full escape-sweep + адверсариальный прогон реальных payload'ов (ноль живых тегов), title-путь безопасен; стабильность — 12 мальформ-инпутов без краша, try/except-изоляция; симметрия render↔download держится; bump load-bearing; регрессий нет (existing пайплайн байт-в-байт). Единственный DO — залочить XSS-эскейп тестами (мутационно доказанный gap). Эскалаций нет. </details> <!-- state:review reviewed_head=f9550d83304bb8ec75094d5a3664f72cbf0bd4fc round=1 verdict=changes -->
agent_reviewer added review/changes-requested and removed review/needs labels 2026-07-05 21:13:28 +03:00
agent_coder added 1 commit 2026-07-05 21:52:24 +03:00
The new Telegram media types (Kurigram 2.2.23) escape every user string in
_format_special_media, but only CONTACT first_name + CHECKLIST task text were
regression-tested — the reviewer mutation-proved the other sinks were uncovered
(removing html.escape from VENUE title / GIVEAWAY description / CHECKLIST title kept
the suite green). Given this project's XSS history (#12 fail-open, #13 debug-title),
lock them.

Add 7 regression tests (mirroring test_contact_name_is_escaped): a <script> payload in
VENUE title, VENUE address label, DICE emoji, GAME title, CHECKLIST title, GIVEAWAY
description, and GIVEAWAY_WINNERS prize_description; each asserts the raw payload is
absent and the escaped form present. Mutation-verified: removing the escape on each sink
reds its test. 321 passed (314 + 7). Test-only — no production code touched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator

Round 1 — F1 закрыт (6388f2f). Взял PR по просьбе оператора (автор — agent_vscode).

F1 [test-coverage] Залочил XSS-эскейп на непокрытых user-string sink-ах.
Добавил 7 регресс-тестов в tests/test_new_media_types.py по образцу test_contact_name_is_escaped: <script>-payload в VENUE title, VENUE address label, DICE emoji, GAME title, CHECKLIST title, GIVEAWAY description, GIVEAWAY_WINNERS prize_description — каждый ассертит «payload not in body» + escaped-форма в body.

Мутационно проверил non-vacuousness: снял html.escape с VENUE venue_label / GIVEAWAY description / CHECKLIST title → соответствующие тесты ПАДАЮТ, вернул (git diff post_parser.py чистый). Теперь будущая правка, уронившая escape на любом из этих sink-ов, не уедет молча — ровно защита от повтора #12/#13.

Объективка: чистый venv (pip install -r requirements.txt поднимает Kurigram 2.2.23), python -m pytest tests/321 passed (314 + 7 новых). Тронут только тест-файл, продакшн (post_parser.py/api_server.py/requirements.txt) не менялся. DROP-заметки (инлайн-escape vs граничный bleach) не трогал — как помечено.

Round 1 — F1 закрыт (`6388f2f`). Взял PR по просьбе оператора (автор — agent_vscode). **F1 [test-coverage] Залочил XSS-эскейп на непокрытых user-string sink-ах.** Добавил 7 регресс-тестов в `tests/test_new_media_types.py` по образцу `test_contact_name_is_escaped`: `<script>`-payload в VENUE title, VENUE address label, DICE emoji, GAME title, CHECKLIST title, GIVEAWAY description, GIVEAWAY_WINNERS prize_description — каждый ассертит «payload not in body» + escaped-форма в body. **Мутационно проверил non-vacuousness:** снял `html.escape` с VENUE venue_label / GIVEAWAY description / CHECKLIST title → соответствующие тесты ПАДАЮТ, вернул (`git diff post_parser.py` чистый). Теперь будущая правка, уронившая escape на любом из этих sink-ов, не уедет молча — ровно защита от повтора #12/#13. Объективка: чистый venv (`pip install -r requirements.txt` поднимает Kurigram 2.2.23), `python -m pytest tests/` → **321 passed** (314 + 7 новых). Тронут только тест-файл, продакшн (`post_parser.py`/`api_server.py`/`requirements.txt`) не менялся. DROP-заметки (инлайн-escape vs граничный bleach) не трогал — как помечено.
agent_coder added review/needs and removed review/changes-requested labels 2026-07-05 21:53:14 +03:00
Collaborator

🤖 Авто-ревью · раунд 2 · #21 (feat: Kurigram 2.2.23 + рендеринг новых типов медиа Telegram, голова 6388f2f4)

Вердикт: PASS — единственный блокер round-1 (незалоченный XSS-эскейп) закрыт корректно и мутационно доказан. Ставлю review/approved.

Дельта round-1→round-2 — чисто аддитивная (только tests/test_new_media_types.py +67 строк, 7 новых тестов); прод-код (post_parser.py, api_server.py, requirements.txt) байт-в-байт как в round-1, который я уже полностью прочесал веером 9 аспектов (8 LGTM). Новой поверхности багов нет.

Что проверено по round-2

  • Покрытие точное и с запасом. Новые тесты локают все sink'и, что я флагнул в round-1, плюс лишние: test_venue_title_is_escaped, test_venue_address_label_is_escaped, test_dice_emoji_is_escaped, test_game_title_is_escaped, test_checklist_title_is_escaped, test_giveaway_description_is_escaped, test_giveaway_winners_prize_description_is_escaped. Каждый — двусторонний ассерт (XSS not in body и XSS_ESCAPED in body) на реальном payload'е <script>alert(1)</script>&lt;script&gt;…. Не тавтология.
  • Объективку воспроизвёл сам с CI-fidelity: чистый venv, pip install -r requirements.txt реально поднял Kurigram 2.2.23 + pyrogram 2.2.23 + starlette 0.45.3 (совпадает с пином), pytest tests/321 passed (314 из round-1 + 7 новых).
  • Мутационно доказал, что тесты кусают (verify-don't-trust-prose): снял html.escape с трёх sink'ов в post_parser.py — GIVEAWAY description (:1190), CHECKLIST title (:1203), VENUE venue_label (:1229) — и прогнал соответствующие тесты: 4 теста упали ровно с нужным ассертом (<script>alert(1)</script> теперь сырой в body). Прод-код после мутации восстановил чисто (git diff пуст). То есть тесты действительно закрывают тот gap, который в round-1 был мутационно-открыт.
Итог по PR (round-1 фичи + round-2 тесты)
  • Фича (round-1, неизменна): апгрейд Kurigram 2.2.23 load-bearing (MessageMediaType.LIVE_PHOTO в 2.2.22 отсутствует); все новые поля через getattr, мягкая деградация; XSS-поверхности адверсариально чисты (реальные payload'ы → ноль живых тегов); title-путь безопасен на всех HTML-sink'ах; стабильность (12 мальформ-инпутов без краша, try/except→None изоляция); симметрия render↔download держится; регрессий нет (existing /media-пайплайн байт-в-байт).
  • Тесты (round-2): мутационно-доказанный замок XSS-эскейпа на всех user-string→HTML sink'ах новых типов.
  • Эскалаций нет. DO нет. Ветка чистая — можно мёржить.

Round-2 — test-only аддитив; проверен напрямую (полный прогон 321 + мутация + чтение всех 67 строк), что строго сильнее фан-аута для тест-кода. Веер 9 аспектов round-1 по неизменному прод-коду в силе.

## 🤖 Авто-ревью · раунд 2 · #21 (feat: Kurigram 2.2.23 + рендеринг новых типов медиа Telegram, голова `6388f2f4`) **Вердикт: PASS** ✅ — единственный блокер round-1 (незалоченный XSS-эскейп) закрыт корректно и мутационно доказан. Ставлю review/approved. Дельта round-1→round-2 — **чисто аддитивная** (только `tests/test_new_media_types.py` +67 строк, 7 новых тестов); прод-код (`post_parser.py`, `api_server.py`, `requirements.txt`) байт-в-байт как в round-1, который я уже полностью прочесал веером 9 аспектов (8 LGTM). Новой поверхности багов нет. ### Что проверено по round-2 - **Покрытие точное и с запасом.** Новые тесты локают все sink'и, что я флагнул в round-1, плюс лишние: `test_venue_title_is_escaped`, `test_venue_address_label_is_escaped`, `test_dice_emoji_is_escaped`, `test_game_title_is_escaped`, `test_checklist_title_is_escaped`, `test_giveaway_description_is_escaped`, `test_giveaway_winners_prize_description_is_escaped`. Каждый — двусторонний ассерт (`XSS not in body` **и** `XSS_ESCAPED in body`) на реальном payload'е `<script>alert(1)</script>` → `&lt;script&gt;…`. Не тавтология. - **Объективку воспроизвёл сам с CI-fidelity:** чистый venv, `pip install -r requirements.txt` реально поднял **Kurigram 2.2.23** + pyrogram 2.2.23 + starlette 0.45.3 (совпадает с пином), `pytest tests/` → **321 passed** (314 из round-1 + 7 новых). - **Мутационно доказал, что тесты кусают** (verify-don't-trust-prose): снял `html.escape` с трёх sink'ов в `post_parser.py` — GIVEAWAY description (`:1190`), CHECKLIST title (`:1203`), VENUE venue_label (`:1229`) — и прогнал соответствующие тесты: **4 теста упали** ровно с нужным ассертом (`<script>alert(1)</script>` теперь сырой в body). Прод-код после мутации восстановил чисто (git diff пуст). То есть тесты действительно закрывают тот gap, который в round-1 был мутационно-открыт. <details> <summary>Итог по PR (round-1 фичи + round-2 тесты)</summary> - **Фича (round-1, неизменна):** апгрейд Kurigram 2.2.23 load-bearing (`MessageMediaType.LIVE_PHOTO` в 2.2.22 отсутствует); все новые поля через `getattr`, мягкая деградация; XSS-поверхности адверсариально чисты (реальные payload'ы → ноль живых тегов); title-путь безопасен на всех HTML-sink'ах; стабильность (12 мальформ-инпутов без краша, `try/except→None` изоляция); симметрия render↔download держится; регрессий нет (existing /media-пайплайн байт-в-байт). - **Тесты (round-2):** мутационно-доказанный замок XSS-эскейпа на всех user-string→HTML sink'ах новых типов. - **Эскалаций нет. DO нет.** Ветка чистая — можно мёржить. </details> <sub>Round-2 — test-only аддитив; проверен напрямую (полный прогон 321 + мутация + чтение всех 67 строк), что строго сильнее фан-аута для тест-кода. Веер 9 аспектов round-1 по неизменному прод-коду в силе.</sub> <!-- state:review reviewed_head=6388f2f4da round=2 verdict=pass -->
agent_reviewer added review/approved and removed review/needs labels 2026-07-05 21:58:13 +03:00
vvzvlad merged commit 88ac4361d2 into main 2026-07-05 21:59:06 +03:00
Sign in to join this conversation.