BuildNot yet confirmed elsewhere1 publisher2 min readPublished
One skipped escape call left Telegram Desktop's HTML export open to stored XSS for 28 months
Telegram patched a stored XSS in Telegram Desktop's HTML chat export in July, after the flaw shipped in stable releases for about 28 months. No CVE or advisory came with the fix, so teams that track exposure by CVE ID had nothing to match against.
The Engineer · Build desk
What happened
- Denis Rostilov and Aleksander Rostilov of ExPatch Vulnerability Research found the flaw and disclosed it on September 12, 2026.
- The export's SerializeString() function escaped message text, sender names and other user fields, but inline keyboard button text went into the file raw.
- The fixing commit also closed a second injection, where copy-button content went into a JavaScript string in an onclick handler with backslashes and single quotes unescaped.
- According to the write-up, opening the exported file runs the script with no further click and can send the page's messages, sender names, timestamps and local file path to a remote server.
Why it matters
- exposure The script runs for whoever needed the record, a compliance officer or lawyer in the write-up's example, so it executes on the machine that holds the exported archive.
- constraint Testing inside the Telegram client could not surface the bug, since its renderer showed the payload as inert text; only the generated HTML, opened in a browser, would expose it.
- decision Teams whose exports, report generators, HTML-to-PDF steps or on-disk test dashboards take user strings have to check every append into HTML, labels included, and not only the escaping helper.
According to the dev.to write-up of the findings, Telegram Desktop draws messages with its own UI framework, not a browser engine, so a literal `<script>` tag in a bot's button text appeared as plain characters [9]. The researchers padded the payload with invisible Unicode, and in the client they tested the button looked empty [10]. The HTML export does interpret that string as markup. It writes the string into a file, a browser opens the file, and the characters run as code [11].
One string had two consumers with different rules. The client was safe because of how it draws text, and nothing had cleaned the data itself [9][6]. The write-up says this split also removed the usual precondition for stored XSS: write access to the place the payload lands [13]. In its example, a bot message carrying the payload sits in a chat for months until someone exports it [17].
The escaping function itself was sound. SerializeString() in export_output_html.cpp escapes the five HTML-significant characters, converts newlines to `<br>` and hex-encodes ASCII control characters [4]. The failure was one call site. The vulnerable line was `block.append(button.text.toUtf8())` [6]. Commit 8457d13a, by Telegram Desktop developer John Preston, changed it to `block.append(SerializeString(button.text.toUtf8()))` [7]. Bug and fix differ by one function name and a pair of parentheses [6][7].
We would study the second injection hardest. Its context was a JavaScript string inside an HTML attribute [8]. Backslash is not among the characters SerializeString() handles [4], so that context needed escaping of its own.
The write-up describes the general failure this way: a template has eleven fields, ten get escaped, and the eleventh is a string treated as just a label [14]. For the Telegram lesson to apply to another app, three things have to hold. User-controlled strings reach an HTML writer. The primary display renders those strings safely by some other route. Someone opens the output in a browser. The Telegram export met all three [9][11].
In our view the fix that lasts is structural. An HTML writer should escape on every append by default and allow raw output only through a separately named call. Then an unescaped field has to be written on purpose, and a reviewer can see it.
What to watch
- Whether a CVE is assigned retroactively, or Telegram publishes an advisory naming the first Telegram Desktop release that carries commit 8457d13a.
- Whether other messaging clients with HTML export turn out to skip bot-controlled fields such as button labels in the same way.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence55
- Adoption
- Insufficient
- Hype gap+25
- Incentives
- Insufficient
- Confidence50
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
Security researchers Denis Rostilov and Aleksander Rostilov of ExPatch Vulnerability Research found a stored XSS in Telegram Desktop's HTML export feature, disclosed on September 12, 2026, after Telegram patched it in July.
- [2]
No CVE number was ever assigned, and Telegram published no dedicated security advisory.
- [3]
The vulnerable line of code sat in stable releases for roughly two years and four months.
- [4]
SerializeString() in export_output_html.cpp escapes the five HTML-dangerous characters (<, >, &, ", ') into entities, converts newlines into <br> tags, and hex-encodes ASCII control characters.
- [5]
The export applied SerializeString() to message text, sender names and other user-controlled fields, but did not apply it to inline keyboard button text.
- [6]
The vulnerable line was: block.append(button.text.toUtf8())
- [7]
The fix, commit 8457d13a by Telegram Desktop developer John Preston, is one function call: block.append(SerializeString(button.text.toUtf8()))
- [8]
The same commit fixed a second injection in the same file, where copy-button content was interpolated into a JavaScript string inside an onclick handler without escaping backslashes and single quotes.
- [9]
Telegram Desktop renders messages through its own UI framework, not a browser engine, so button text containing a literal <script> tag was displayed as plain characters.
- [10]
The researchers padded the payload with invisible Unicode characters, and the button looked completely empty in the app they tested.
- [11]
The export pipeline interprets the text as HTML and writes the same string into a file; a browser then opens that file and the characters become code.
- [12]
Opening the exported file runs the script with no second click and no warning dialog; every message on the page, plus sender names, timestamps and the local file path, can be sent to a server on the internet, and the page can be rewritten.
- [13]
A stored XSS usually requires the attacker to have write access to the place where the payload lands; according to the write-up, this one did not.
- [14]
because a template has eleven fields, ten get escaped, and the eleventh is a string that 'is just a label.'
- [15]
Software sharing the risk includes export features, report generators, invoice PDF pipelines with an HTML intermediate step, test-result dashboards written to disk, and user input stored as JSON and later serialized into HTML emails, webviews or HTML-rendering PDF generators.
- [16]
The vulnerable line shipped in stable releases for about 28 months.
- [17]
In the write-up's scenario, a bot sends one message with an invisible payload, it is forwarded into a large group, and it sits dormant for months until someone exports the chat to HTML.
- [18]
In the write-up's scenario, the person who exports the chat and double-clicks the file is someone who needs a record of the conversation, such as a compliance officer, a journalist or a lawyer.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toThe Telegram Export XSS Is Not About Telegram. It Is About Every App That Generates HTML.
1 article · October 10, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Topics
- Output encoding and HTML escapingFollow
- Cross-Site ScriptingFollow
- Vulnerability DisclosureFollow