diff --git a/src/bot/services/ai/telegram_html.py b/src/bot/services/ai/telegram_html.py
index ed0f0e8..e384a57 100644
--- a/src/bot/services/ai/telegram_html.py
+++ b/src/bot/services/ai/telegram_html.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+from html import escape
import re
_BOLD_RE = re.compile(r"\*\*(.+?)\*\*")
@@ -15,7 +16,7 @@ def normalize_llm_output_for_telegram_html(text: str) -> str:
lines = text.splitlines()
normalized_lines: list[str] = []
for line in lines:
- normalized_lines.append(_BULLET_RE.sub("• ", line))
+ normalized_lines.append(_BULLET_RE.sub("• ", escape(line, quote=False)))
normalized = "\n".join(normalized_lines)
normalized = _BOLD_RE.sub(r"\1", normalized)
diff --git a/tests/test_telegram_html.py b/tests/test_telegram_html.py
index 2c8b4ed..b44856f 100644
--- a/tests/test_telegram_html.py
+++ b/tests/test_telegram_html.py
@@ -13,3 +13,15 @@ def test_markdown_bullets_to_dot_bullets() -> None:
text = "- first\n* second\n - third"
out = normalize_llm_output_for_telegram_html(text)
assert out.splitlines() == ["• first", "• second", "• third"]
+
+
+def test_escapes_raw_html_control_characters() -> None:
+ text = "Use x < y && y > 0 in ."
+ out = normalize_llm_output_for_telegram_html(text)
+ assert out == "Use x < y && y > 0 in <script>alert(1)</script>."
+
+
+def test_markdown_formatting_escapes_inner_html() -> None:
+ text = "**x < y** and `a & b`"
+ out = normalize_llm_output_for_telegram_html(text)
+ assert out == "x < y and a & b"