fix(api): enforce workspace membership on GenericAssetEndpoint#9212
Conversation
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.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis 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 ChangesCross-Workspace Asset Access Control
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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]toGenericAssetEndpoint. - Add contract regression tests covering GET/POST/PATCH cross-workspace access denial plus a positive-control member case.
- Update
.gitignoreto 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. |
Description
The public REST API
GenericAssetEndpoint(/api/v1/workspaces/<slug>/assets/) declared nopermission_classes, so it inherited onlyIsAuthenticated. BecauseAPIKeyAuthenticationdoes not bind a Personal Access Token to a workspace and the workspace is read straight from the URLslug, 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.pyendpoints, but the equivalent/api/v1/endpoint was never covered.Fix: add
permission_classes = [WorkspaceUserPermission]toGenericAssetEndpoint. DRF evaluates it indispatchbefore 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 existingsticky.pyusage, so legitimate members — including guests — are unaffected.Also adds the local
/security/advisory-notes folder to.gitignore.Type of Change
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):/api/v1/workspaces/<slug>/assets/<id>/) →403, and no presigned download URL is generated.403, and noFileAssetrow is planted in the victim workspace.403, and the asset is left unmodified.204.Full
plane/tests/contract/api/suite passes (34 tests, no regressions).References
GenericAssetEndpoint(High).🤖 Generated with Claude Code
Summary by CodeRabbit