[WEB-7855] fix(security): prevent project invite email disclosure via unauthenticated GET (#9305)

ProjectJoinEndpoint.get() was AllowAny and used ProjectMemberInviteSerializer
(fields = "__all__"), leaking the invitee's email and token to anyone who
knew the workspace slug, project ID, and invite UUID (GHSA-2r58-hgv7-635q).

Introduce ProjectMemberInvitePublicSerializer with an explicit safe field list
that excludes `email` and `token`, and swap it in for the public GET endpoint.
The full serializer is retained for authenticated admin viewsets.

Co-authored-by: Plane AI <noreply@plane.so>
This commit is contained in:
Manish Gupta
2026-07-01 17:34:45 +05:30
committed by GitHub
parent 24fad369e9
commit 4b52dce76e
3 changed files with 31 additions and 2 deletions

View File

@@ -35,6 +35,7 @@ from .project import (
ProjectDetailSerializer,
ProjectMemberSerializer,
ProjectMemberInviteSerializer,
ProjectMemberInvitePublicSerializer,
ProjectIdentifierSerializer,
ProjectLiteSerializer,
ProjectMemberLiteSerializer,

View File

@@ -203,6 +203,31 @@ class ProjectMemberInviteSerializer(BaseSerializer):
fields = "__all__"
class ProjectMemberInvitePublicSerializer(BaseSerializer):
"""Safe read-only serializer for the public project invite GET endpoint.
Intentionally excludes ``email`` and ``token`` so that an unauthenticated
caller cannot retrieve the invitee's email address or the acceptance token
(GHSA-2r58-hgv7-635q).
"""
project = ProjectLiteSerializer(read_only=True)
workspace = WorkspaceLiteSerializer(read_only=True)
class Meta:
model = ProjectMemberInvite
fields = [
"id",
"project",
"workspace",
"role",
"message",
"accepted",
"responded_at",
]
read_only_fields = fields
class ProjectIdentifierSerializer(BaseSerializer):
class Meta:
model = ProjectIdentifier

View File

@@ -19,7 +19,10 @@ from rest_framework.permissions import AllowAny
# Module imports
from .base import BaseViewSet, BaseAPIView
from plane.app.serializers import ProjectMemberInviteSerializer
from plane.app.serializers import (
ProjectMemberInviteSerializer,
ProjectMemberInvitePublicSerializer,
)
from plane.app.permissions import allow_permission, ROLE
from plane.db.models import (
ProjectMember,
@@ -250,5 +253,5 @@ class ProjectJoinEndpoint(BaseAPIView):
def get(self, request, slug, project_id, pk):
project_invitation = ProjectMemberInvite.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
serializer = ProjectMemberInviteSerializer(project_invitation)
serializer = ProjectMemberInvitePublicSerializer(project_invitation)
return Response(serializer.data, status=status.HTTP_200_OK)