Repository navigation
Escape the language in hpre so it can't break the class attribute - #2644
Conversation
hpre() inserts language straight into the class attribute while escaping
content. A quote in the language ends the attribute early:
>>> hpre("x", language='a"b')
'<pre><code class="a"b">x</code></pre>'
Telegram rejects that as malformed HTML, and a crafted language adds
attributes instead: hpre("x", language='a" onmouseover="alert(1)') parses
as class="a" onmouseover="alert(1)".
Run language through escape_html() the same way content already is. Plain
values are unchanged, since html.escape() only touches & < > " '.
There was a problem hiding this comment.
🟢 Approval recommended
The narrowly scoped fix addresses the attribute injection issue, with focused regression coverage and no identified blocking issues.
No open issues 🎉
What changed in this PR
This PR fixes HTML attribute escaping in hpre() so quotes or crafted language values cannot break the generated class attribute or inject additional attributes.
Changes:
- Escape
languagewithescape_html()while preserving existing content-escaping behavior. - Add four regression tests covering ordinary language values, quotes, attribute injection, and content escaping.
| File | Description |
|---|---|
tests/test_formatting.py |
Adds regression coverage for safe hpre() HTML output. |
telebot/formatting.py |
Escapes the language value before inserting it into the class attribute. |
Review effort: Balanced
|
Do not see any reason for setting language like 'a"b' and passing "onmouseover" that is not supported by Telegram. FORMALLY you are right, 'a"b' is parsed incorrectly, butdo not see when 'a"b' should be used as language. |
|
Fair - lang, _, code = message.text.partition("\n")
bot.reply_to(message, hpre(code, language=lang), parse_mode="HTML")
|
|
👌 |
Description
hpre()escapescontentbut notlanguage, which goes straight into theclassattribute:Telegram rejects that as malformed HTML. A crafted language adds attributes instead of breaking the tag:
Parsing that back gives
('code', [('class', 'a'), ('onmouseover', 'alert(1)')]).The fix runs
languagethroughescape_html()the same waycontentalready is.html.escape()only touches& < > " ', so ordinary values likepythonorc++come out unchanged.mcode()has a similar problem - a backtick inlanguagecloses the code fence - but that needs a different fix, so I left it out of this one.Describe your tests
New
tests/test_formatting.py; the module had no tests before.Two cases fail on master and pass with the change: a quote in the language, and the attribute injection. Two more cover the plain-language path and confirm
contentis still escaped.Without the change: 2 failed, 2 passed. With it: 4 passed. Full suite: 288 passed, 2 skipped.
Python version: 3.14.9
OS: macOS
Checklist: