The admin platform role granted owner-level access to every workspace on
every surface, including bearer-borne callers (PATs on /api/v1, CLI
session bearers, PATs/OAuth on /mcp). A user with "All current
workspaces" consent on an MCP client — or a leaked admin token of any
kind — could reach data the admin never joined.
Policy: the admin global bypass now fires only for cookie session auth
(web UI / SPA / /console/admin). Bearer-borne callers fall back to a
strict workspace_members check (membership-only; no guest-grants
fallback either).
Gated four sites in lockstep:
- RequireWorkspaceAccess (internal/server/middleware_auth.go) — covers
/api/v1/* routes; emits the existing not_a_member MCP authz denial
metric on bearer-admin denials.
- handleSSE entry (internal/server/handlers_events.go) — adds an
explicit GetWorkspaceMember check for bearer-borne admin after
resolveWorkspace's global slug lookup.
- sseSubscriberStillHasAccess (internal/server/handlers_events.go) —
per-tick revalidation now matches entry-time policy.
- computeSSEVisibility (internal/server/handlers_events.go) —
bearer-admin gets a real VisibleCollectionIDs filter instead of
"no filtering"; a bearer-admin who's a member with
collection_access=specific is now correctly scoped.
- authorizeCollabAccess (internal/server/handlers_collab.go) —
WebSocket collab upgrade gate; same membership-only stance.
New shared helper isBearerAuth(r) folds two signals (Authorization:
Bearer header OR ctxIsAPIToken stash) so MCP-dispatcher synthesized
requests and CLI session bearers are both covered. Mirrors the dual
check middleware_csrf.go already uses.
Tests (9 total):
- middleware_auth_admin_token_gate_test.go (5) — PAT denied/allowed
permutations, CLI session-bearer denial, cookie-session bypass
preserved.
- handlers_admin_bearer_gate_test.go (4) — SSE revalidation,
visibility filter, collab WebSocket auth.
Companion BUG-1617 (store-layer admin bypass in backlinks visibility)
tracked separately.