Skip to content

[WEB-8372] fix(security): restrict StateViewSet.partial_update to project admins - #9473

Open
mguptahub wants to merge 3 commits into
previewfrom
web-8372/state-partial-update-admin-only
Open

[WEB-8372] fix(security): restrict StateViewSet.partial_update to project admins#9473
mguptahub wants to merge 3 commits into
previewfrom
web-8372/state-partial-update-admin-only

Conversation

@mguptahub

@mguptahub mguptahub commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

StateViewSet.partial_update (apps/api/plane/app/views/state/base.py) was decorated @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]), while every sibling write on the viewset — create, destroy, mark_as_default — is @allow_permission([ROLE.ADMIN]). StateSerializer exposes name, color, group, default, description, sequence, order as writable, so any project Guest could rewrite any workflow state and set it as the project default — a functional bypass of the admin-only mark_as_default.

Fixes WEB-8372 (CWE-862). Confirmed vulnerable against origin/preview @ a8e53b6ac7.

Fix

Tighten partial_update to @allow_permission([ROLE.ADMIN]), matching the other state writes.

Tests

test_state_partial_update_admin_scope_app.py — guest PATCH → 403 (state + default unchanged), member PATCH → 403, admin PATCH → 200. Fail-before verified (guest & member could edit without the fix); ruff clean.

EE

plane-ee vendors its own copy; EE port to be assessed under Epic WEB-8293 (EE StateViewSet uses the @can engine — likely already gated, will confirm separately).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Restricted workflow state editing to project administrators only.
    • Members and guests can no longer modify workflow state details, including names and defaults.
    • Unauthorized update attempts are blocked, preserving existing workflow states.
  • Tests

    • Added coverage confirming authorization behavior for guests, members, and administrators.

…ject admins (GHSA-4jpp-964m-27cr)

Editing a workflow state is project configuration; its sibling writes (create,
destroy, mark_as_default) are all @allow_permission([ROLE.ADMIN]). partial_update
was the outlier at [ADMIN, MEMBER, GUEST], so any project Guest could rewrite any
state's name/color/group/description and set it as the project default — a
functional bypass of the admin-only mark_as_default.

Tighten partial_update to [ROLE.ADMIN]. Adds 3 contract tests (guest 403, member
403, admin 200); fail-before verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mguptahub
mguptahub requested a review from dheeru0198 as a code owner July 24, 2026 09:06
Copilot AI lite review requested due to automatic review settings July 24, 2026 09:06
@makeplane

makeplane Bot commented Jul 24, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

React Doctor found 4 issues in 4 files · 1 error & 3 warnings · score 82 / 100 (Needs work) · vs preview

Errors

3 warnings

core/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx

  • ⚠️ L14 Import from a barrel file no-barrel-import

core/components/issues/peek-overview/properties.tsx

  • ⚠️ L42 Import from a barrel file no-barrel-import

core/components/issues/workspace-draft/draft-issue-properties.tsx

  • ⚠️ L31 Import from a barrel file no-barrel-import
⚠️ Warning: .github/workflows/react-doctor.yml is configured incorrectly. See below to fix.

React Doctor compares against preview to report only the issues this pull request introduces. This run couldn't complete that comparison (usually a shallow CI checkout with no merge base), so it listed every issue in the changed files, including ones that already existed on preview.

Add fetch-depth: 0 to the actions/checkout step in .github/workflows/react-doctor.yml so the checkout includes the history React Doctor needs:

 jobs:
   react-doctor:
     steps:
       - uses: actions/checkout@v5
+        with:
+          fetch-depth: 0

       - uses: millionco/react-doctor@v2

To silence this warning, set silence-missing-baseline-warning: true on the React Doctor action.

Reviewed by React Doctor for commit 3321fd3. See inline comments for fixes.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a0c1c892-c170-4942-a3dc-32fe08fda270

📥 Commits

Reviewing files that changed from the base of the PR and between 110bd54 and 3321fd3.

📒 Files selected for processing (2)
  • apps/api/plane/app/views/state/base.py
  • apps/api/plane/tests/contract/app/test_state_partial_update_admin_scope_app.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/plane/app/views/state/base.py

📝 Walkthrough

Walkthrough

Workflow state partial updates now require ROLE.ADMIN. Contract tests verify guest and member requests receive 403 FORBIDDEN, while admin requests succeed and persist the state name change.

Changes

Workflow state update authorization

Layer / File(s) Summary
Restrict state updates to admins
apps/api/plane/app/views/state/base.py
The partial_update endpoint now permits only ROLE.ADMIN and documents the configuration boundary.
Validate role-scoped update behavior
apps/api/plane/tests/contract/app/test_state_partial_update_admin_scope_app.py
Contract tests verify guest and member updates are rejected without persistence, while admin updates return 200 OK and persist the renamed state.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: dheeru0198

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the security fix and the restriction of StateViewSet.partial_update to project admins.
Description check ✅ Passed The description explains the vulnerability, fix, tests, issue reference, and EE scope, but omits the template headings for change type and screenshots.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch web-8372/state-partial-update-admin-only

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an authorization bypass in the API by restricting StateViewSet.partial_update so that only project admins can edit workflow states, aligning it with other state-mutation endpoints and preventing guests/members from changing project configuration.

Changes:

  • Tighten StateViewSet.partial_update permission to @allow_permission([ROLE.ADMIN]).
  • Add a regression-focused contract test verifying guest/member are forbidden (403) and admin is allowed (200).
  • Add inline security context commentary referencing GHSA-4jpp-964m-27cr.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
apps/api/plane/app/views/state/base.py Restricts state PATCH authorization to admins for security hardening.
apps/api/plane/tests/contract/app/test_state_partial_update_admin_scope_app.py Adds contract coverage to prevent reintroducing the permission regression.

Comment thread apps/api/plane/app/views/state/base.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/api/plane/tests/contract/app/test_state_partial_update_admin_scope_app.py`:
- Around line 75-85: Update test_member_cannot_patch_state to include
default=True alongside the name change in the rejected PATCH request, then
assert after state.refresh_from_db() that state.default remains False, matching
the guest regression coverage while preserving the existing forbidden-status and
name assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1096bac5-fc28-44e8-b8a7-ae624cf80990

📥 Commits

Reviewing files that changed from the base of the PR and between a8e53b6 and 73675d7.

📒 Files selected for processing (2)
  • apps/api/plane/app/views/state/base.py
  • apps/api/plane/tests/contract/app/test_state_partial_update_admin_scope_app.py

…n member test (CodeRabbit/Copilot #9473)

- Copilot: partial_update mutates state data but (unlike create/destroy/
  mark_as_default) did not invalidate the workspaces/:slug/states/ cache, so
  clients could see stale state after a PATCH. Add the same @invalidate_cache
  decorator as the sibling writes.
- CodeRabbit: member test now also sends default=True and asserts it stays False,
  matching the guest regression coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 24, 2026 09:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@mguptahub mguptahub changed the title [WEB-8372] fix(security): restrict StateViewSet.partial_update to project admins (GHSA-4jpp-964m-27cr) [WEB-8372] fix(security): restrict StateViewSet.partial_update to project admins Aug 7, 2026
Explanations kept unchanged; only the IDs are removed.

Co-authored-by: Plane AI <noreply@plane.so>
Copilot AI review requested due to automatic review settings August 7, 2026 10:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

apps/api/plane/app/views/state/base.py:66

  • Decorator order means invalidate_cache(...) runs before @allow_permission(...). Because invalidate_cache deletes the shared workspaces/:slug/states/ key unconditionally (even when the permission wrapper returns 403), any unauthorized user can repeatedly evict this cache by spamming PATCH requests, increasing load and reducing cache effectiveness.

Swapping the decorator order ensures permission checks short-circuit before invalidation on 403 responses.

    @invalidate_cache(path="workspaces/:slug/states/", url_params=True, user=False)
    @allow_permission([ROLE.ADMIN])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants