From 6a74d8d1c6e246e1b48152246a4b1217958a08d3 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:16:40 -0700 Subject: [PATCH] docs(bridge): name the other half of the attribution invariant #1289's private-chat gate proves the Telegram sender is the chat's counterpart. It does not prove the counterpart is config.linkedUserId -- that is held by the PATCH /api/integrations/:id guard added in #1290, which derives linkedUserId from the authenticated caller and rejects a client-supplied value. The two halves live in different files at different tiers and no test joins them: both bridge suites hand-build config, so a mutation to the route guard turns nothing in the bridge red. Comment only. --- backend/services/telegramBridgeService.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/services/telegramBridgeService.ts b/backend/services/telegramBridgeService.ts index feb898a36d..3a5043803e 100644 --- a/backend/services/telegramBridgeService.ts +++ b/backend/services/telegramBridgeService.ts @@ -185,6 +185,15 @@ export const relayTelegramMessageToPod = async (opts: { return { relayed: false }; } + // The gate proves the Telegram sender IS the chat's counterpart. It does NOT + // prove the counterpart is `linkedUserId` — that half is held one layer out, + // by the guard in `routes/integrations.ts` (PATCH /:id), which derives + // `config.linkedUserId` from the authenticated caller when `liveRelay` flips + // on and rejects any client-supplied value. Relax that guard and this gate + // still passes while authoring under an identity the caller chose. No test + // joins the two: both bridge suites hand-build `config`, so a mutation to the + // route guard turns nothing here red. + // // Attribution gate. Every relayed message is authored as `linkedUserId`, so // relaying is only honest where Telegram itself guarantees the sender IS that // person: a `private` chat is 1:1 between the bot and one user. In a group,