[SECUR-104] fix: Arbitrary Modification of API Token Rate Limits#8612
Conversation
|
Linked to Plane Work Item(s) This comment was auto-generated by Plane |
📝 WalkthroughWalkthroughAPI token serializer now exposes three additional read-only fields (is_active, last_used, user_type). Contract tests updated to validate the detail endpoint and enforce immutability constraints on token properties during PATCH operations. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 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.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/api/plane/app/serializers/api.py (1)
15-24:⚠️ Potential issue | 🔴 CriticalAdd
allowed_rate_limitandis_servicetoread_only_fields.The serializer is used for PATCH updates (apps/api/plane/app/views/api.py), and
allowed_rate_limitis currently writable. This allows authenticated users to modify their own API token rate limits, keeping the SECUR-104 vector open. Additionally,is_serviceshould also be protected. Add both toread_only_fields:🔒 Proposed fix
read_only_fields = [ "token", "expired_at", "created_at", "updated_at", "workspace", "user", + "allowed_rate_limit", + "is_service", "is_active", "last_used", "user_type", ]
🧹 Nitpick comments (1)
apps/api/plane/tests/contract/app/test_api_token.py (1)
336-385: Add a PATCH immutability test forallowed_rate_limit.Given SECUR-104, this is the most important field to lock down; adding a test here will prevent regressions.
✅ Suggested test
`@pytest.mark.django_db` def test_patch_cannot_modify_user_type(self, session_client, create_user, create_api_token_for_user): """Test that user_type cannot be modified via PATCH""" # Arrange session_client.force_authenticate(user=create_user) url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk}) update_data = {"user_type": 1} # Act response = session_client.patch(url, update_data, format="json") # Assert assert response.status_code == status.HTTP_200_OK create_api_token_for_user.refresh_from_db() assert create_api_token_for_user.user_type == 0 + `@pytest.mark.django_db` + def test_patch_cannot_modify_allowed_rate_limit(self, session_client, create_user, create_api_token_for_user): + """Test that allowed_rate_limit cannot be modified via PATCH""" + # Arrange + session_client.force_authenticate(user=create_user) + url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk}) + original_rate_limit = create_api_token_for_user.allowed_rate_limit + update_data = {"allowed_rate_limit": "9999/min"} + + # Act + response = session_client.patch(url, update_data, format="json") + + # Assert + assert response.status_code == status.HTTP_200_OK + create_api_token_for_user.refresh_from_db() + assert create_api_token_for_user.allowed_rate_limit == original_rate_limit
Manually re-implement 10 upstream security fixes on our customized fork, verifying each against current panel state to avoid conflict with our Persian/RTL, modules, LLM, and CSV importer customizations. Privilege escalation - project member.partial_update: derive is_workspace_admin from requester not target; restrict role changes to admins; block assigning equal/higher roles than your own (GHSA-x63v-p7wc-47x4, GHSA-494h-3rcq-5g3c) IDOR (cross-workspace resource access) - ProjectViewSet.partial_update: scope by workspace__slug - BulkEstimatePointEndpoint: scope by workspace__slug + project_id - WorkspaceUserProfileEndpoint: require target user to be active member - IssueBulkUpdateDateEndpoint: scope by workspace__slug + project_id Workspace authorization - WorkspaceFileAssetEndpoint: add @allow_permission to post/patch/delete/get - DuplicateAssetEndpoint: scope source asset to caller's workspaces - allow_permission(creator=True): require workspace membership ORM injection - Analytics: centralize VALID_ANALYTICS_FIELDS / VALID_YAXIS allowlist; defense-in-depth raise in build_graph_plot/extract_axis; validate stored query_dict in SavedAnalyticEndpoint (GHSA-93x3-ghh7-72j3) Path traversal - Add sanitize_filename() utility; apply to all 8 upload endpoints and both get_upload_path() model paths (GHSA-v57h-5999-w7xp) SSRF - work_item_link_task: extract safe_get() that validates each redirect hop; use for both page and favicon (favicon was the missed code path) - webhook: replace inline IP check with validate_url + WEBHOOK_ALLOWED_IPS allowlist; re-validate at send time to prevent DNS rebinding; sanitize error messages - New WEBHOOK_ALLOWED_IPS env var (comma-separated CIDRs) — set this for any internal webhook targets before deploy Misc - IssueListEndpoint: filter out issues linked to soft-deleted states - InstanceConfiguration.patch: strip whitespace and handle null values Skipped (already in our merge base under different SHA) - IDOR in asset/v2.py + issue/attachment.py (makeplane#8644) - Member info disclosure in space project endpoint (makeplane#8646) - API token rate_limit modification (makeplane#8612) Refs: GHSA-x63v-p7wc-47x4, GHSA-494h-3rcq-5g3c, GHSA-93x3-ghh7-72j3, GHSA-v57h-5999-w7xp, GHSA-9fr2-pprw-pp9j, GHSA-qw87-v5w3-6vxx, GHSA-4q54-h4x9-m329 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Description
Update the patch endpoint for the Personal Access token endpoint with right permissions.
Summary by CodeRabbit
New Features
Bug Fixes