Files
plane/apps
Manish Gupta d9841a5e02 fix(security): guard ProjectViewSet PUT — routed verb fell through to DRF
urls/project.py maps "put": "update", but ProjectViewSet never defined update,
so PUT fell through to DRF's ModelViewSet.update. The class sets no
permission_classes, so that ran under the default IsAuthenticated while
partial_update on the identical URL requires workspace or project admin.

A workspace MEMBER with no membership of a Secret project could PUT it to
network=2 (Public) and then join via the public-project API — read plus
member-level write on a project they were never in. Confirmed: an unpatched
non-member PUT returns 200 and flips network.

PUT now routes through partial_update so both verbs enforce the same rule; a
full PUT-replace is not a meaningful operation for projects.

Contract tests cover the denied PUT, that a denial writes nothing, that PUT and
PATCH reach the same verdict (the defect was that they disagreed), and a
positive control that an admin PUT still works. Fail-before verified: 3 failed /
1 passed unpatched, 4 passed patched.

Note the payload must be complete — DRF's generic update runs the serializer
with partial=False, so an incomplete body 400s on validation before
authorization differs, and the test would pass either way.

Co-authored-by: Plane AI <noreply@plane.so>
2026-08-13 17:04:16 +05:30
..
2026-08-04 20:11:48 +05:30
2026-08-04 20:11:48 +05:30
2026-08-04 20:11:48 +05:30