Any workspace member can publish (and delete) another user's private draft
What we observed
Draft issues in Plane are personal — the list and detail endpoints filter by the current user, so each member only sees their own drafts. However, the "publish draft to issue" endpoint does not verify that the requesting user owns the draft. Any workspace member who knows (or guesses) the draft's UUID can convert it to a real issue and permanently delete the draft in the process.
1. Member publishes admin's private draft
User A (admin) created a draft issue with confidential planning notes. User B (member in the same workspace) called the draft-to-issue endpoint with the draft's ID. Despite never being able to see the draft through the listing or detail APIs, User B's request succeeded with 201. The draft was converted into a real issue in the project, and the original draft record was deleted. We verified at the database level that the draft no longer exists and the issue was created with the admin's original content.
Impact
- Any workspace member can force-publish another user's unfinished draft, exposing work-in-progress content before the author intended
- The draft is permanently deleted as part of the publish operation — the original author loses their draft without warning or any audit trail pointing to who published it
- Since draft IDs are UUIDs, a determined attacker with workspace access could enumerate recent drafts via timing or ID pattern analysis
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 (User A) and sign up a second user (User B)
- Create a workspace with User A
- Add User B to the workspace as a MEMBER (role 15)
- Create a project within the workspace
Authenticate both users (session cookies from sign-up/sign-in).
User A creates a draft:
POST /api/workspaces/{slug}/draft-issues/
Cookie: sessionid={admin_session}
Content-Type: application/json
{"name": "Admin's Confidential Draft", "description_html": "<p>This draft contains private planning notes</p>", "project_id": "{project_id}", "priority": "high"}
Returns 201 with the draft's id.
Control — User B cannot see User A's draft in listing:
GET /api/workspaces/{slug}/draft-issues/
Cookie: sessionid={member_session}
Returns 200 with an empty results array — User B has no drafts, and User A's draft is correctly hidden.
Bug — User B publishes User A's draft:
POST /api/workspaces/{slug}/draft-to-issue/{draft_id}/
Cookie: sessionid={member_session}
Content-Type: application/json
{"name": "Admin's Confidential Draft", "description_html": "<p>This draft contains private planning notes</p>", "priority": "high"}
Returns 201. The draft has been converted to a real issue in the project.
Database verification:
-- Draft was deleted
SELECT count(*) FROM draft_issues WHERE id = '{draft_id}' AND deleted_at IS NULL;
-- Returns 0
-- Issue was created with the original draft content
SELECT name, priority FROM issues WHERE id = '{issue_id}';
-- Returns name = 'Admin's Confidential Draft', priority = 'high'
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.
Any workspace member can publish (and delete) another user's private draft
What we observed
Draft issues in Plane are personal — the list and detail endpoints filter by the current user, so each member only sees their own drafts. However, the "publish draft to issue" endpoint does not verify that the requesting user owns the draft. Any workspace member who knows (or guesses) the draft's UUID can convert it to a real issue and permanently delete the draft in the process.
1. Member publishes admin's private draft
User A (admin) created a draft issue with confidential planning notes. User B (member in the same workspace) called the draft-to-issue endpoint with the draft's ID. Despite never being able to see the draft through the listing or detail APIs, User B's request succeeded with 201. The draft was converted into a real issue in the project, and the original draft record was deleted. We verified at the database level that the draft no longer exists and the issue was created with the admin's original content.
Impact
Steps to reproduce
Environment:
makeplane/plane-backend:stableENABLE_EMAIL_PASSWORD=1,ENABLE_SIGNUP=1Setup:
Authenticate both users (session cookies from sign-up/sign-in).
User A creates a draft:
Returns 201 with the draft's
id.Control — User B cannot see User A's draft in listing:
Returns 200 with an empty results array — User B has no drafts, and User A's draft is correctly hidden.
Bug — User B publishes User A's draft:
Returns 201. The draft has been converted to a real issue in the project.
Database verification:
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.