close
Skip to content

fix(api): enforce workspace membership on GenericAssetEndpoint#9212

Merged
sriramveeraghanta merged 1 commit into
previewfrom
fix/api-asset-idor-workspace-auth
Jun 4, 2026
Merged

fix(api): enforce workspace membership on GenericAssetEndpoint#9212
sriramveeraghanta merged 1 commit into
previewfrom
fix/api-asset-idor-workspace-auth

Conversation

@sriramveeraghanta
Copy link
Copy Markdown
Member

@sriramveeraghanta sriramveeraghanta commented Jun 4, 2026

Description

The public REST API GenericAssetEndpoint (/api/v1/workspaces/<slug>/assets/) declared no permission_classes, so it inherited only IsAuthenticated. Because APIKeyAuthentication does not bind a Personal Access Token to a workspace and the workspace is read straight from the URL slug, any valid PAT could read, create, and modify assets in a workspace the caller is not a member of — a cross-workspace IDOR.

This is the public-API sibling of the dashboard asset fix (CVE-2026-46558 / GHSA-qw87-v5w3-6vxx): that fix added workspace-membership checks to the app/views/asset/v2.py endpoints, but the equivalent /api/v1/ endpoint was never covered.

Fix: add permission_classes = [WorkspaceUserPermission] to GenericAssetEndpoint. DRF evaluates it in dispatch before any handler runs, so GET/POST/PATCH all require active workspace membership. WorkspaceUserPermission ("any active member") matches the [ADMIN, MEMBER, GUEST] semantics of the dashboard fix and the existing sticky.py usage, so legitimate members — including guests — are unaffected.

Also adds the local /security/ advisory-notes folder to .gitignore.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

Test Scenarios

New contract regression tests in apps/api/plane/tests/contract/api/test_generic_asset.py (verified failing before the fix, passing after):

  • GET a non-member's asset (/api/v1/workspaces/<slug>/assets/<id>/) → 403, and no presigned download URL is generated.
  • POST an upload into a workspace the caller doesn't belong to → 403, and no FileAsset row is planted in the victim workspace.
  • PATCH a non-member's asset → 403, and the asset is left unmodified.
  • Positive control: an active member can still PATCH their own workspace's asset → 204.

Full plane/tests/contract/api/ suite passes (34 tests, no regressions).

References

  • Cross-workspace asset IDOR in the public REST API GenericAssetEndpoint (High).
  • Sibling of CVE-2026-46558 / GHSA-qw87-v5w3-6vxx (dashboard asset endpoints).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a security vulnerability that could allow unauthorized users to access assets from other workspaces. Workspace membership is now properly enforced.

The public REST API GenericAssetEndpoint (/api/v1/workspaces/<slug>/assets/)
declared no permission class, inheriting only IsAuthenticated. Since
APIKeyAuthentication does not bind a token to a workspace and the workspace is
read straight from the URL slug, any valid Personal Access Token could read
(GET), create (POST), and modify (PATCH) assets in a workspace the caller is
not a member of — a cross-workspace IDOR, the public-API sibling of the
CVE-2026-46558 dashboard asset fix.

Add permission_classes = [WorkspaceUserPermission] so every method requires
active workspace membership, matching the dashboard fix semantics. Also add
contract regression tests covering cross-workspace GET/POST/PATCH (now 403)
and a positive control confirming members retain access.

Also ignore the local /security/ advisory notes folder.
Copilot AI review requested due to automatic review settings June 4, 2026 12:30
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

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

Run ID: 7955a2ea-f2fd-461c-8f36-daa4eef07e3c

📥 Commits

Reviewing files that changed from the base of the PR and between b6e47cc and 61a7442.

📒 Files selected for processing (3)
  • .gitignore
  • apps/api/plane/api/views/asset.py
  • apps/api/plane/tests/contract/api/test_generic_asset.py

📝 Walkthrough

Walkthrough

This PR adds workspace membership authorization to the asset endpoint and validates the enforcement with comprehensive cross-workspace IDOR regression tests. Non-workspace-members are now blocked from accessing, uploading, or modifying assets in workspaces they do not belong to. The .gitignore configuration is also updated to ignore a /security/ directory.

Changes

Cross-Workspace Asset Access Control

Layer / File(s) Summary
Workspace permission enforcement on asset endpoint
apps/api/plane/api/views/asset.py
Import WorkspaceUserPermission and add it to GenericAssetEndpoint.permission_classes to enforce workspace membership verification on all asset requests.
Cross-workspace IDOR regression test suite
apps/api/plane/tests/contract/api/test_generic_asset.py
Create isolated test fixtures for a victim user, workspace, and asset; implement tests verifying non-members receive 403 on GET, POST, and PATCH operations; include positive-control test confirming members can modify their own assets.

Sequence Diagram

sequenceDiagram
  participant Client
  participant GenericAssetEndpoint
  participant WorkspaceUserPermission
  participant AssetOperation
  participant Database

  Client->>GenericAssetEndpoint: GET/POST/PATCH /workspace/slug/assets/
  GenericAssetEndpoint->>WorkspaceUserPermission: check_permission()
  alt Non-member
    WorkspaceUserPermission-->>GenericAssetEndpoint: 403 Forbidden
    GenericAssetEndpoint-->>Client: 403 Permission Denied
  else Active workspace member
    WorkspaceUserPermission-->>GenericAssetEndpoint: Permission granted
    GenericAssetEndpoint->>AssetOperation: Execute request handler
    AssetOperation->>Database: Read/write asset
    Database-->>AssetOperation: Result
    AssetOperation-->>Client: 200/204 Success
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

A rabbit hops through workspaces secure,
No IDOR tricks will slip through the door! 🐰🔐
Permission checks guard every asset,
Cross-workspace theft? The tests say "nay, I'd pass it!" ✓

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% 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 'fix(api): enforce workspace membership on GenericAssetEndpoint' accurately and concisely summarizes the main security fix in the changeset.
Description check ✅ Passed The description is comprehensive and complete, covering all required template sections including detailed explanation, type of change checkbox, test scenarios, and references.
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/api-asset-idor-workspace-auth

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 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 fixes a cross-workspace IDOR in the public REST GenericAssetEndpoint (/api/v1/workspaces/<slug>/assets/) by enforcing active workspace membership at the DRF permission layer, ensuring API keys/PATs cannot read or mutate assets in workspaces the caller doesn’t belong to.

Changes:

  • Enforce workspace membership by adding permission_classes = [WorkspaceUserPermission] to GenericAssetEndpoint.
  • Add contract regression tests covering GET/POST/PATCH cross-workspace access denial plus a positive-control member case.
  • Update .gitignore to ignore a local /security/ notes directory.

Reviewed changes

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

File Description
apps/api/plane/tests/contract/api/test_generic_asset.py Adds contract tests to prevent cross-workspace access to generic assets for API-key authenticated callers.
apps/api/plane/api/views/asset.py Applies WorkspaceUserPermission to GenericAssetEndpoint so all methods require active membership in the URL workspace.
.gitignore Ignores a local /security/ directory for non-versioned security notes.

@sriramveeraghanta sriramveeraghanta merged commit 9a30a07 into preview Jun 4, 2026
17 of 19 checks passed
@sriramveeraghanta sriramveeraghanta deleted the fix/api-asset-idor-workspace-auth branch June 4, 2026 13:19
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