The gap
POST /sse/subscribe (apps/api/src/app/sse/sse.controller.ts) takes an eventType and an arbitrary params array and passes both to sseService.subscribeUser without checking that the caller may see the thing params names. Any authenticated user who knows a space id can therefore subscribe to that space's channel, whatever organization it belongs to.
Two event types are affected today:
What leaks
Activity, not content. Both events are deliberately thin — SPACE_CONTENT_CHANGED carries { organizationId, spaceId } and nothing else, precisely because a subscriber is not checked against the space it names, and every subscriber learns what changed by refetching through endpoints that do check. So a subscriber to a space they do not belong to learns that something changed in it, and when — not what, and not enough to read any of it.
That is a low ceiling, which is why #518 shipped rather than widening into this. It is still a timing side channel across organization boundaries, and it should not be the reason the payload has to stay thin forever.
What would close it
A membership check in the subscribe endpoint, per event type, before the subscription is registered — space-scoped types resolve params[0] as a space and check the caller belongs to it; unknown event types are refused rather than allowed by default. Both event types above gain it at once, so CHANGE_PROPOSAL_UPDATE subscribers need checking against the same rule.
Raised by Greptile on #518 (P2, security), and called out in that PR's own reviewer notes.
The gap
POST /sse/subscribe(apps/api/src/app/sse/sse.controller.ts) takes aneventTypeand an arbitraryparamsarray and passes both tosseService.subscribeUserwithout checking that the caller may see the thingparamsnames. Any authenticated user who knows a space id can therefore subscribe to that space's channel, whatever organization it belongs to.Two event types are affected today:
SPACE_CONTENT_CHANGED— added in Refacto/use sse on packages page #518, scoped by space id;CHANGE_PROPOSAL_UPDATE— pre-existing.What leaks
Activity, not content. Both events are deliberately thin —
SPACE_CONTENT_CHANGEDcarries{ organizationId, spaceId }and nothing else, precisely because a subscriber is not checked against the space it names, and every subscriber learns what changed by refetching through endpoints that do check. So a subscriber to a space they do not belong to learns that something changed in it, and when — not what, and not enough to read any of it.That is a low ceiling, which is why #518 shipped rather than widening into this. It is still a timing side channel across organization boundaries, and it should not be the reason the payload has to stay thin forever.
What would close it
A membership check in the subscribe endpoint, per event type, before the subscription is registered — space-scoped types resolve
params[0]as a space and check the caller belongs to it; unknown event types are refused rather than allowed by default. Both event types above gain it at once, soCHANGE_PROPOSAL_UPDATEsubscribers need checking against the same rule.Raised by Greptile on #518 (P2, security), and called out in that PR's own reviewer notes.