Skip to content

fix(api): scope cross-workspace resource lookups to prevent IDOR#9008

Merged
sriramveeraghanta merged 1 commit intopreviewfrom
fix/idor-cross-workspace-resource-access
May 4, 2026
Merged

fix(api): scope cross-workspace resource lookups to prevent IDOR#9008
sriramveeraghanta merged 1 commit intopreviewfrom
fix/idor-cross-workspace-resource-access

Conversation

@sriramveeraghanta
Copy link
Copy Markdown
Member

@sriramveeraghanta sriramveeraghanta commented May 4, 2026

Summary

Three endpoints fetched objects by primary key alone after a workspace-scoped permission check. An authenticated caller could act on resources in other workspaces by supplying a foreign UUID together with their own workspace slug in the URL.

  • ProjectViewSet.partial_update (apps/api/plane/app/views/project/base.py:335): A workspace admin could PATCH any project's name, identifier, description, network/visibility, default assignee, etc. across workspaces. Scope the Project.objects.get(...) lookup by workspace__slug=slug, matching the pattern already used in destroy (line 393).
  • BulkEstimatePointEndpoint.partial_update (apps/api/plane/app/views/estimate/base.py:116): Any project member could rename or retype estimates in foreign workspaces. Scope the Estimate.objects.get(...) lookup by workspace__slug and project_id, matching retrieve (line 104) and destroy (line 148).
  • WorkspaceUserProfileEndpoint.get (apps/api/plane/app/views/workspace/user.py:282): Any authenticated workspace member could read another user's email, name, avatar, timezone, and join date by UUID, regardless of whether the target user belonged to that workspace. Require the target user_id to be an active WorkspaceMember of the requested workspace before exposing profile data; fetch the user via the membership row in a single query.

The reported logic flaw in the partial_update permission guard (workspace admins bypass the project-admin check) is intentional behavior in this codebase — workspace admins do have project-edit authority. The bug was strictly the unscoped object lookup; once the project fetch is bound to the workspace slug, an admin of workspace A cannot reach into workspace B even if the guard is satisfied.

Test plan

  • As a workspace-A admin, attempt PATCH /api/v1/workspaces/workspace-a/projects/{workspace-B-project-uuid}/ with a body changing name. Expect 404 (was 200 + mutation).
  • As a workspace-A admin/member, attempt PATCH /api/v1/workspaces/workspace-a/projects/{workspace-A-project-uuid}/estimates/{workspace-B-estimate-uuid}/. Expect 404 (was 200 + mutation).
  • As a workspace-A member, attempt GET /api/v1/workspaces/workspace-a/user-profile/{user-not-in-workspace-A-uuid}/. Expect 404 (was 200 + PII).
  • Regress the happy paths: in-workspace project edit, in-project estimate edit, and same-workspace user profile fetch all still return 200 with expected payloads.

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Fixed estimate updates to properly validate workspace and project context, preventing unintended cross-workspace modifications
    • Strengthened project update validation to enforce workspace-level scoping for better isolation
    • Enhanced workspace user profile endpoint to verify active workspace membership before granting access

`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.
Copilot AI review requested due to automatic review settings May 4, 2026 10:19
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aa8e6fd2-06d9-4132-80dd-96f4ec6290ac

📥 Commits

Reviewing files that changed from the base of the PR and between a62fe8a and c2c2df3.

📒 Files selected for processing (3)
  • apps/api/plane/app/views/estimate/base.py
  • apps/api/plane/app/views/project/base.py
  • apps/api/plane/app/views/workspace/user.py

📝 Walkthrough

Walkthrough

Three API endpoints add stricter scope validation to their resource lookups: Estimate now requires workspace and project context, Project requires workspace context, and WorkspaceMember lookup replaces direct user access with active membership verification.

Changes

Scope Tightening for Resource Access Control

Layer / File(s) Summary
Estimate Lookup Scoping
apps/api/plane/app/views/estimate/base.py
BulkEstimatePointEndpoint.partial_update restricts estimate fetch to include workspace__slug=slug and project_id=project_id alongside the estimate pk, ensuring updates only affect estimates in the specified workspace and project.
Project Lookup Scoping
apps/api/plane/app/views/project/base.py
ProjectViewSet.partial_update adds workspace__slug=slug to the project lookup, narrowing object resolution to the requested workspace instead of allowing cross-workspace access by pk alone.
Workspace User Lookup Scoping
apps/api/plane/app/views/workspace/user.py
WorkspaceUserProfileEndpoint.get replaces direct User.objects.get(pk=user_id) with a WorkspaceMember query requiring active membership (is_active=True) in the workspace, enforcing that target users must be active members before data is returned.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Three endpoints guard their gates with care,
No workspace scope? You can't go there!
From estimates to users, all made tight,
Access controls shine oh-so-right! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. 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 and concisely identifies the main change: fixing IDOR vulnerabilities by scoping resource lookups to workspaces, which directly corresponds to the changeset.
Description check ✅ Passed The description includes a comprehensive Summary explaining the vulnerability, Type of Change (Bug fix), detailed Test Scenarios with concrete steps, and References to the affected code locations, matching the template structure well.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/idor-cross-workspace-resource-access

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 hardens several workspace-scoped “app” endpoints against IDOR by ensuring resource lookups are constrained to the workspace (and project where applicable), preventing authenticated users from operating on cross-workspace resources by UUID.

Changes:

  • Scope ProjectViewSet.partial_update project lookup by workspace__slug.
  • Scope BulkEstimatePointEndpoint.partial_update estimate lookup by workspace__slug and project_id.
  • Ensure WorkspaceUserProfileEndpoint.get only returns profile data for users who are active members of the requested workspace (fetching the user via the membership row).

Reviewed changes

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

File Description
apps/api/plane/app/views/workspace/user.py Prevents cross-workspace profile access by requiring an active workspace membership for the target user.
apps/api/plane/app/views/project/base.py Prevents cross-workspace project mutation by scoping the project lookup to the workspace slug.
apps/api/plane/app/views/estimate/base.py Prevents cross-workspace estimate mutation by scoping estimate lookup to workspace + project.

Comment thread apps/api/plane/app/views/project/base.py
@sriramveeraghanta sriramveeraghanta merged commit 9491bdb into preview May 4, 2026
20 of 21 checks passed
@sriramveeraghanta sriramveeraghanta deleted the fix/idor-cross-workspace-resource-access branch May 4, 2026 12:28
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.

3 participants