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

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-07-09 18:32:22 +05:30
committed by GitHub
parent 73e360844b
commit e1ef42023a

View File

@@ -148,10 +148,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(
[
@@ -162,7 +168,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,
)
@@ -175,7 +181,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,
)