· via dev.to (home feed)
Telegram export XSS traced to one unescaped field, a pattern in every HTML-generating app
A stored XSS in Telegram Desktop's HTML export ran from unescaped bot button text and sat dormant in group chats until someone exported and opened the file. The pattern affects any app that writes untrusted data into HTML.

A payload that waits for an export
According to a dev.to write-up, security researchers Denis Rostilov and Aleksander Rostilov of ExPatch Vulnerability Research found a stored cross-site scripting flaw in Telegram Desktop's chat export feature. Telegram shipped fixes in July 2026, and the researchers published details on September 12, 2026. The report notes that no CVE was ever assigned, that Telegram released no dedicated security advisory, and that the vulnerable code had been present in stable releases for roughly two years and four months.
The attack does not require the victim to visit a website. A bot sends a single message whose inline button carries a hidden payload. Someone forwards it into a group, where it can sit for months. When a compliance officer, journalist or lawyer later exports the conversation to HTML and opens the file, the script executes on page load, with no further click or warning.
One field skipped the escaper
Telegram Desktop's export code, in export_output_html.cpp, contains a helper called SerializeString() that escapes the five HTML-sensitive characters, converts newlines to br tags, and hex-encodes control characters. The dev.to report says the function was applied to message text, sender names and other user-controlled fields, but not to the text of inline keyboard buttons. The raw button string was appended straight into the generated document.
The fix, commit 8457d13a by Telegram Desktop developer John Preston, consists of routing that one field through SerializeString(). The same commit also closed a second injection in the same file, where copy-button content was placed inside a JavaScript string in an onclick attribute without escaping backslashes and single quotes. Two omissions, one file, both shipped for years.
Two consumers, two sets of rules
The desktop client renders messages with its own UI framework rather than a browser engine, so a literal script tag in button text displayed as plain characters. The researchers padded the payload with invisible Unicode so the button looked empty in the app. The flaw only materialised when the export pipeline, which does treat the text as HTML, wrote the same string into a file that a browser then interpreted as markup.
The dev.to piece frames this as a trust problem between consumers: identical data passes through two components with different parsing rules, and the safety of one is silently assumed for the other. Storing user input as JSON and later embedding it in HTML emails, webviews or PDF generators repeats the same mistake.
How the payload reaches a group
Three platform mechanics combined to make delivery easy. Bots fully control inline keyboard button text, and the Bot API accepts arbitrary Unicode there, markup included. Inline keyboards whose buttons are all URLs survive forwarding, so the crafted message can be relayed into any group by one of its members. And the bot never has to join the target group at all.
Telegram's bot privacy model, which hides ordinary group traffic from bots by default, is sidestepped entirely: the payload never reads messages through the Bot API. It reads them out of the exported file in the victim's browser, after the fact.
What the script could reach
Once the export opened with JavaScript enabled, the injected code could read everything in the document: message text, sender names and timestamps, chat metadata such as name, type and member count, plus the local file path through location.href, which leaks the operating system username and directory layout. The researchers' proof of concept rewrote the page into a fake Telegram-branded verification form, showing that rendered content, including names and timestamps, can be altered silently.
They rated the issue CVSS 3.1 8.2, High, with a scope change, because the impact lands in a context the Bot API was never meant to reach. One real limit exists: Telegram splits long exports into files of 1,000 messages, so a poisoned file exposes at most its own slice. But chat exports are used as records in disputes, investigations and compliance reviews, so a presentation-layer tampering vector aimed at an evidence file is serious on its own. The victim has no reason to doubt a file sitting on their own disk.
Updates do not fix files already on disk
The code was fixed in beta 6.9.4 on July 3, 2026 and in stable 7.0.1 on July 14, 2026, according to the report. Export files created before those releases are plain text on someone's drive, and a patched client does not rewrite them. Any old export may still be poisoned.
Why it matters
The bug is not really about Telegram. Any application that generates HTML from untrusted content — export features, report generators, invoice pipelines with an HTML intermediate step, test-result dashboards written to disk — has the same surface. Escaping failures rarely come from a team not knowing what escaping is; they come from a template where ten fields are escaped and the eleventh is assumed to be an inert label. Every value crossing into an HTML document needs the same gate, with no exceptions. Architectures where the same data reaches multiple consumers with different parsing rules deserve a second look. And generated artifacts outlive patches: fixing the generator does nothing for the files already sitting on disks.
- #security
- #xss
- #telegram
- #html-escaping
- #vulnerability