SevyDevy

Guide · Backend

Telegram Desktop's October 2026 Bug Explained: A Semicolon Put Sessions at Risk

18 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • The Telegram Desktop vulnerability CVE-2026-107181 let one crafted link make the app upload local files, including login session files, to an attacker's channel. A CVE is a public ID for a security bug.
  • A clicked link can start a second copy of the app. That copy hands the link to the running copy over a local socket, a private channel on your machine, as text records ending in a semicolon.
  • Before version 7.2.9 the link was not escaped, meaning its special characters were not rewritten into harmless codes. So a semicolon inside it ended the record early and started a new one.
  • Version 7.2.9 escapes the semicolon, ignores commands that arrive with a link and deletes the old file-sending helper. Check yours under Settings, Advanced, Version and updates.
  • Beginners can stop after the section on delimiter injection, the bug class behind this. Engineers then get what the 7.2.8 source checked, why an older escape function missed the semicolon, and two tested fixes.

A link that contains the app's own separator character can turn into two instructions. That is what happened in the Telegram Desktop vulnerability tracked as CVE-2026-107181, fixed in version 7.2.9.

Picture a common setup. A team ships a desktop chat app that keeps only one copy running. When you click one of its links in a browser, the system starts a second copy just long enough to pass the link on. The setup is made up, but the problem is real.

The second copy writes the link into one line of text and sends it to the first copy over a local socket. To mark where one message ends, the team puts a semicolon after it. The first copy cuts the line at every semicolon and acts on each message.

Now ask the question the team skipped. What if a link itself contains a semicolon? Then the first copy cuts the link in two, and the tail becomes a new message that nobody meant to send.

Which words do you need before the Telegram Desktop vulnerability makes sense?

You need five: local socket, single instance, record and delimiter, escaping, and session files. Each gets a plain meaning and an example from the team's app.

Local socket. Two programs on one machine often talk through inter-process communication, or IPC. A local socket is one kind of IPC: a private channel that never leaves the computer. Example: the app's second copy talks to the first over one.

Single instance. A single-instance app keeps one copy running. A second launch hands its input to the first copy and exits. Example: clicking a second link does not open a second chat window.

Record and delimiter. A record is one message in a stream of messages. A delimiter is the character that marks where a record ends. Example: the team's app uses a semicolon as its delimiter.

Escaping. Escaping rewrites a special character into a harmless code before sending, and turns it back after reading. Example: a semicolon could travel as a percent sign plus a few digits, so the reader never sees a bare semicolon.

Session files. Session files are the small files that keep you logged in, including the keys that prove who you are. Telegram Desktop stores them in a folder called tdata. The CVE record says the flaw could upload tdata session keys, which is why it calls the result account takeover.

What is the Telegram Desktop vulnerability in CVE-2026-107181?

It is a record-separator injection flaw in Telegram Desktop before version 7.2.9. A crafted tg:// link, the app's own link type, carried an unescaped semicolon. That let it add an extra OPEN record, the record type that asks the running copy to open a link.

The extra record pointed at interpret:, an internal link scheme. A scheme is the part of a link before the colon, like https or tg. That scheme led to an old helper that reads an instruction file and sends the file it names to a chat.

The instruction file must already be on the victim's disk. Cybernews reports the researcher posted such files in a group the victim was added to, where the app downloads small files automatically. The CVE record says this could upload tdata session keys to an attacker's channel.

The record format is plain text. Each record starts with a type, such as OPEN for a link, CMD for a command like show, or CTRL for a control message. Each ends with a semicolon. The bug class has a catalogue name: CWE-143, improper neutralisation of record delimiters.

This path runs when a clicked link launches a second copy of the app. The second copy wrote the link straight into the stream and added a semicolon after it. The running copy cut the stream at every semicolon. So a semicolon inside the link created a boundary the sender never meant.

You might expect URL encoding to stop this. It does not, because a semicolon is a legal URL character. The 7.2.8 code converted each link with the fully encoded form from Qt, the toolkit the app is built on. In our test on Qt 6.9.1, that form kept the semicolon as it was.

Follow the diagram from left to right. Notice that the text arrives intact. The socket delivers exactly what was sent, and only the reader's cut turns one record into two.

How one clicked link became two records

Notice the fork: the split, not the socket, creates the extra record that reaches the helper.

Diagram connections:

  • Clicked tg:// link to Second launch (System starts a new copy)
  • Second launch to Local socket (One line of text)
  • Local socket to Running copy splits
  • Running copy splits to Expected OPEN record (First record)
  • Running copy splits to Extra OPEN record (Second record)
  • Extra OPEN record to interpret: helper (Reaches the old path)
  • Clicked tg:// link: Carries a semicolon inside it
  • Second launch: Writes OPEN, the link, then a semicolon
  • Local socket: Second launch to running copy
  • Running copy splits: Cuts the text at every semicolon
  • Expected OPEN record: The first part of the link
  • Extra OPEN record: The tail, read as an interpret: link
  • interpret: helper: Reads an instruction file, sends a file

What is delimiter injection, and why is it a whole bug class?

Delimiter injection is when a value contains the character that separates values, so it escapes its own field and is read as structure. It has the same shape as SQL injection, where text rewrites a database query. In both, data is trusted as instructions.

Think of a shop order slip where items are separated by commas: "rice 2 kg, dal 1 kg". A customer who writes an item name containing a comma has quietly added an item. The slip is the stream, the comma is the delimiter and each item is a record.

Where the picture breaks: a shopkeeper might notice an odd item and ask. The program never asks. It applies the split rule to every character, every time.

You meet this anywhere a program glues values together with a special character. A comma in a CSV (comma-separated values) field and a newline in a log line can both fake a boundary when nothing encodes them. The table compares four ways to frame records.

Four ways to frame records, and what happens when a value holds the delimiter
Framing approachHow a boundary is markedIf a value holds the delimiterGood for
Raw delimiter (the 7.2.8 bug)A separator such as a semicolonThe value adds a new recordNothing; avoid it
Escaped delimiter (the 7.2.9 fix)A separator, with the separator and escape character encodedSafe only if every value is escaped firstSimple text protocols
Length-prefixingThe size of each field is written before itSafe; the reader never scans inside the dataBinary and mixed data
Structured formatA real parser reads a format such as JSONSafe if you use the parser, not string joinsRich, nested messages

If you are new to this topic, you can stop here: you know what broke, why, and that 7.2.9 fixes it. The sections below go into the source code, the fix line by line, and how to build framing that cannot be fooled.

What did the old file-send helper in Telegram Desktop 7.2.8 check?

Very little. In the 7.2.8 source, the helper reads an instruction file named by the interpret: link. It then sends one named file to a channel, with an optional caption. It shows no confirmation prompt.

Its checks are narrow. The instruction file and the named file must both exist on disk, and the channel must be one the app has already loaded. If the instruction file names a user, that user must be the one logged in; if it names none, nothing is checked.

Compare how the same code treats a plain local file path. In 7.2.8, a file path opens a box asking you to choose a chat. The interpret: path skipped that box and sent straight away. Nothing in this 7.2.8 code path limits it to support staff accounts.

Telegram's view, given to Cybernews, is that an attacker would also need the exact system path of the file. The 7.2.8 code does not require a full path: for the target file it checks only that the file exists. Cybernews reports the researcher used relative paths, so the victim's username was not needed.

How did Telegram fix the vulnerability in version 7.2.9?

The fix is commit db34056, titled "Remove legacy interpret path helper". It makes three changes, and the 7.2.9 source contains all three. Each one blocks a different step of the old chain.

Where each 7.2.9 change cuts the old chain

Notice the split of roles: escaping and deletion break this exact chain, while refusing mixed records guards the step in between.

Diagram connections:

  • Second launch writes to Running copy reads (Records over socket)
  • Running copy reads to Link handling (Links passed on)
  • Fix 1: escape to Second launch writes (No extra boundary)
  • Fix 2: refuse mixing to Running copy reads (Skips smuggled commands)
  • Fix 3: delete helper to Link handling (Nothing to reach)
  • Second launch writes: OPEN record for the clicked link
  • Running copy reads: Splits records, then acts on them
  • Link handling: Decides what each link does
  • Fix 1: escape: Semicolon sent as %003b
  • Fix 2: refuse mixing: No CMD, CTRL or file paths with a link
  • Fix 3: delete helper: interpret: scheme no longer handled

Fix 1: escape every link before it crosses the socket

The sender now escapes each link. A semicolon, a percent sign, control characters and anything beyond basic ASCII, the 128 standard text characters, become a percent sign plus four hex (base-16) digits. The reader splits first and decodes after, so a link can no longer add a boundary.

Note the format. This is not standard URL percent-encoding, which uses two hex digits. A semicolon travels as %003b, not %3B.

Fix 2: refuse records that should never arrive together

The reader now looks at each batch it receives. If the batch holds an OPEN record, any CMD or CTRL records in it are skipped. Once a connection delivers a link that is not a local file, local file paths on that connection are dropped and a warning is logged.

This does not block an extra OPEN record by itself. It is defence in depth for the case where escaping fails again. A smuggled command or control record that arrives in the same read as the link is skipped, and file paths are dropped for the rest of that connection. A comment in the 7.2.9 source states that reasoning.

Fix 3: delete the old interpret path

The interpret: scheme handling and the file-send helper are gone. In the 7.2.9 source, start links are no longer checked for that scheme, and the helper function no longer exists. Even a stray record can no longer reach the file-send path.

Why an older escape function was not enough

Older builds already had an escape helper. The 6.2.4 source, from October 2025, escapes control characters, non-ASCII and the percent sign, but not the semicolon. From the 6.2.5 beta, links went out with no escaping at all. The CVE still lists every version before 7.2.9 as affected.

The 7.2.9 helper finally adds the semicolon to the list. The lesson for your own code: an escape function protects you only if it covers the delimiter itself. Write a test that feeds it the delimiter.

How do you send messages between processes without delimiter injection?

Pick one of three safe habits: escape the delimiter, length-prefix each field, or use a structured format with a real parser. All three stop a value from acting as a boundary. A raw separator with no escaping, the 7.2.8 pattern, is the one to avoid.

The first sample shows the raw pattern next to an escaped one. It uses a harmless example.com link with a semicolon in it. The escape mirrors the 7.2.9 format for the characters it covers.

python
def write_raw(links):
    return "".join("OPEN:" + link + ";" for link in links)

def escape(text):              # % plus 4 hex digits, as 7.2.9 does
    out = ""
    for ch in text:
        if ord(ch) < 32 or ch in "%;":
            out += "%" + format(ord(ch), "04x")
        else:
            out += ch
    return out

def unescape(text):
    out, i = "", 0
    while i < len(text):
        if text[i] == "%":
            out += chr(int(text[i + 1:i + 5], 16))
            i += 5
        else:
            out += text[i]
            i += 1
    return out

def write_safe(links):
    return "".join("OPEN:" + escape(link) + ";" for link in links)

def read(stream):              # split on every ; like the reader
    return [r for r in stream.split(";") if r]

link = "https://example.com/menu;lunch"
print(read(write_raw([link])))
print(read(write_safe([link])))
print([unescape(r) for r in read(write_safe([link]))])

Check it worked: run it with Python 3. The first line prints two records, OPEN with the link cut short and a stray lunch. The second prints one record holding %003b. The third prints the original link back in one piece.

Length-prefixing goes further, because the reader never scans inside the data. You write each field's size in bytes, then the bytes. The reader takes exactly that many bytes, so no value can fake a boundary.

python
def pack(fields):
    out = b""
    for f in fields:
        data = f.encode("utf-8")
        out += str(len(data)).encode() + b":" + data   # size, colon, bytes
    return out

def unpack(stream):
    fields, i = [], 0
    while i < len(stream):
        j = stream.index(b":", i)
        n = int(stream[i:j])
        fields.append(stream[j + 1:j + 1 + n].decode("utf-8"))
        i = j + 1 + n
    return fields

packed = pack(["OPEN:https://example.com/menu;lunch", "note; with; semicolons"])
print(packed)
print(unpack(packed))

Check it worked: the first line shows 35 and 22 before the two fields, their sizes in bytes. The second line prints both fields whole, semicolons included. One caution: this toy accepts a cut-off stream, so a real socket reader must wait until all the promised bytes arrive. For rich messages, a structured format read by a proper parser gives the same safety with nested fields.

How do you check whether your Telegram Desktop is patched?

Open the app's settings and read the version. Anything at 7.2.9 or later has the fix. Linux users who install from a distribution's package manager should check the package version too, because distribution packages can lag behind.

  1. Read the version

    Open Settings, then Advanced, and look under Version and updates. The line should read Version 7.2.9 or higher. As of 19 October 2026 the newest stable release is 7.3.1.

  2. Update if it is older

    Click Check for updates in the same place, or install the latest build from the official site or your app store.

  3. Check distribution packages

    On Debian, run apt-cache policy telegram-desktop and read the Installed line. Debian's tracker lists its 4.6.5 and 5.7.2 packages as vulnerable and unfixed, as of 19 October 2026.

  4. Turn on two-step verification

    Do this before ending any session. Telegram's FAQ lists it first for a lost phone. It stops new logins with only your phone number; it does not end an open session.

  5. End the session that ran the old build

    If you clicked odd links on an old build, open Settings, then Devices, on another device. Terminate the desktop session that ran it, then log in again after updating.

  6. Harden builds you cannot update yet

    Cybernews reports the stolen files restore a session only if no local passcode is set. Set one under Settings, Privacy and Security, Local passcode.

  7. Limit automatic downloads in groups

    The reported delivery path was a group file the app downloaded by itself. Under Settings, Advanced, Automatic media download, turn off files in groups until you update.

Why is CVE-2026-107181 rated high and not critical?

Because a user has to click. CVSS, the standard 0-to-10 severity score, rates the flaw 8.1 in version 3.1 and 8.6 in version 4.0. Both are high; critical starts at 9.0.

The user-interaction setting alone makes the difference. We recomputed both published vectors with the cvss Python library, version 3.4. Change only user interaction to none and the scores become 9.1 and 9.3, both critical.

The two sides disagree on how much interaction is needed. Telegram told Cybernews a user would have to click and then confirm a suspicious link. The researcher calls it one click and says the file send itself asks nothing. Either way, update.

How do you explain the Telegram Desktop vulnerability in a system design interview?

Start with the bug class, then the chain, then the fix. The steps below are a frame you can say out loud in about a minute.

  1. Name the bug class

    Call it delimiter injection, catalogued as CWE-143. A link carried the record separator, so one message became two.

  2. Trace the chain

    A link crossed the single-instance socket unescaped. A hidden semicolon split it, and the extra record reached an old file-send helper with no prompt.

  3. State the impact and its limits

    The helper could upload session files, so one link could lead to account takeover. A user had to click, which keeps the score at high, not critical.

  4. Give the fix

    Escape records including the delimiter, refuse commands that arrive with a link, and delete the old file-send path.

  5. Draw the lesson

    Keep data separate from the marks that frame it. Prefer length-prefixing or a real parser, and test your escaping with the delimiter itself.

Questions people ask

What is CVE-2026-107181 in simple terms?
It is a flaw in Telegram Desktop before version 7.2.9. The app passed clicked links to its running copy as text, with a semicolon after each record. A link that hid a semicolon could add an extra record. That record could reach an old helper that uploaded a named file, including session files.
Am I still at risk if I use Telegram Desktop?
Not from this flaw if you run version 7.2.9 or later. Check under Settings, Advanced, Version and updates. Linux distribution packages can lag: as of 19 October 2026, Debian's tracker lists its packages as unfixed. If you are unsure about an old build, update, then end the desktop session that ran it from another device.
What is delimiter injection?
It is when a value contains the character used to separate values. The value then breaks out of its field and is read as structure, such as a new record or command. It has the same shape as SQL injection. The cure is to keep data separate from the marks that frame it, by escaping or by counting bytes.
Does URL encoding stop delimiter injection?
Not on its own. A semicolon is a legal URL character, so URL encoding can leave it untouched. In our test, Qt 6.9.1's fully encoded form kept a semicolon as it was. You need an escape step built for your record format, one that covers your delimiter.
Why did the record reach a file-send handler?
The extra record was a link using interpret:, an internal scheme. In 7.2.8, that scheme led to a helper that read an instruction file and sent one named file to a channel. It asked for no confirmation. Version 7.2.9 removed the scheme handling and the helper.
Was this exploited in the wild?
None of the sources we checked reports real-world abuse, as of 19 October 2026. Telegram told Cybernews the issue was patched in mid-September and that up-to-date desktop apps are safe. The write-up came out in October, after the fix shipped. The technique is now public, so update promptly.

Sources and further reading

Practise this with Lala

Practise