github →

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.

Issue
sonarly:high

(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.canUserCreateView allows UNLISTED views for authenticated users, while WORKSPACE views require PermissionFlagType.VIEWS.
  • NavigationMenuItemService.findAll returns workspace nav items plus only the current user's userWorkspaceId rows.
  • NavigationMenuItemAccessService requires LAYOUTS for 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.publish broadcasts navigationMenuItem metadata events workspace-wide.
  • MetadataStoreSSEEffect applies create/update metadata events directly into every client metadata store.
  • useNavigationMenuItemsData treats every item with a defined userWorkspaceId as a personal favorite/sidebar item, without checking that it belongs to currentWorkspaceMemberId.

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 LAYOUTS for workspace-level nav items.

Technical inputs

Likely changes:

  1. Restrict navigationMenuItem metadata broadcasts when the event is user-scoped.

    WorkspaceEventBroadcaster already supports recipientUserWorkspaceIds. For navigationMenuItem create/update events where the effective record has userWorkspaceId, pass recipientUserWorkspaceIds: [userWorkspaceId]. Workspace-level items, where userWorkspaceId is null/undefined, should continue to broadcast workspace-wide.

  2. 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,
    );
    
  3. Add regression coverage:

    • Server/SSE test: a navigationMenuItem event with userWorkspaceId = A is 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.

Useful files from the traced code path:

  • packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts
  • packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-access.service.ts
  • packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-publisher.ts
  • packages/twenty-server/src/engine/subscriptions/workspace-event-broadcaster/workspace-event-broadcaster.service.ts
  • packages/twenty-front/src/modules/metadata-store/effect-components/MetadataStoreSSEEffect.tsx
  • `packages/twenty-front/src/modules/navigation-menu-item/display/hooks/useNavigationMenuItemsData.ts
Assessmentadvisory
bug●● medium85% confidence

Workspace-wide metadata SSE events push user-scoped navigation menu items into every connected client store without filtering by userWorkspaceId.

Likely files
  • 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
Create the request

This opens a fresh agent run and a draft PR for issue #20483.

Cancel