Repository navigation
Description
Affected layers
- Backend (
server/): affected.pairChatinserver/src/telegram.tsrenews an expired pairing code once, before it starts asking Telegram for pending messages, and then accepts the first private/startcarrying the code it captured at that moment. After each await it checks only that the bot and chat have not changed.renewExpiredPairingCode, whichGET /api/telegramalso runs, replaces the code without counting as such a change. A check that started while the code was valid therefore keeps accepting it after it expires, and after the settings page has shown a new link. - Frontend (
src/): not affected. The Telegram card shows the link the backend returns and calls the pair route when the operator presses the check button; expiry and matching are decided on the backend. shared/: not affected.
Problem
The pairing link is a bearer code valid for 15 minutes, and the first private chat that sends /start with it is paired. A check the operator started just before the code expired keeps accepting the old code until it finishes, which takes up to 10 batches of pending messages, each request bounded by a 10-second timeout. Reproduced on main at 828dad4 with the Loupe test under Node 24: a /start with the old code from another private chat, sent after expiry, is paired, both when the link had only expired and when the settings page had already replaced it.
Impact
Someone holding a leaked pairing link can pair their own chat after the link expired, if the operator's check is running at that moment. That chat then receives alerts and status summaries and can change alert settings. The window is the few seconds of one running check around the expiry instant. A leaked link that has not expired already lets its holder pair outright by sending /start before the operator, so this adds seconds to the link's 15 minutes rather than a new way in. The settings page shows the paired recipient's name, so a wrong chat is visible. Telegram is opt-in. Loupe rates it medium.
Expected outcome
- A pairing link pairs a chat only while it is valid and is still the current link. A
/startwith an expired code, or with a code the settings page has replaced, never pairs a chat, whenever the check that sees it started. - When a check sees only such a code, nothing is paired or saved, and the operator is told to use the current link.
Loupe findings
- project-loupe/audit-sv2-ui#111: a running pairing check still accepts the code it started with after that code expires or is replaced
Related issues and PRs
- Add Experimental Telegram integration to get notifications (block found, best diff, workers connections) #228 added chat pairing with a 15-minute link.
- Shares
TelegramServicewith telegram: a disconnected chat receives nothing more and is never paired again without a new link #404, which is a separate defect. - Tracker: [Tracker] Telegram notifications #402.
Activity