# Vikunja has cross-tenant IDOR in kanban move-task endpoint via unauthorized body task_id

Vikunja's kanban move-task endpoint authorizes only the URL's bucket, not the body-supplied task_id, letting any user read and mark-done any task by sequential ID.

- Payout: Research
- Severity: high
- Program: Vikunja
- Found by: hoangperry
- Disclosed: 2026-08-28
- CVE: CVE-2026-55066
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-5pg6-m483-7vrg
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-5pg6-m483-7vrg
- Tags: area:web domain:api domain:web impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:go technique:incomplete-fix technique:source-review vuln:access-control vuln:idor

**Target:** Kanban bucket task-move REST endpoint (POST /api/v1/projects/{project}/views/{view}/buckets/{bucket}/tasks)
**Root cause:** TaskBucket.CanUpdate only checks permissions on the project/view/bucket from the URL; the body-bound TaskID is never permission-checked, and updateTaskBucket loads the task via Task.ReadOne which performs no authorization.
**Steps:**
1. Authenticate as any normal user and create own kanban project/view/bucket.
2. Send `POST /api/v1/projects/{ATTACKER_PROJECT}/views/{ATTACKER_VIEW}/buckets/{ATTACKER_BUCKET}/tasks` with body `{"task_id": {VICTIM_TASK_ID}}` and attacker JWT.
3. Read the 200 response `"task"` field containing the victim task in full.
4. Iterate sequential task_id to enumerate all tasks on the instance.
5. If the attacker bucket is the view's done bucket, the same request sets victim task `done = true` and `done_at`.
**How found:** Reporter noticed the same missing-authorization-on-task-child class already fixed for task relations (CVE-2026-33676), attachments (CVE-2026-33678), comments (CVE-2026-33313) and CalDAV read (CVE-2026-35598), and identified TaskBucket as the un-remediated sibling by reading the struct tags and CanUpdate/updateTaskBucket code paths.
**Exploitation:** Any authenticated user creates a free kanban project/view/bucket, then posts victim task IDs (global sequential integers) to their own bucket endpoint. The server loads the victim task without authorization and returns it in the response; using a done bucket also writes done/done_at/due_date/start_date/end_date to the victim row, inserts a task_buckets row, propagates done-state to other kanban views in the victim's project, and triggers updateDone rescheduling for repeating tasks.
**Escalation:** Sequential global task IDs allow enumeration of every task across all users, projects and organizations; write side effects propagate across the victim's kanban views and repeating-task rescheduling.
**Insight:** When a project has a history of IDOR fixes on task-child models, enumerate every remaining model with a body-bound task_id and check whether its CanUpdate resolves permissions only from URL params; struct tags (`param:` vs plain `json:`) reveal which fields are trusted vs attacker-controlled.

## Proof of concept (copied exactly from the original report)

**Cross-tenant kanban move-task IDOR request**

```
POST /api/v1/projects/{ATTACKER_PROJECT}/views/{ATTACKER_VIEW}/buckets/{ATTACKER_BUCKET}/tasks HTTP/1.1
    Host: TARGET
    Authorization: Bearer {ATTACKER_JWT}
    Content-Type: application/json

    {"task_id": {VICTIM_TASK_ID}}
```

