Skip to content

Commit c9bbe67

Browse files
amtiYoclaude
andcommitted
fix: only act on a panel message the caller is entitled to
Telegram lets a client send any callback data for any message it can see, not only the buttons it was shown. The owner id inside the callback data stops one person driving another's panel, but it does not stop somebody forging their own id against a message that was never a panel. In a group, Voicy's public messages are transcripts somebody asked for and everyone can read. A crafted callback naming one of them could delete it, or overwrite it with a settings screen. An ephemeral message stays addressable: it is already visible to exactly one person, so acting on it at that person's request destroys nothing that was not theirs. In a direct chat every message is that person's own, so the panel is still edited in place. This is the hole branchy closed in the same release. It was not introduced here, but shipping one bot with the guard and its sibling without it is not a choice worth making by accident. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FxNv1rk7jMQtb9tMBREc5B
1 parent 384bec6 commit c9bbe67

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ See [`docs/versioning.md`](docs/versioning.md) for what the numbers mean and
1313

1414
### Changed
1515

16+
- A panel callback only acts on a message it is entitled to act on. Telegram
17+
lets a client send any callback data for any message it can see, not only the
18+
buttons it was shown; the owner id inside the data stopped one person driving
19+
another's panel, but not somebody forging their own id against a public
20+
message. In a group Voicy's public messages are transcripts, so `close` could
21+
have deleted someone else's, and opening a tab could have overwritten one.
22+
Ephemeral messages are unaffected — they are already visible to one person —
23+
and a direct chat is that person's own.
24+
1625
- **The direct-chat command menu is one entry: `/start`.** `/stats`,
1726
`/language`, `/help` and `/about` were published as commands while already
1827
being buttons on the `/start` panel, so every one of them listed the same door

‎internal/bot/bot.go‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,17 +708,42 @@ func (b *Bot) handleCallback(ctx context.Context, act decide.Action, lang string
708708
return nil
709709
}
710710

711+
// panelIsAddressable reports whether a callback may act on the message it names.
712+
//
713+
// Telegram lets a client send any callback data for any message it can see, not
714+
// only the buttons it was shown. The owner id inside the data stops one person
715+
// driving another's panel, but not somebody forging their own id against a
716+
// message that was never a panel -- and in a group Voicy's public messages are
717+
// transcripts, which somebody asked for and everyone can read.
718+
//
719+
// So in a group only an ephemeral message is addressable: it is already visible
720+
// to one person, and acting on it at that person's request destroys nothing
721+
// that was not theirs. In a direct chat every message is that person's own, and
722+
// the panel is edited in place as before.
723+
func panelIsAddressable(act decide.Action) bool {
724+
if act.Ephemeral && act.EphemeralMessageID != 0 {
725+
return true
726+
}
727+
return act.Chat.Type == "private"
728+
}
729+
711730
func (b *Bot) editPanel(ctx context.Context, act decide.Action, text string, markup *tg.InlineKeyboardMarkup) error {
712731
if act.Ephemeral && act.EphemeralMessageID != 0 {
713732
return b.tg.EditEphemeralMessageText(ctx, act.ChatID, act.UserID, act.EphemeralMessageID, text, markup)
714733
}
734+
if !panelIsAddressable(act) {
735+
return nil
736+
}
715737
return b.tg.EditMessageText(ctx, act.ChatID, act.CallbackMessageID, text, markup)
716738
}
717739

718740
func (b *Bot) deletePanel(ctx context.Context, act decide.Action) error {
719741
if act.Ephemeral && act.EphemeralMessageID != 0 {
720742
return b.tg.DeleteEphemeralMessage(ctx, act.ChatID, act.UserID, act.EphemeralMessageID)
721743
}
744+
if !panelIsAddressable(act) {
745+
return nil
746+
}
722747
return b.tg.DeleteMessage(ctx, act.ChatID, act.CallbackMessageID)
723748
}
724749

0 commit comments

Comments
 (0)