test(media): lock XSS escaping on new-media-type user-string sinks (#21 review F1)
Docker Image CI / build (pull_request) Has been cancelled

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>
This commit is contained in:
2026-07-05 21:52:03 +03:00
parent f9550d8330
commit 6388f2f4da
+67
View File
@@ -371,6 +371,73 @@ def test_checklist_task_text_is_escaped(parser):
assert "📝 My list" in body
def test_venue_title_is_escaped(parser):
# VENUE title feeds venue_label -> html.escape(venue_label)
venue = SimpleNamespace(title=XSS, address="1 Main St",
location=SimpleNamespace(latitude=1.5, longitude=2.5))
msg = make_message(303, media=MessageMediaType.VENUE, venue=venue)
body = parser._generate_html_body(msg)
assert XSS not in body
assert XSS_ESCAPED in body
def test_venue_address_label_is_escaped(parser):
# VENUE address also feeds venue_label -> html.escape(venue_label)
venue = SimpleNamespace(title="Blue Bottle", address=XSS,
location=SimpleNamespace(latitude=1.5, longitude=2.5))
msg = make_message(304, media=MessageMediaType.VENUE, venue=venue)
body = parser._generate_html_body(msg)
assert XSS not in body
assert XSS_ESCAPED in body
def test_dice_emoji_is_escaped(parser):
# DICE emoji -> html.escape(str(dice_emoji))
msg = make_message(305, media=MessageMediaType.DICE,
dice=SimpleNamespace(emoji=XSS, value=6))
body = parser._generate_html_body(msg)
assert XSS not in body
assert XSS_ESCAPED in body
def test_game_title_is_escaped(parser):
# GAME title -> html.escape(game_title.strip())
msg = make_message(306, media=MessageMediaType.GAME,
game=SimpleNamespace(title=XSS))
body = parser._generate_html_body(msg)
assert XSS not in body
assert XSS_ESCAPED in body
def test_checklist_title_is_escaped(parser):
# CHECKLIST title -> html.escape(title_str)
checklist = SimpleNamespace(title=XSS, tasks=[])
msg = make_message(307, media=MessageMediaType.CHECKLIST, checklist=checklist)
body = parser._generate_html_body(msg)
assert XSS not in body
assert XSS_ESCAPED in body
def test_giveaway_description_is_escaped(parser):
# GIVEAWAY description -> html.escape(description.strip())
giveaway = SimpleNamespace(quantity=5, months=None, stars=None,
until_date=None, description=XSS)
msg = make_message(308, media=MessageMediaType.GIVEAWAY, giveaway=giveaway)
body = parser._generate_html_body(msg)
assert XSS not in body
assert XSS_ESCAPED in body
def test_giveaway_winners_prize_description_is_escaped(parser):
# GIVEAWAY_WINNERS prize_description -> html.escape(prize_description.strip())
winners = SimpleNamespace(winner_count=2, quantity=5, prize_description=XSS,
unclaimed_prize_count=0, winners=[])
msg = make_message(309, media=MessageMediaType.GIVEAWAY_WINNERS, giveaway_winners=winners)
body = parser._generate_html_body(msg)
assert XSS not in body
assert XSS_ESCAPED in body
# ---------------------------------------------------------------------------
# 7. Giveaway / giveaway winners info blocks
# ---------------------------------------------------------------------------