Skip to content

Completed cycles can be edited by including sort_order in the payload #9362

Description

@avisprince

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:

  1. Complete instance admin sign-up to bootstrap the Plane instance
  2. Create a workspace and project
  3. Create an API token for use with the v1 API
  4. 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/planedokkimi run reproduces it from scratch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions