Skip to content

Fix project invitation authorization#9301

Open
gauravbsinghal wants to merge 1 commit into
makeplane:previewfrom
gauravbsinghal:niro-fix-project-invitation-auth
Open

Fix project invitation authorization#9301
gauravbsinghal wants to merge 1 commit into
makeplane:previewfrom
gauravbsinghal:niro-fix-project-invitation-auth

Conversation

@gauravbsinghal

@gauravbsinghal gauravbsinghal commented Jun 24, 2026

Copy link
Copy Markdown

Finding

Niro TC-B92EA42A found that an authenticated user from another workspace could list and retrieve invitation records for a private project, including invitee emails, roles, and raw invitation tokens.

Original PoC

As a user who belongs only to another workspace, request:

  • GET /api/workspaces/<victim_slug>/projects/<victim_project_id>/invitations/
  • GET /api/workspaces/<victim_slug>/projects/<victim_project_id>/invitations/<invitation_id>/

Before this fix the API returned 200 OK and serialized project invitation data.

Red test on unfixed code

plane/tests/contract/app/test_project_invitation_app.py F

_ TestProjectInvitationAPI.test_foreign_workspace_user_cannot_read_project_invitations _

>       assert list_response.status_code == status.HTTP_403_FORBIDDEN
E       assert 200 == 403
E        +  where 200 = <Response status_code=200, "application/json">.status_code
E        +  and   403 = status.HTTP_403_FORBIDDEN

FAILED plane/tests/contract/app/test_project_invitation_app.py::TestProjectInvitationAPI::test_foreign_workspace_user_cannot_read_project_invitations
============================== 1 failed in 0.99s ===============================

Green test on fixed code

plane/tests/contract/app/test_project_invitation_app.py .

============================== 1 passed in 0.98s ===============================

Fix

Add explicit project-admin permission gates to project invitation list, retrieve, and destroy, matching the existing create gate. Added a regression test that verifies a user from another workspace receives 403 and cannot obtain the invitation token.

Pentested and fixed by Niro

Summary by CodeRabbit

  • Bug Fixes

    • Project invitation endpoints now enforce explicit authorization restrictions
    • Prevents unauthorized access to invitation details across workspaces
  • Tests

    • Added authorization validation tests for invitation endpoints

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Gaurav Singhal seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds explicit list, retrieve, and destroy method overrides to ProjectInvitationsViewset, each decorated with admin-only permissions delegating to BaseViewSet. A new contract test verifies that a user from a different workspace receives 403 FORBIDDEN on both invitation list and detail endpoints with no token leakage.

Changes

ProjectInvitationsViewset Admin Permission Enforcement

Layer / File(s) Summary
Admin-only list, retrieve, and destroy overrides
apps/api/plane/app/views/project/invite.py
Adds list, retrieve, and destroy methods to ProjectInvitationsViewset, each decorated with @allow_permission([ROLE.ADMIN]) and delegating to the corresponding BaseViewSet method with slug, project_id, and pk forwarded.
Contract test: 403 for cross-workspace user
apps/api/plane/tests/contract/app/test_project_invitation_app.py
New pytest contract test creates a project invitation in one workspace, authenticates as a user from another workspace, calls both list and detail endpoints, and asserts 403 FORBIDDEN plus absence of the invitation token from both response bodies.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~5 minutes

Suggested reviewers

  • pablohashescobar
  • dheeru0198

Poem

🐇 Hop hop, the admin gate is set,
No cross-workspace peeking, not just yet!
403 bounces strangers away,
The invitation token's safe today.
A rabbit guards the project door with care~ 🔑

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix project invitation authorization' directly and clearly describes the main security fix in the changeset.
Description check ✅ Passed The PR description comprehensively documents the security finding, includes a PoC, test evidence (red then green), and the fix details, covering all essential information.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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.

🧹 Nitpick comments (1)
apps/api/plane/tests/contract/app/test_project_invitation_app.py (1)

42-48: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add contract coverage for the new destroy permission gate.

Line 42 to Line 48 validate list/retrieve only; destroy was also changed in this PR and should be asserted as 403 for cross-workspace users.

Suggested test extension
         list_response = session_client.get(list_url)
         detail_response = session_client.get(detail_url)
+        delete_response = session_client.delete(detail_url)

         assert list_response.status_code == status.HTTP_403_FORBIDDEN
         assert detail_response.status_code == status.HTTP_403_FORBIDDEN
+        assert delete_response.status_code == status.HTTP_403_FORBIDDEN
         assert b"secret-project-invite-token" not in list_response.content
         assert b"secret-project-invite-token" not in detail_response.content
🤖 Prompt for 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.

In `@apps/api/plane/tests/contract/app/test_project_invitation_app.py` around
lines 42 - 48, The test currently only validates list and retrieve endpoints for
403 permission gates, but the destroy endpoint was also modified in this PR and
needs contract coverage. Add a destroy_response by making a DELETE request to a
destroy_url endpoint using session_client, similar to how list_response and
detail_response are obtained. Assert that destroy_response returns
status.HTTP_403_FORBIDDEN and verify that b"secret-project-invite-token" is not
exposed in the destroy_response.content, matching the pattern of the existing
assertions for list and detail endpoints.
🤖 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.

Nitpick comments:
In `@apps/api/plane/tests/contract/app/test_project_invitation_app.py`:
- Around line 42-48: The test currently only validates list and retrieve
endpoints for 403 permission gates, but the destroy endpoint was also modified
in this PR and needs contract coverage. Add a destroy_response by making a
DELETE request to a destroy_url endpoint using session_client, similar to how
list_response and detail_response are obtained. Assert that destroy_response
returns status.HTTP_403_FORBIDDEN and verify that b"secret-project-invite-token"
is not exposed in the destroy_response.content, matching the pattern of the
existing assertions for list and detail endpoints.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 73e501e0-fb15-4711-849a-f3449c7d1163

📥 Commits

Reviewing files that changed from the base of the PR and between 6c9dbb5 and c259b95.

📒 Files selected for processing (2)
  • apps/api/plane/app/views/project/invite.py
  • apps/api/plane/tests/contract/app/test_project_invitation_app.py

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