Skip to content

[INFRA-777] fix(security): scope ProjectArchiveUnarchiveAPIEndpoint to the URL project - #9706

Open
mguptahub wants to merge 1 commit into
previewfrom
infra-777/project-archive-scope-to-project
Open

[INFRA-777] fix(security): scope ProjectArchiveUnarchiveAPIEndpoint to the URL project#9706
mguptahub wants to merge 1 commit into
previewfrom
infra-777/project-archive-scope-to-project

Conversation

@mguptahub

@mguptahub mguptahub commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • ProjectArchiveUnarchiveAPIEndpoint used ProjectBasePermission, whose POST branch is written for project creation and checks only workspace-level role (ADMIN/MEMBER) with no project_id binding at all.
  • Since archive is a POST but is not creation, any active workspace member could archive (and delete every UserFavorite row on) any project in the workspace — including a fully private one they have no ProjectMember row on and cannot otherwise read.
  • DELETE (unarchive) was never affected — it falls through to ProjectBasePermission's non-POST branch, which does check project membership. So this was specifically an asymmetry between the two verbs of the same endpoint. That branch also turned out to be stricter than intended (project ADMIN, or workspace-ADMIN-plus-project-membership — not just an active project MEMBER), which this fix corrects too.
  • Fix: a dedicated ProjectArchiveUnarchivePermission, scoped to project_id, mirroring the app-layer twin's gate (allow_permission([ROLE.ADMIN, ROLE.MEMBER])) on both post and delete — instead of touching ProjectBasePermission itself, which is correctly relied on by the actual project-create endpoint and by ProjectDetailAPIEndpoint's patch/delete.
  • Checked thoroughly for an existing fix first: ProjectMemberPermission has the same POST-branch shape but is a different class, already tracked and being fixed by the open f63c cluster's PR [SECUR-247] fix(security): scope ProjectMemberPermission POST to the URL project #9596 — confirmed by reading that PR's actual diff, which explicitly leaves ProjectBasePermission untouched.

Test plan

  • New regression tests in apps/api/plane/tests/contract/api/test_project_archive_scope.py: a workspace member with no project membership cannot archive or unarchive a private project; an active project member (not just admin) can do both
  • Verified fail-before/pass-after via git stash — the archive-bypass test fails against pre-fix code, and the positive-control test also caught a genuine pre-existing over-restriction (a plain project member couldn't unarchive their own project before this fix)
  • Related project contract test suites still green (18 passed)
  • ruff check / ruff format --check clean

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened project archive and unarchive access controls.
    • Workspace members without project-level membership can no longer archive or unarchive projects.
    • Authorized project members retain access to both actions.
…o the URL project

ProjectArchiveUnarchiveAPIEndpoint used ProjectBasePermission, whose POST
branch is written for project creation and checks only workspace-level
role (ADMIN/MEMBER) with no project_id binding at all. Since archive is
POST but is not creation, any active workspace member could archive (and
delete every UserFavorite row on) any project in the workspace, including
a fully private one they have no ProjectMember row on and cannot
otherwise read.

DELETE (unarchive) was never affected by this — it falls through to
ProjectBasePermission's non-POST branch, which does check project
membership, so this was an asymmetry between the two verbs of the same
endpoint (and that branch was itself stricter than intended: it required
project ADMIN or workspace-ADMIN-plus-project-membership, not just an
active project MEMBER).

Give the endpoint a dedicated ProjectArchiveUnarchivePermission, scoped to
project_id, mirroring the app-layer twin's gate (active ProjectMember
with role ADMIN or MEMBER, or any active ProjectMember plus a workspace
ADMIN) on both post and delete — instead of touching ProjectBasePermission
itself, which is correctly relied on by the actual project-create
endpoint and by ProjectDetailAPIEndpoint's patch/delete.

3 new tests, fail-before verified.

Co-authored-by: Plane AI <noreply@plane.so>
Copilot AI lite review requested due to automatic review settings August 28, 2026 09:24
@makeplane

makeplane Bot commented Aug 28, 2026

Copy link
Copy Markdown
@coderabbitai

coderabbitai Bot commented Aug 28, 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: 7b145d9d-129d-4200-a529-4ceee3a95db2

📥 Commits

Reviewing files that changed from the base of the PR and between 3478d4f and 77a8eff.

📒 Files selected for processing (4)
  • apps/api/plane/api/views/project.py
  • apps/api/plane/app/permissions/__init__.py
  • apps/api/plane/app/permissions/project.py
  • apps/api/plane/tests/contract/api/test_project_archive_scope.py

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The archive and unarchive endpoint now uses project-scoped authorization. Workspace members without project membership receive 403 responses. Active project members retain archive and unarchive access.

Changes

Project archive authorization

Layer / File(s) Summary
Project-scoped permission contract
apps/api/plane/app/permissions/project.py, apps/api/plane/app/permissions/__init__.py
Adds ProjectArchiveUnarchivePermission, which validates active project membership or workspace administrator membership on the target project.
Endpoint enforcement and regression coverage
apps/api/plane/api/views/project.py, apps/api/plane/tests/contract/api/test_project_archive_scope.py
Applies the permission to archive and unarchive requests. Tests reject unauthorized workspace members and preserve access for active project members.

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

Merge Risk: ⚪ Minimal · up to 77a8e

The PR scopes archive and unarchive actions to active membership in the selected project and preserves the existing endpoint contract. No actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: dheeru0198, pablohashescobar

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main security fix: scoping the archive/unarchive endpoint to the URL project.
Description check ✅ Passed The description explains the vulnerability, fix, authorization behavior, scope, and test results. It omits some template headings, but it provides the required change and test information in equivalen…
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.
Full details: Description check

Explanation

The description explains the vulnerability, fix, authorization behavior, scope, and test results. It omits some template headings, but it provides the required change and test information in equivalent sections.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch infra-777/project-archive-scope-to-project

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.

@mguptahub

Copy link
Copy Markdown
Collaborator Author

/code-review: clean, no findings. Verified the permission class's query logic against both ProjectBasePermission's non-POST branch and the app-layer allow_permission([ROLE.ADMIN, ROLE.MEMBER]) decorator, confirmed view.project_id/view.workspace_slug are valid BaseAPIView properties, and confirmed ProjectBasePermission/ProjectMemberPermission are correctly left untouched.

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

Scopes the external API’s project archive/unarchive endpoint authorization to the target project_id, closing a workspace-member authorization bypass and aligning archive/unarchive behavior between POST and DELETE.

Changes:

  • Introduces ProjectArchiveUnarchivePermission to enforce project-scoped membership checks for both archive and unarchive.
  • Updates ProjectArchiveUnarchiveAPIEndpoint to use the new permission instead of ProjectBasePermission.
  • Adds contract regression tests covering both the bypass case and a positive-control member case.

Reviewed changes

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

File Description
apps/api/plane/tests/contract/api/test_project_archive_scope.py Adds regression/positive-control tests for archive/unarchive project scoping.
apps/api/plane/app/permissions/project.py Adds ProjectArchiveUnarchivePermission with project-scoped checks for POST/DELETE.
apps/api/plane/app/permissions/init.py Exports the new permission class for app-wide import usage.
apps/api/plane/api/views/project.py Switches the archive/unarchive endpoint to the new permission class.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

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

Labels

None yet

2 participants