high · 7.1CVE-2026-55066Aug 28, 2026

CVE-2026-55066: Vikunja Kanban Move-Task IDOR (Cross-Tenant Task Read and Write)

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Any authenticated Vikunja user can read the full contents of every other user's tasks and mark them done by supplying an arbitrary task ID in the kanban move-task request body, bypassing all…

Packagecode.vikunja.io/api
Ecosystemgo
Affected<= 2.3.0
Fixed in2.4.0
CVE-2026-55066: Vikunja Kanban Move-Task IDOR (Cross-Tenant Task Read and Write)

The problem

The endpoint POST /api/v1/projects/{project}/views/{view}/buckets/{bucket}/tasks moves a task into a kanban bucket. The task is identified by task_id in the JSON body. TaskBucket.CanUpdate only checks whether the caller can write to the bucket named in the URL.

It never checks whether the caller can access the task named in the body.

Because task IDs are a global sequential integer, any authenticated user can iterate task_id values against a bucket in their own project. The server loads and returns the victim task in full (title, description, assignees, labels, attachment metadata). If the target bucket is the done bucket, the server also flips task.Done = true and rewrites done_at, due_date, start_date, and end_date on the victim's row.

Proof of concept

A working proof-of-concept for CVE-2026-55066 in code.vikunja.io/api, with the exact payload below.

http
POST /api/v1/projects/{ATTACKER_PROJECT}/views/{ATTACKER_VIEW}/buckets/{ATTACKER_BUCKET}/tasks HTTP/1.1
Host: vikunja.target.example
Authorization: Bearer {ATTACKER_JWT}
Content-Type: application/json

{"task_id": 1337}

# 200 response returns victim task in full under the "task" key.
# To also mark the victim task done, set ATTACKER_BUCKET to
# the done bucket of ATTACKER_VIEW before sending the request.

The Go web handler calls ctx.Bind, which populates BucketID, ProjectViewID, and ProjectID from trusted URL path params but populates TaskID entirely from the attacker-controlled request body. TaskBucket.CanUpdate then calls canDoBucket, which resolves only the URL-supplied bucket and terminates in a Project.CanUpdate check on the attacker's own project.

Because the attacker owns that project, the check passes. The body-supplied TaskID is never referenced during authorization.

Inside updateTaskBucket, Task{ID: b.TaskID}.ReadOne(s, a) loads any task by ID without any permission check, because ReadOne is an internal call that bypasses the separate CanRead gate. The patch (commit 36cdc2ce, PR #3239) adds a task.CanUpdate(s, a) call inside TaskBucket.CanUpdate immediately after the bucket check, mirroring the remediation already applied to task relations (CVE-2026-33676), attachments (CVE-2026-33678), and comments (CVE-2026-33313).

CWE-639 Authorization Bypass Through User-Controlled Key.

The fix

Upgrade to Vikunja 2.4.0. The fix adds an explicit task permission check inside TaskBucket.CanUpdate (pkg/models/kanban_task_bucket.go) before any task data is loaded. The model-level fix closes both the v1 and v2 API paths simultaneously because both routes share the same TaskBucket model.

Reported by hoangperry.

References: [1][2][3][4][5]

Related research