From 87c68b66e1e5e65c96fc16d22aaa8016c9be5cf6 Mon Sep 17 00:00:00 2001 From: Ilango Rajagopal Date: Thu, 7 May 2026 15:06:06 +0530 Subject: [PATCH 1/2] fix: access to invited workspace --- .../enterprise/controllers/workspace-user.controller.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/server/src/enterprise/controllers/workspace-user.controller.ts b/packages/server/src/enterprise/controllers/workspace-user.controller.ts index 90ba4f2e2e5..226a0d78885 100644 --- a/packages/server/src/enterprise/controllers/workspace-user.controller.ts +++ b/packages/server/src/enterprise/controllers/workspace-user.controller.ts @@ -55,14 +55,7 @@ export class WorkspaceUserController { queryRunner ) } else if (query.userId) { - if (query.userId !== user.id && !userMayManageOrgUsers(user)) { - throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) - } - workspaceUser = await workspaceUserService.readWorkspaceUserByOrganizationIdUserId( - user.activeOrganizationId, - query.userId, - queryRunner - ) + workspaceUser = await workspaceUserService.readWorkspaceUserByUserId(query.userId, queryRunner) } else if (query.roleId) { if (!userMayManageOrgUsers(user)) { throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) From 824af95f599e0fc3a21a21c95ff88e68c06001d1 Mon Sep 17 00:00:00 2001 From: Ilango Rajagopal Date: Thu, 7 May 2026 16:08:11 +0530 Subject: [PATCH 2/2] update: idor guards --- .../controllers/workspace-user.controller.ts | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/server/src/enterprise/controllers/workspace-user.controller.ts b/packages/server/src/enterprise/controllers/workspace-user.controller.ts index 226a0d78885..b2c9945f7eb 100644 --- a/packages/server/src/enterprise/controllers/workspace-user.controller.ts +++ b/packages/server/src/enterprise/controllers/workspace-user.controller.ts @@ -7,6 +7,7 @@ import { getRunningExpressApp } from '../../utils/getRunningExpressApp' import { WorkspaceUser } from '../database/entities/workspace-user.entity' import { WorkspaceUserService } from '../services/workspace-user.service' import { + assertMayReadTargetUser, assertQueryOrganizationMatchesActiveOrg, assertWorkspaceIdAccessibleToUser, getLoggedInUser, @@ -35,6 +36,7 @@ export class WorkspaceUserController { let workspaceUser: any if (query.workspaceId && query.userId) { + // Caller must have access to the workspace (own, assigned, or org admin within their org). await assertWorkspaceIdAccessibleToUser(user, query.workspaceId, queryRunner) workspaceUser = await workspaceUserService.readWorkspaceUserByWorkspaceIdUserId( query.workspaceId, @@ -42,21 +44,35 @@ export class WorkspaceUserController { queryRunner ) } else if (query.workspaceId) { + // Caller must have access to the workspace (own, assigned, or org admin within their org). await assertWorkspaceIdAccessibleToUser(user, query.workspaceId, queryRunner) workspaceUser = await workspaceUserService.readWorkspaceUserByWorkspaceId(query.workspaceId, queryRunner) } else if (query.organizationId && query.userId) { + // organizationId must match the caller's active org to prevent cross-org access. + // Caller must be the target user or an org user manager whose target belongs to the same org (IDOR guard). assertQueryOrganizationMatchesActiveOrg(user, query.organizationId) - if (query.userId !== user.id && !userMayManageOrgUsers(user)) { - throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) - } + await assertMayReadTargetUser(user, query.userId, queryRunner) workspaceUser = await workspaceUserService.readWorkspaceUserByOrganizationIdUserId( query.organizationId, query.userId, queryRunner ) } else if (query.userId) { - workspaceUser = await workspaceUserService.readWorkspaceUserByUserId(query.userId, queryRunner) + if (query.userId === user.id) { + // Self-lookup: return memberships across all orgs so the user can switch to an invited org/workspace. + workspaceUser = await workspaceUserService.readWorkspaceUserByUserId(query.userId, queryRunner) + } else { + // Non-self: caller must be an org user manager and the target must belong to the caller's active org (IDOR guard). + // Results are scoped to the caller's active org to prevent cross-org data leakage. + await assertMayReadTargetUser(user, query.userId, queryRunner) + workspaceUser = await workspaceUserService.readWorkspaceUserByOrganizationIdUserId( + user.activeOrganizationId, + query.userId, + queryRunner + ) + } } else if (query.roleId) { + // Only org user managers may list workspace members by role. if (!userMayManageOrgUsers(user)) { throw new InternalFlowiseError(StatusCodes.FORBIDDEN, GeneralErrorMessage.FORBIDDEN) }