diff --git a/apps/api/plane/app/views/estimate/base.py b/apps/api/plane/app/views/estimate/base.py index 46d5f7a6b6..de1d47cf9e 100644 --- a/apps/api/plane/app/views/estimate/base.py +++ b/apps/api/plane/app/views/estimate/base.py @@ -159,6 +159,17 @@ class EstimatePointEndpoint(BaseViewSet): {"error": "Key and value are required"}, status=status.HTTP_400_BAD_REQUEST, ) + # Verify the estimate belongs to this workspace and project before creating a point + estimate = Estimate.objects.filter( + pk=estimate_id, + workspace__slug=slug, + project_id=project_id, + ).first() + if not estimate: + return Response( + {"error": "Estimate not found"}, + status=status.HTTP_404_NOT_FOUND, + ) key = request.data.get("key", 0) value = request.data.get("value", "") estimate_point = EstimatePoint.objects.create( @@ -227,8 +238,18 @@ class EstimatePointEndpoint(BaseViewSet): epoch=int(timezone.now().timestamp()), ) - # delete the estimate point - old_estimate_point = EstimatePoint.objects.filter(pk=estimate_point_id).first() + # delete the estimate point — scope to this estimate/project/workspace to prevent cross-tenant key manipulation + old_estimate_point = EstimatePoint.objects.filter( + pk=estimate_point_id, + estimate_id=estimate_id, + project_id=project_id, + workspace__slug=slug, + ).first() + if not old_estimate_point: + return Response( + {"error": "Estimate point not found"}, + status=status.HTTP_404_NOT_FOUND, + ) # rearrange the estimate points updated_estimate_points = []