* [WEB-8352] fix(security): scope SubIssuesEndpoint to the URL project (GHSA-gxhv-fw9x-2pg3)
SubIssuesEndpoint is guarded only by ProjectEntityPermission, which verifies
the caller belongs to the URL project_id but not that the path issue_id lives
in that project. Both handlers then resolved issues without a project scope:
- GET filtered sub-issues by parent_id + workspace__slug only, leaking the
names/priorities/assignees/dates of another project's sub-issues (read IDOR).
- POST loaded the parent by bare pk (no workspace/project scope) and filtered
the moved sub-issues by workspace__slug only, letting any project member
re-parent issues from other projects/workspaces (write IDOR).
Scope the parent lookup and both sub-issue querysets to the URL project_id
(and bind the parent to the workspace), returning 404 when the parent is not
in the caller's project. Adds 5 contract tests (3 security, 2 positive
controls); fail-before verified.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [WEB-8352] fix: dispatch sub-issue activity only for project-scoped issues (CodeRabbit/Copilot #9466)
The DB update + response were scoped to the URL project, but the activity loop
still iterated the raw caller-supplied sub_issue_ids. A cross-project id
(excluded from the re-parent) would still fire issue_activity.delay, whose task
does an unscoped Issue.objects.get and bumps updated_at — touching a foreign
issue and creating a bogus activity row.
Dispatch from the project-scoped sub_issues (scoped_sub_issue_ids) instead.
Strengthened the test to assert the foreign issue is absent from the response
body (sub_issues / state_distribution) and that no activity is dispatched for it
(mock). Fail-before verified.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(security): drop advisory identifiers from code comments
Explanations kept unchanged; only the IDs are removed.
Co-authored-by: Plane AI <noreply@plane.so>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Plane AI <noreply@plane.so>
* [WEB-8074] fix: scope IssueListEndpoint to guest created_by
IssueListEndpoint.get (/workspaces/<slug>/projects/<project_id>/issues/list/)
returned any issue whose id was passed in ?issues=, without the guest
created_by restriction its sibling IssueViewSet.list enforces. A project GUEST
(role=5) on a project with guest_view_all_features=False could read issues they
did not author by supplying their ids (GHSA-32c7-84jc-4w67).
Replicate the guest scope: when the requester is an active role=5 ProjectMember
and not project.guest_view_all_features, filter the queryset to
created_by=request.user. Applied to the base queryset so it flows through
filtering, annotation and grouping.
Contract regression tests cover the restricted guest (own-only), a full member
(sees all), and a guest with guest_view_all_features enabled (sees all);
fail-before verified.
Co-authored-by: Plane AI <noreply@plane.so>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Plane AI <noreply@plane.so>
Co-authored-by: Dheeraj Kumar Ketireddy <dheeraj.ketireddy@plane.so>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
All three V2 issue attachment handlers (PATCH, DELETE, GET single) looked
up FileAsset by (pk, workspace, project_id) only — issue_id in the URL
was silently ignored. Any project member could target another user's
attachment UUID using their own issue_id, and PATCH would transfer
ownership via unconditional created_by = request.user.
Add issue_id=issue_id to all three FileAsset.objects.get() calls so the
lookup is correctly scoped to the attachment's owning issue. Remove the
created_by overwrite in PATCH — created_by is set at creation time and
must not be reassigned by a subsequent upload-confirm call.
Co-authored-by: Plane AI <noreply@plane.so>
* fix(security): scope issue ID validation to workspace/project in bulk endpoints
Prevents cross-tenant IDOR by filtering incoming issue IDs through
workspace+project scope before bulk_create/bulk_update in:
- CycleIssueListCreateAPIEndpoint: validate new_issues against workspace+project (GHSA-22g9-9xfv-q3fr)
- SubIssuesEndpoint: validate sub_issue_ids against workspace (GHSA-38vj-gf85-7q5x)
- IssueRelationListCreateAPIEndpoint: validate issues against workspace (GHSA-8cvv-8jh5-g6mj)
- ModuleIssueListCreateAPIEndpoint: already scoped at line 673, no change needed (GHSA-x5c5-hmvm-94v9)
Co-authored-by: Plane AI <noreply@plane.so>
* fix(security): extend IDOR scope validation to app-layer endpoints
Same cross-tenant IDOR fix applied to the app/views/ counterparts
which are used by the web frontend (api/views/ covered in previous commit):
- app/views/cycle/issue.py: filter new_issues to workspace+project (GHSA-22g9-9xfv-q3fr)
- app/views/module/issue.py: filter issues to workspace+project before bulk_create (GHSA-x5c5-hmvm-94v9)
- app/views/issue/relation.py: filter issues to workspace before bulk_create (GHSA-8cvv-8jh5-g6mj)
Co-authored-by: Plane AI <noreply@plane.so>
* chore: remove advisory ID references from code comments
---------
Co-authored-by: Plane AI <noreply@plane.so>
Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
* fix(security): scope cascade deletes to workspace in BulkDeleteIssuesEndpoint
CycleIssue and ModuleIssue cascade deletes used raw issue_ids from the
request instead of the already workspace+project scoped issues queryset,
allowing cross-workspace deletion of related records.
Fixes GHSA-6cw7-h92q-p9hg and GHSA-2rr4-rp7r-32p4.
GHSA-7q7r-mrr4-2wwx (sub-issue parent reassign) covered in WEB-7727.
Co-authored-by: Plane AI <noreply@plane.so>
* chore: remove advisory ID reference from code comment
---------
Co-authored-by: Plane AI <noreply@plane.so>
Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
* fix: filter out soft-deleted states from API endpoints
- Add deleted_at__isnull=True filter to StateListCreateAPIEndpoint.get_queryset()
- Add deleted_at__isnull=True filter to StateDetailAPIEndpoint.get_queryset()
- Prevents soft-deleted states from reappearing in UI after navigation
- Fixes#8829
* Fix: exclude issues linked to soft-deleted states
* fix: sanitize filenames in upload paths to prevent path traversal (GHSA-v57h-5999-w7xp)
Add server-side filename sanitization across all file upload endpoints
to prevent path traversal sequences (../) in user-supplied filenames
from being incorporated into S3 object keys. While S3 keys are flat
strings and not vulnerable to filesystem traversal, this adds
defense-in-depth and prevents S3 key pollution.
Changes:
- Add sanitize_filename() utility in path_validator.py
- Sanitize filenames in get_upload_path() for FileAsset and IssueAttachment models
- Sanitize name parameter in all upload view endpoints
* fix: address PR review feedback on filename sanitization
- Remove unused `import re`
- Normalize backslashes to forward slashes before os.path.basename()
so Windows-style paths (e.g. ..\..\..\evil.txt) are handled on POSIX
- Strip whitespace before removing leading dots so " .env" is caught
- Return None instead of "unnamed" for empty input so existing
`if not name` validation guards remain effective
- Add `or "unnamed"` fallback at call sites that lack a name guard
* fix: use random hex name as fallback in get_upload_path instead of "unnamed"
* fix: resolve ruff E501 line too long in DuplicateAssetEndpoint
The bulk update date endpoint fetched issues by ID without filtering
by workspace or project, allowing any authenticated project member to
modify start_date and target_date of issues in any workspace/project
across the entire instance (IDOR - CWE-639).
Scoped the query to include workspace__slug and project_id filters,
consistent with other issue endpoints in the codebase.
Ref: GHSA-4q54-h4x9-m329
* refactor: rename IssueUserProperty to ProjectUserProperty and update related references across the codebase
* migrate: move issue user properties to project user properties and update related fields and constraints
* refactor: rename IssueUserPropertySerializer and IssueUserDisplayPropertyEndpoint to ProjectUserPropertySerializer and ProjectUserDisplayPropertyEndpoint, updating all related references
* fix: enhance ProjectUserDisplayPropertyEndpoint to handle missing properties by creating new entries and improve response handling
* fix: correct formatting in migration for ProjectUserProperty model options
* migrate: add migration to update existing non-service API tokens to remove workspace association
* migrate: refine migration to update existing non-service API tokens by excluding bot users from workspace removal
* chore: changed the project sort order in project user property
* chore: remove allowed_rate_limit from APIToken
* chore: updated user-properties endpoint for frontend
* chore: removed the extra projectuserproperty
* chore: updated the migration file
* chore: code refactor
* fix: type error
---------
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: sangeethailango <sangeethailango21@gmail.com>
Co-authored-by: vamsikrishnamathala <matalav55@gmail.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
* feat: add no_activity flag to control issue activity tracking during partial updates
* refactor: rename no_activity flag to skip_activity for clarity in issue activity tracking
* enhance description input handling with migration update support
* feat: implement skip_activity flag to conditionally log issue updates during partial updates
* refactor: skip-activity
* feat: add migration description update check to conditionally log issue updates
---------
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
* fix: wrong WI count while scrolling
* chore: optimize issue queryset
* fix: use separate query for total_count_queryset
* fix: guest visibility constraint
* fix: use separate query for total_count_queryset in external api
* fix: use queryset.count()