* [WEB-8060] fix(security): enforce authz on is_active member (de)activation
ProjectMemberViewSet.partial_update nested every authorization guard inside
`if "role" in request.data:`. Because ProjectMemberSerializer exposes is_active
through fields="__all__" with no read-only, a project GUEST could PATCH
{"is_active": false} while omitting "role" to deactivate any member — including
admins — and take over the project (GHSA-hpgm-9r34-c4x5 / GHSA-25gg-cxm8-g7h9).
Add an independent is_active guard, mirroring the role block and destroy(): only
a project admin (or workspace admin) may (de)activate a member, and never one
whose role is equal to or higher than the requester's own.
Adds contract regression tests covering guest/member deactivation attempts and
the legitimate project-admin path.
Co-authored-by: Plane AI <noreply@plane.so>
* [WEB-8060] test(security): cover workspace-admin is_active bypass
Address Copilot review on #9367: add a positive-control test asserting a
workspace admin holding only a project GUEST role can still deactivate a project
admin (the intended is_workspace_admin bypass), so future changes cannot silently
remove it.
Co-authored-by: Plane AI <noreply@plane.so>
---------
Co-authored-by: Plane AI <noreply@plane.so>
The `create` handler validated the network (SECRET/PUBLIC) check against
a workspace-scoped queryset but then used the raw client-supplied
`project_ids` list in the subsequent bulk_create and update calls.
An attacker could include UUIDs of projects from other workspaces: those
are absent from the validation queryset (no network check performed),
yet get inserted as ProjectMember rows via bulk_create(ignore_conflicts=True),
granting cross-workspace project access.
Fix: derive `validated_project_ids` from the filtered queryset (projects
already scoped to the requested workspace and passed the SECRET check),
and use it exclusively for all subsequent DB writes.
Co-authored-by: Plane AI <noreply@plane.so>
* fix(security): enforce token + auth validation on project invite accept/reject
ProjectJoinEndpoint.post() only checked that the caller-supplied email matched
the invited email — no token required, no authentication required. Anyone who
knew the workspace slug, project ID, invite UUID, and invitee email could
accept or reject the invitation on the invitee's behalf (GHSA-g36h-p63v-g9c7).
Mirror WorkspaceJoinEndpoint.post() exactly:
- Validate `token` from request body against project_invite.token (→ 403 on mismatch)
- Require authenticated session (→ 401 if unauthenticated)
- Validate request.user.email against project_invite.email (→ 403 on mismatch)
- Remove the old request.data["email"] guard
- Use project_invite.email for downstream User lookup
Co-authored-by: Plane AI <noreply@plane.so>
* fix(security): address CR review on project invite token validation
- Use request.user directly instead of re-querying User by exact
project_invite.email — avoids case-variant miss after the case-insensitive
email check already validated the authenticated user (CR comment 1)
- Validate `accepted` as a real boolean before saving — form-encoded
strings like "false" are truthy and could accidentally create memberships
(CR comment 2)
Co-authored-by: Plane AI <noreply@plane.so>
---------
Co-authored-by: Plane AI <noreply@plane.so>
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>
* fix: prevent ORM order_by injection via user-supplied query params (GHSA-2r95, GHSA-w45q)
Add field-name allowlists and a sanitize_order_by() utility in order_queryset.py.
All allowlists are centralised there; each call site imports the named constant
so there are no inline sets scattered across view files.
- order_queryset.py: ISSUE_ORDER_BY_ALLOWLIST, INTAKE_ISSUE_ORDER_BY_ALLOWLIST,
ACTIVITY_ORDER_BY_ALLOWLIST, PROJECT_ORDER_BY_ALLOWLIST, VIEW_ORDER_BY_ALLOWLIST,
NOTIFICATION_ORDER_BY_ALLOWLIST + sanitize_order_by() utility; validation added
at the top of order_issue_queryset() — fixes all callers including the
unauthenticated ProjectIssuesPublicEndpoint (GHSA-w45q)
- api/views/cycle.py, api/views/module.py: cycle/module issue list endpoints
- api/views/issue.py: IssueActivity list and detail endpoints
- app/views/intake/base.py: IntakeIssue list
- app/views/view/base.py: saved-view list
- app/views/notification/base.py: notification paginator
- app/views/project/base.py: project list paginator
- app/views/user/base.py, app/views/workspace/user.py: activity paginators
Closes WEB-7813
Co-authored-by: Plane AI <noreply@plane.so>
* fix: harden sanitize_order_by against multi-dash malformed inputs
lstrip("-") stripped all leading dashes, allowing "--created_at" to
pass the allowlist check unchanged and reach .order_by() as a malformed
token (causing FieldError). Now strips only one leading dash; any
remaining dash prefix is rejected to the safe default.
Co-authored-by: Plane AI <noreply@plane.so>
---------
Co-authored-by: Plane AI <noreply@plane.so>
is_workspace_admin in ProjectMemberViewSet.partial_update was derived
from the target member's workspace role, not the requester's. When the
target happened to be a workspace admin, all three project-role guards
(L231/238/247) were bypassed regardless of who was making the request,
allowing a non-admin requester to re-role a workspace admin's project
membership. Compute is_workspace_admin from the requester instead and
keep the target's workspace role under a distinct name for the existing
new-role-vs-workspace-role cap.
`ProjectViewSet.partial_update`, `BulkEstimatePointEndpoint.partial_update`,
and `WorkspaceUserProfileEndpoint.get` previously fetched objects by primary
key alone after a workspace-scoped permission check, allowing an authenticated
caller to act on resources belonging to other workspaces by supplying a
foreign UUID with their own workspace slug in the URL.
- Project partial_update: scope `Project.objects.get` by `workspace__slug`,
matching the existing pattern in `destroy`.
- Bulk estimate partial_update: scope `Estimate.objects.get` by
`workspace__slug` and `project_id`, matching `retrieve` and `destroy`.
- Workspace user profile: require the target `user_id` to be an active
member of the requested workspace before returning email and other PII.
Restrict role modification in ProjectMemberViewSet.partial_update to
Admins only and enforce that requesters cannot modify or assign roles
equal to or higher than their own. Previously, Guests could demote
Admins by exploiting a missing lower-bound check on role changes.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* 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: implement cover image handling and static image selection
- Added functionality to handle cover image uploads and selections in project and profile forms.
- Introduced a new helper for managing cover images, including static images and uploaded assets.
- Updated components to utilize the new cover image helper for displaying and processing cover images.
- Added a set of static cover images for selection in the image picker.
- Enhanced error handling for image uploads and processing.
This update improves the user experience by providing a more robust cover image management system.
* refactor: rename STATIC_COVER_IMAGES_ARRAY to STATIC_COVER_IMAGES for consistency
- Updated the cover image helper to export STATIC_COVER_IMAGES instead of STATIC_COVER_IMAGES_ARRAY.
- Adjusted the ImagePickerPopover component to utilize the renamed export for rendering static cover images.
* feat: enhance project creation and image handling
- Introduced default project form values with a random emoji for logo props.
- Updated cover image handling in various components, ensuring consistent usage of the new cover image helper.
- Refactored image picker to improve search functionality and loading states.
- Removed unused constants and streamlined cover image type checks for better clarity and performance.
This update enhances the user experience in project creation and image selection processes.
* refactor: simplify cover image type definition and clean up code
- Removed duplicate type from TCoverImageType, streamlining the definition.
- Cleaned up whitespace in the cover image helper for improved readability.
This update enhances code clarity and maintains consistency in cover image handling.
* refactor: update cover image type definitions and simplify logic
- Changed ICoverImageResult and ICoverImagePayload interfaces to type aliases for better clarity.
- Simplified the logic in getCoverImageDisplayURL function to enhance readability and maintainability.
This update improves the structure and clarity of the cover image helper code.
* refactor: remove unused project cover image endpoint and update cover image handling
- Removed the ProjectPublicCoverImagesEndpoint and its associated URL from the project.
- Updated the cover image handling in the cover-image helper to utilize imported assets instead of static paths.
- Cleaned up the ProjectFavoritesViewSet and FileService by removing the now obsolete getProjectCoverImages method.
This update streamlines the cover image management and eliminates unnecessary code, enhancing overall maintainability.
* refactor: update cover image imports to new asset structure
- Replaced static path imports for cover images with updated paths to the new asset structure.
- This change improves organization and maintainability of cover image assets in the project.
This update aligns with recent refactoring efforts to streamline cover image handling.
* feat: add additional cover images to the helper
- Imported new cover images (24 to 29) into the cover-image helper.
- This update expands the available cover image options for use in the project, enhancing visual variety.
* refactor: remove ProjectPublicCoverImagesEndpoint from project URLs and views
* refactor: update cover image imports to include URL query parameter
- Modified cover image imports in the cover-image helper to append a URL query parameter for better asset handling.
- This change enhances the way cover images are processed and utilized within the project.
* refactor: extract default project form values into a utility function
- Created a new utility function `getProjectFormValues` to encapsulate the default project form values.
- Updated the `CreateProjectForm` component to use this utility function for setting default form values, improving code organization and maintainability.
* feat: integrate project update functionality in CreateProjectForm
- Added `updateProject` method to the `CreateProjectForm` component for updating project cover images after creation.
- Enhanced cover image handling by ensuring the correct URL is set for both uploaded and existing cover images.
This update improves the project creation workflow and ensures cover images are accurately updated.
* fix: update documentation for cover image handling
- Corrected the comment regarding local static images to reflect that they are served from the assets folder instead of the public folder.
- This change ensures accurate documentation for the `getCoverImageType` and `getCoverImageDisplayURL` functions, improving clarity for future developers.
* feat: implement random cover image selection for project forms
- Replaced the default cover image URL with a new utility function `getRandomCoverImage` that selects a random cover image from the available options.
- Updated the `getProjectFormValues` function to utilize this new method, enhancing the project creation experience with varied cover images.
---------
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
* fix: separate retrieve method in WorkspaceMemberViewSet
* fix: non project members accessing member detail:
* chore: error handle
* fix: role based response
* fix: use Enum
* fix: project error message and status code
* fix: incorrect member role check
* fix: project error message and status code
* fix: improve project permission checks and error handling in ProjectViewSet
* feat: enhance project settings layout with better loading strategy and fix all flicker
* fix: prevent rendering during project loading in ProjectAuthWrapper
* refactor: adjust layout structure in ProjectDetailSettingsLayout and enhance access restriction logic in ProjectAccessRestriction
* refactor: replace ProjectAccessRestriction component with updated version and enhance error handling
- Deleted the old ProjectAccessRestriction component.
- Introduced a new ProjectAccessRestriction component with improved error handling and user prompts for joining projects.
- Updated translations for new error states in multiple languages.
* fix: enhance error handling in IssueDetailsPage and remove JoinProject component
---------
Co-authored-by: Dheeraj Kumar Ketireddy <dheeru0198@gmail.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
* chore: traige state in intake
* chore: triage state changes
* feat: implement intake state dropdown component and integrate into issue properties
* chore: added the triage state validation
* chore: added triage state filter
* chore: added workspace filter
* fix: migration file
* chore: added triage group state check
* chore: updated the filters
* chore: updated the filters
* chore: added variables for intake state
* fix: import error
* refactor: improve project intake state retrieval logic and update TriageGroupIcon component
* chore: changed the intake validation logic
* refactor: update intake state types and clean up unused interfaces
* chore: changed the state color
* chore: changed the update serializer
* chore: updated with current instance
* chore: update TriageGroupIcon color to match new intake state group color
* chore: stringified value
* chore: added validation in serializer
* chore: added logger instead of print
* fix: correct component closing syntax in ActiveProjectItem
* chore: updated the migration file
* chore: added noop in migation
---------
Co-authored-by: b-saikrishnakanth <bsaikrishnakanth97@gmail.com>
* chore: update navigation_project_limit and navigation_control_preference
* chore: set default true for user specific widgets
* chore: use serializer in ProjectMemberPreferenceEndpoint
chore: use serializer in WorkspaceUserPropertiesEndpoint
"
* fix: validate preferences
* fix: status code
* fix: remove saving from validate
* fix: simply validate_preferences
* chore: create WorkspaceUserProperties if it doesn't exist
* fix: create WorksapceUserProperties it not exist
* fix: copy the instance
* Revert "fix: copy the instance"
This reverts commit ddb0384b6dfa0dc52929972c4e2cd7ce85c69667.
* chore: migrate WorkspaceUserPreference to set defaults
* fix: migration file name
* Revert "fix: migration file name"
This reverts commit 80a21dedf1a1245f22e45bfeaf20e8e9f91a1cbf.
* Revert "chore: migrate WorkspaceUserPreference to set defaults"
This reverts commit 25bc583a081ce79d52ec721f69cf8e61de3e8fb3.