Completed cycles can be edited by including sort_order in the payload
What we observed
The cycle update endpoint is intended to lock completed cycles (those with an end_date in the past) so that only sort_order can be changed. However, any field — name, description, dates — can be modified as long as sort_order is included alongside the other fields in the same PATCH request.
1. Completed cycle accepts name and description changes
We created a cycle with start and end dates in the past (already completed). A PATCH request with only {"name": "new name"} was correctly rejected with 400 — "The Cycle has already been completed so it cannot be edited." But when we sent {"sort_order": 1, "name": "new name", "description": "new desc"}, the request succeeded with 200 and the cycle's name and description were both updated. We verified at the database level that the name and description had been overwritten.
Impact
- Completed cycle data can be silently altered, undermining the integrity of historical sprint records
- Auditing and reporting that relies on completed-cycle immutability becomes unreliable
- Any user with write access to cycles can retroactively change cycle metadata after a sprint closes
Steps to reproduce
Environment:
makeplane/plane-backend:stable
- PostgreSQL 15.7, Valkey 7.2.11, RabbitMQ 3.13.6, MinIO
ENABLE_EMAIL_PASSWORD=1, ENABLE_SIGNUP=1
Setup:
- Complete instance admin sign-up to bootstrap the Plane instance
- Create a workspace and project
- Create an API token for use with the v1 API
- Create a cycle with both
start_date and end_date in the past (e.g., 2024-01-01 to 2024-01-14) — this makes the cycle "completed"
The cycle is created via:
POST /api/v1/workspaces/{slug}/projects/{project_id}/cycles/
X-Api-Key: {token}
Content-Type: application/json
{"name": "Sprint Alpha", "description": "Original description", "start_date": "2024-01-01", "end_date": "2024-01-14"}
Control — sort_order-only edit (should succeed):
PATCH /api/v1/workspaces/{slug}/projects/{project_id}/cycles/{cycle_id}/
X-Api-Key: {token}
Content-Type: application/json
{"sort_order": 1}
Returns 200 — sort_order is the one field that should remain editable on completed cycles.
Control — name-only edit (should be rejected):
PATCH /api/v1/workspaces/{slug}/projects/{project_id}/cycles/{cycle_id}/
X-Api-Key: {token}
Content-Type: application/json
{"name": "Attempted Change"}
Returns 400 — "The Cycle has already been completed so it cannot be edited." This confirms the guard is active.
Bug — including sort_order alongside other fields bypasses the guard:
PATCH /api/v1/workspaces/{slug}/projects/{project_id}/cycles/{cycle_id}/
X-Api-Key: {token}
Content-Type: application/json
{"sort_order": 1, "name": "HIJACKED NAME", "description": "HIJACKED DESCRIPTION"}
Returns 200. The cycle's name and description are both updated despite the cycle being completed.
Database verification:
SELECT name, description FROM cycles WHERE id = '{cycle_id}';
Returns name = 'HIJACKED NAME' and description = 'HIJACKED DESCRIPTION', confirming the data was persisted.
Reproducing with Dokkimi
Dokkimi is an open-source testing tool that stands up isolated Docker environments from declarative YAML definitions — services, databases, seed data, test steps, and assertions all in one file. The definition for this bug is in dokkimi/dokkimi-in-the-wild/.dokkimi/plane — dokkimi run reproduces it from scratch.
Completed cycles can be edited by including sort_order in the payload
What we observed
The cycle update endpoint is intended to lock completed cycles (those with an end_date in the past) so that only
sort_ordercan be changed. However, any field — name, description, dates — can be modified as long assort_orderis included alongside the other fields in the same PATCH request.1. Completed cycle accepts name and description changes
We created a cycle with start and end dates in the past (already completed). A PATCH request with only
{"name": "new name"}was correctly rejected with 400 — "The Cycle has already been completed so it cannot be edited." But when we sent{"sort_order": 1, "name": "new name", "description": "new desc"}, the request succeeded with 200 and the cycle's name and description were both updated. We verified at the database level that the name and description had been overwritten.Impact
Steps to reproduce
Environment:
makeplane/plane-backend:stableENABLE_EMAIL_PASSWORD=1,ENABLE_SIGNUP=1Setup:
start_dateandend_datein the past (e.g., 2024-01-01 to 2024-01-14) — this makes the cycle "completed"The cycle is created via:
Control — sort_order-only edit (should succeed):
Returns 200 — sort_order is the one field that should remain editable on completed cycles.
Control — name-only edit (should be rejected):
Returns 400 — "The Cycle has already been completed so it cannot be edited." This confirms the guard is active.
Bug — including sort_order alongside other fields bypasses the guard:
Returns 200. The cycle's name and description are both updated despite the cycle being completed.
Database verification:
Returns
name = 'HIJACKED NAME'anddescription = 'HIJACKED DESCRIPTION', confirming the data was persisted.Reproducing with Dokkimi
Dokkimi is an open-source testing tool that stands up isolated Docker environments from declarative YAML definitions — services, databases, seed data, test steps, and assertions all in one file. The definition for this bug is in dokkimi/dokkimi-in-the-wild/.dokkimi/plane —
dokkimi runreproduces it from scratch.