Skip to content

fix(messages): only delete messages that belong to the given chat - #320

Merged
chigwell merged 2 commits into
chigwell:mainfrom
axisrow:fix/delete-messages-chat-scope
Oct 17, 2026
Merged

chigwell merged 2 commits into
chigwell:mainfrom
axisrow:fix/delete-messages-chat-scope

Conversation

@axisrow

@axisrow axisrow commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Related to #317 — this hardens the server against the class of cross-chat deletion described there. Whether that incident was caused by this server has not been established; see the discussion in the issue.

What this changes

delete_message and delete_messages_bulk now verify that every ID they are asked
to delete actually belongs to chat_id before calling Telegram. If any ID is a
message of another chat, nothing is deleted and the tool reports which IDs are
foreign. Channel/supergroup deletes are skipped by the check — Telegram scopes
those by channel already.

Why the old behavior was hazardous for private chats

Private chats and basic groups share one account-wide message counter. For them
Telethon sends messages.deleteMessages with bare IDs (and Telethon's default
revoke=True), so a delete meant for chat A with a stale or shifted ID removes the
message that currently holds that ID in chat B — for both sides. A single tool
call with wrong IDs can wipe outgoing messages across several unrelated private
chats — the class of loss described in #317, though the cause of that incident has
not been established.

Telethon.get_messages(entity, ids=...) returns None for an ID that does not
belong to that chat, so the check is one extra read before any delete — no extra
API surface, no behavior change for correct calls.

Tests

tests/test_delete_messages.py runs the real Telethon request path against a
fake in-memory account where private chats share the counter:

  • delete_message refuses a message belonging to another chat and issues no
    DeleteMessagesRequest.
  • delete_message still deletes a message that does belong to the chat.
  • delete_messages_bulk refuses atomically when any ID is foreign.
  • delete_messages_bulk deletes a valid set.
  • Channel deletes skip the ownership lookup — covered for both the single-message
    and the bulk tool (Telegram scopes channel deletes itself).

Scope note

This PR removes the only plausible server-side path for cross-chat deletions, but it cannot confirm whether the server caused #317 — the reporter's Codex logs did not show delete calls either.

🤖 Generated with Claude Code

In private chats and basic groups Telegram numbers messages with one
account-wide counter, so messages.deleteMessages takes bare IDs and deletes
the message from whichever chat holds it, for both sides. delete_message and
delete_messages_bulk passed chat_id only to pick the method, so a wrong or
injected ID deleted messages in another chat, including one outside
TELEGRAM_ALLOWED_CHAT_IDS. Telethon documents that delete_messages does not
check that the IDs belong to the chat.

Both tools now look the IDs up in the chat first (get_messages(entity,
ids=...) returns None for an ID from another chat) and delete nothing when
any ID is not there. Channel deletes are already scoped by Telegram and skip
the lookup.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018Xun6C4mXUAKbVSmiEEh79
AO review approved with one optional coverage note: the channel
short-circuit was only exercised via delete_messages_bulk. Add the
one-case test for the single-message tool.

Co-Authored-By: Claude Code <[email protected]>
@chigwell
chigwell merged commit d2e7db0 into chigwell:main Oct 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants