Files
pad/internal
xarmian 2aa4f141e3 fix(security): require workspace membership on SSE subscriptions (TASK-264) (#937)
The SSE events stream (`GET /api/v1/events?workspace=`) resolves the
workspace via resolveWorkspace. The slug form is membership-scoped
(GetWorkspacesBySlugForUser → nil → 404), but the UUID form resolves
through the GLOBAL, unscoped GetWorkspaceByID. So an authenticated
non-member passing another workspace's UUID reached SubscribeIfAllowed
with a fully-resolved workspace and no explicit membership check.

Events are still fail-closed filtered by computeSSEVisibility and the
stream is torn down within ~60s by the revalidation tick, so it is not a
data leak — but the open connection is a connection-slot DoS and a
workspace-existence oracle (200+connected vs 404), and it diverged from
the explicit membership gate enforced by RequireWorkspaceAccess and the
collab sibling authorizeCollabAccess.

Add an entry membership/grant gate in handleSSE for regular (non-admin)
user-context callers: admit only direct workspace members or guest-grant
holders; otherwise return 404 (matching the slug path — a 403 would
itself be an existence oracle). Admin-via-cookie keeps its platform-wide
bypass; admin-via-bearer and the legacy-token / fresh-install paths are
gated by the pre-existing branches above and left untouched.

Regression test asserts a non-member is rejected with 404 at the SSE
entry for BOTH the slug and UUID forms (and consumes no connection
slot), while a legitimate member still connects (200 + connected).

Claude-Session: https://claude.ai/code/session_01EZ6yr6pAUFb1uffan912ra
2026-07-14 23:43:19 -04:00
..
2026-03-26 01:52:36 +00:00