Any workspace member can publish (and delete) another user's private draft
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 59.6k
- Forks
- 5.8k
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 49
Description
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the POST /api/workspaces/{slug}/draft-to-issue/{draft_id}/ endpoint and compare its authorization with the draft listing and detail endpoints. Use the Dokkimi definition in dokkimi/dokkimi-in-the-wild/.dokkimi/plane to reproduce the member-publishing scenario. Done means another workspace member cannot publish or delete a draft they do not own, while the owner’s publish flow still works.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- django, postgresql, python, typescript
- Domain
- api, authorization, backend, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100