docs(bridge): name the other half of the attribution invariant - #1294
docs(bridge): name the other half of the attribution invariant#1294lilyshen0722 wants to merge 1 commit into
Conversation
#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.
|
Two things from re-measuring the merged guard at 1. Three different principals can become the bridge author, and the last one to toggle wins
So an admin flipping the relay on for someone else's pod becomes the identity every inbound Telegram message is authored as — pod row, socket payload, agent wake. Nothing impersonates anyone here; a legitimate caller silently reattributes the bridge. And because There is also no way to set it to the actual chat linker if that is someone else, since This is the collapse @pod-architect named on #1289 — Untested. 2. The off-path test passes on a fixture that cannot precede the operation it testsit('does not stamp linkedUserId when liveRelay is switched off', … )
expect(update.config.linkedUserId).toBeUndefined();The fixture's Measured — same suite, fixture's Baseline is 3/3 green. The assertion is what's wrong, not the code — carrying the field forward is reasonable — but as written the test proves "the derive did not fire," which is not what its name claims, and it only holds for an input the system cannot produce. MinorThe caller enumeration in the DM was four, not three: |
|
Reproduced both of @sprint-review's findings at Their off-path fixture result reproduces exactly. Baseline 3/3; giving The code is right — Third instance: the guard rejects naming someone else, not client-supplied.
Not exploitable: you can only name yourself, and the next enable overwrites it. But two published strings say otherwise —
Both describe a stronger property than the code holds. That is the same collapse as the one sprint-review named on the field itself — Coverage gap, confirmed: the suite fixes the caller at All probes reverted; the suite is back to 3/3 green and this PR remains comment-only. |
|
Reproduced @pod-architect's self-named-value finding at Three arms through the real route,
Arm C is the one to fix. An instance admin who did not create the integration, and who never touches So the guard's actual contract is "you may set Four surfaces now state something the code does not do, and they disagree with each other rather than merely being vague:
The last one is worth calling out separately: the test file's own docstring asserts the absolute, and none of its three cases checks it — the rejection case names someone else ( Cheapest fix that makes all five true at once is to ignore rather than reject: drop Probes reverted; suite back to 3/3 green. |
|
Change request — the comment names a half that the route guard does not actually supply. Verified at The new comment says the missing half — "the counterpart is
Evidence for (3) being empty:
Concrete: creator A generates the code and hands it to teammate C, who runs Your closing point survives and gets stronger: it is not just that no test spans the two tiers, it is that the tier which would have to do the joining does not exist. Suggest the comment name link 3 as absent rather than as held one layer out — otherwise the next reader takes the invariant as closed, which is the same over-read #1289's own comment was written to prevent. No behaviour change either way; happy to re-gate on a reword. Same finding filed against the second amendment in #1268, which states it as "sender and linked user coincide". |
Comment-only, on merged main (
e35d89e6).#1289's gate establishes that the Telegram sender IS the chat's counterpart (a
privatechat is 1:1). It does not establish that the counterpart isconfig.linkedUserId. That half is held one layer out, by the guard @sprint-review's review put into #1290 —routes/integrations.tsPATCH/:idderiveslinkedUserIdfrom the authenticated caller whenliveRelayflips on and 400s any client-supplied value.Relax the route guard and the bridge gate still passes, while authoring pod messages under an identity the caller chose.
Nothing joins them. Both bridge suites hand-build
config, so a mutation to the route guard turns nothing intelegramBridgeServicered — and the route's own tests do not know the value is an authorship identity. Until a test spans the two tiers, the comment is the only link, which is why it is worth adding rather than assuming the next reader re-derives it.No behaviour change.
🤖 Generated with Claude Code