[WEB-7895] fix: scope UserProjectInvitationsViewset to workspace-validated project IDs (GHSA-45hc-q4mw-jhxm)

The `create` handler validated the network (SECRET/PUBLIC) check against
a workspace-scoped queryset but then used the raw client-supplied
`project_ids` list in the subsequent bulk_create and update calls.
An attacker could include UUIDs of projects from other workspaces: those
are absent from the validation queryset (no network check performed),
yet get inserted as ProjectMember rows via bulk_create(ignore_conflicts=True),
granting cross-workspace project access.

Fix: derive `validated_project_ids` from the filtered queryset (projects
already scoped to the requested workspace and passed the SECRET check),
and use it exclusively for all subsequent DB writes.

Co-authored-by: Plane AI <noreply@plane.so>
This commit is contained in:
Manish Gupta
2026-06-29 17:34:55 +05:30
parent 1e8f3630c7
commit 7d0b33b7e7

View File

@@ -145,10 +145,16 @@ class UserProjectInvitationsViewset(BaseViewSet):
workspace_role = workspace_member.role
workspace = workspace_member.workspace
# Use the workspace-scoped, network-validated project IDs only.
# Raw project_ids may contain UUIDs from other workspaces; those are
# absent from the `projects` queryset and therefore bypass the SECRET
# network check above (GHSA-45hc-q4mw-jhxm).
validated_project_ids = [str(p.id) for p in projects]
# If the user was already part of workspace
_ = ProjectMember.objects.filter(workspace__slug=slug, project_id__in=project_ids, member=request.user).update(
is_active=True
)
_ = ProjectMember.objects.filter(
workspace__slug=slug, project_id__in=validated_project_ids, member=request.user
).update(is_active=True)
ProjectMember.objects.bulk_create(
[
@@ -159,7 +165,7 @@ class UserProjectInvitationsViewset(BaseViewSet):
workspace=workspace,
created_by=request.user,
)
for project_id in project_ids
for project_id in validated_project_ids
],
ignore_conflicts=True,
)
@@ -172,7 +178,7 @@ class UserProjectInvitationsViewset(BaseViewSet):
workspace=workspace,
created_by=request.user,
)
for project_id in project_ids
for project_id in validated_project_ids
],
ignore_conflicts=True,
)