fix(security): scope the duplicate ProjectMemberPermission POST branch too

Addresses review on #9596.

plane/utils/permissions/project.py holds a second ProjectMemberPermission that,
comments aside, was byte-identical to the one in plane/app/permissions. Its POST
branch still checked workspace membership alone.

It is imported (api/views/member.py) but its POST branch is currently
unreachable: ProjectMemberListCreateAPIEndpoint.get_permissions() routes non-GET
to ProjectAdminPermission, and the other consumer is GET-only. So this is a
latent hazard rather than a second live vector — but two same-named classes that
have already drifted make reintroduction easy, and this repo has previously had
to patch the same duplication in the page permission classes. Both copies now
carry a comment saying they must not drift.

Also aligns the deploy-board 404 string with the module's existing wording
("Project does not exist", cf. base.py:230) rather than introducing a second
phrasing for clients to handle.

Co-authored-by: Plane AI <noreply@plane.so>
This commit is contained in:
Manish Gupta
2026-08-13 14:18:02 +05:30
parent 0f9249153f
commit f494bfd76d
2 changed files with 7 additions and 3 deletions

View File

@@ -565,7 +565,7 @@ class DeployBoardViewSet(BaseViewSet):
# caller could aim their own workspace at another tenant's project id.
# Defence in depth: the permission class now binds both.
if not Project.objects.filter(pk=project_id, workspace__slug=slug).exists():
return Response({"error": "Project not found"}, status=status.HTTP_404_NOT_FOUND)
return Response({"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND)
project_deploy_board, _ = DeployBoard.objects.get_or_create(
entity_name="project", entity_identifier=project_id, project_id=project_id

View File

@@ -66,12 +66,16 @@ class ProjectMemberPermission(BasePermission):
project_id=view.project_id,
is_active=True,
).exists()
## Only workspace owners or admins can create the projects
# Scope POST to the URL project, as the other two branches already do.
# A workspace-only check would let any member create sub-resources in a
# project they do not belong to. Kept identical to the copy in
# app/permissions/project.py — the two must not drift.
if request.method == "POST":
return WorkspaceMember.objects.filter(
return ProjectMember.objects.filter(
workspace__slug=view.workspace_slug,
member=request.user,
role__in=[ROLE.ADMIN.value, ROLE.MEMBER.value],
project_id=view.project_id,
is_active=True,
).exists()