New request
#20483: User-scoped navigation menu items can leak into other users' Favorites/sidebar
We'll provision a sandbox, run an agent against the issue, and open a draft PR. You can pull the branch and iterate from there.
(from Val's Codex)
Bug Description
Personal Favorites/sidebar navigation entries can leak across users in the same workspace.
Observed in a self-hosted Twenty workspace on twentycrm/twenty:v2.0.0: a low-permission workspace member created an unlisted view, added it to Favorites, and the favorite later appeared at the top of the sidebar while impersonating another workspace member. The intended row is user-scoped, but the UI can still display it for other users.
I did not attach the production screenshot because it contains internal workspace object names, but the visible behavior was: a test favorite created by one member appeared under Favorites for another impersonated member.
The server-side read/mutation model seems mostly correct:
ViewAccessService.canUserCreateViewallowsUNLISTEDviews for authenticated users, whileWORKSPACEviews requirePermissionFlagType.VIEWS.NavigationMenuItemService.findAllreturns workspace nav items plus only the current user'suserWorkspaceIdrows.NavigationMenuItemAccessServicerequiresLAYOUTSfor workspace-level nav items and lets users mutate only their own user-level nav items.
The leak appears to be in the live metadata/client-store path:
MetadataEventPublisher.publishbroadcastsnavigationMenuItemmetadata events workspace-wide.MetadataStoreSSEEffectapplies create/update metadata events directly into every client metadata store.useNavigationMenuItemsDatatreats every item with a defineduserWorkspaceIdas a personal favorite/sidebar item, without checking that it belongs tocurrentWorkspaceMemberId.
That means if user A creates/updates a personal navigationMenuItem, user B's active client can receive it through SSE, store it locally, and render it in user B's Favorites/sidebar.
Related: #19832 fixed the same class of cross-user metadata event contamination for agentChatThread by adding recipientUserWorkspaceIds, but that fix does not appear to be applied to navigationMenuItem events.
I checked the relevant code path in v2.0.0 and later tags through v2.4.0; the frontend hook still filters only on isDefined(item.userWorkspaceId), and navigation menu item metadata events still appear to be broadcast workspace-wide.
Expected behavior
User-scoped navigation menu items should stay isolated to the owning workspace member.
Concretely:
- Any workspace member should be able to create unlisted/personal views.
- Any workspace member should be able to favorite views, create personal favorite folders, and rearrange their own sidebar/favorites.
- Those favorites/folders/reordering changes should not appear for other workspace members.
- Workspace/global navigation changes should remain restricted to users with the appropriate high-trust permission, currently
LAYOUTSfor workspace-level nav items.
Technical inputs
Likely changes:
-
Restrict
navigationMenuItemmetadata broadcasts when the event is user-scoped.WorkspaceEventBroadcasteralready supportsrecipientUserWorkspaceIds. FornavigationMenuItemcreate/update events where the effective record hasuserWorkspaceId, passrecipientUserWorkspaceIds: [userWorkspaceId]. Workspace-level items, whereuserWorkspaceIdis null/undefined, should continue to broadcast workspace-wide. -
Add a frontend defense in
useNavigationMenuItemsData.Current pattern:
const userNavigationMenuItems = navigationMenuItems.filter((item) => isDefined(item.userWorkspaceId), );Safer behavior:
const userNavigationMenuItems = navigationMenuItems.filter( (item) => isDefined(item.userWorkspaceId) && item.userWorkspaceId === currentWorkspaceMemberId, ); -
Add regression coverage:
- Server/SSE test: a
navigationMenuItemevent withuserWorkspaceId = Ais delivered only to streams for A, while workspace-level navigation events still go to the workspace. - Frontend hook/component test: when the metadata store contains personal nav items for A and B, a B session renders only B's user-scoped favorites plus workspace nav items.
- Server/SSE test: a
Useful files from the traced code path:
packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.tspackages/twenty-server/src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-access.service.tspackages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-publisher.tspackages/twenty-server/src/engine/subscriptions/workspace-event-broadcaster/workspace-event-broadcaster.service.tspackages/twenty-front/src/modules/metadata-store/effect-components/MetadataStoreSSEEffect.tsx- `packages/twenty-front/src/modules/navigation-menu-item/display/hooks/useNavigationMenuItemsData.ts
Workspace-wide metadata SSE events push user-scoped navigation menu items into every connected client store without filtering by userWorkspaceId.
- packages/twenty-front/src/modules/metadata/components/MetadataStoreSSEEffect.tsx
- packages/twenty-front/src/modules/ui/navigation/hooks/useNavigationMenuItemsData.ts
- packages/twenty-server/src/engine/metadata-modules/metadata-event/metadata-event-publisher.ts
- packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts