mirror of
https://github.com/makeplane/plane.git
synced 2026-08-29 10:08:51 +02:00
* [WEB-8283] fix: bind Spaces board object IDs to the anchor's project The public Spaces board endpoints resolved the DeployBoard from the URL anchor but trusted the caller-supplied issue_id/comment_id/intake_id verbatim, without verifying the object belonged to that board's project/workspace. Any authenticated user could write comments, reactions and votes onto arbitrary issues cross-tenant, and read EXTERNAL comments from a different project in the same workspace. Bind every caller-supplied object id to the board's project + workspace before writing: - comment / issue-reaction / vote create: require the issue to exist in the board's project via Issue.issue_objects (excludes draft/archived/ triage), else 404. - comment-reaction create: require the comment to exist in the board's project with access="EXTERNAL", else 404. - intake create: require the URL intake_id to match the board's intake, else 400. - comment list read: scope the queryset to the board's project_id. Also add the missing is_votes_enabled gate on vote create for parity with comment/reaction create (pre-existing gap in the same method). Adds contract regression tests (fail-before verified): cross-tenant writes and the cross-project comment read now rejected, with positive controls confirming legitimate board writes/reads still succeed. Co-authored-by: Plane AI <noreply@plane.so> * [WEB-8283] test: address Copilot review — cross-workspace + votes-disabled coverage - Add cross-workspace write test (issue in a different workspace) to exercise the workspace_id binding, matching the advisory's cross-tenant impact (previously only same-workspace/different-project was covered). - Add a regression test for the new is_votes_enabled gate on vote create (votes-disabled board → 400), preventing the pre-existing gap from reappearing. - Clarify the section header comment: cross-tenant writes return 404 for issue/comment binding, 400 for the intake binding mismatch. Co-authored-by: Plane AI <noreply@plane.so> * [WEB-8283] refactor: extract board-scope guards into shared helpers Address CodeRabbit review: the identical "object belongs to the board's project+workspace" existence check was duplicated across four create() methods (in four different ViewSets). Extract two module-level helpers — _issue_in_board_scope and _comment_in_board_scope — so the check is a single source of truth and cannot drift between endpoints or be forgotten on a new one (the exact class of bug this PR fixes). Behavior-preserving; 14 contract tests still green. Co-authored-by: Plane AI <noreply@plane.so> * chore(security): drop advisory identifiers from code comments Explanations kept unchanged; only the IDs are removed. Co-authored-by: Plane AI <noreply@plane.so> * fix(security): use the deploy board's project_id in the reaction-create activity log CommentReactionPublicViewSet.create() logged the activity with str(self.kwargs.get("project_id", None)) — this route's URL only ever supplies anchor and comment_id, never project_id, so every comment reaction created on a public board logged project_id="None", silently corrupting the activity/audit trail. destroy() on the same viewset already resolves the correct project_id from the deploy board; create() now does the same. Co-authored-by: Plane AI <noreply@plane.so> * [WEB-8283] fix: apply board-scope guards to comment/reaction read, update and delete paths The board/issue/external-comment scoping added by this PR's create() methods was never applied to the list, update and delete paths built on the same models. A caller could read reactions on an INTERNAL (non-public) comment through the public reaction list, or reach a comment or reaction they authored through a board it doesn't actually belong to via partial_update() or destroy(), since those methods looked up objects by pk/actor only. Bind IssueCommentPublicViewSet.partial_update()/destroy() to the board's project, workspace, issue_id and EXTERNAL access; bind IssueReactionPublicViewSet.destroy() to the board's project (previously only workspace-scoped); and bind CommentReactionPublicViewSet.get_queryset()/ destroy() to EXTERNAL comments only. Add regression coverage for each gap, plus positive controls confirming legitimate reads/writes on the board's own objects still work. Co-authored-by: Plane AI <noreply@plane.so> --------- Co-authored-by: Plane AI <noreply@plane.so>