GHSA-9rg3-v78m-26q8: Go/code.vikunja.io/api vulnerability

Published Oct 9, 2026
·
Updated

Summary

API token permission checks only match the HTTP method and route path. The expand query parameter on task read endpoints embeds data from other permission groups (task comments, reactions, time entry counts) without checking whether the token holds those scopes. A token scoped only to tasks read permissions can therefore read task comments and reactions it was explicitly not granted.

Details

models.CanDoAPIRoute (pkg/models/apiroutes.go, ~line 440) authorises API tokens purely by comparing method and c.Path() against the routes stored for each granted permission group. The query string is never consulted.

The task read endpoints accept expand:

- GET /api/v1/tasks/:task, GET /api/v1/tasks (pkg/models/tasks.go ReadOne/ReadAll, pkg/models/taskcollection.go) - GET /api/v2/tasks/:id, GET /api/v2/tasks, GET /api/v2/projects/:project/tasks (pkg/routes/api/v2/tasks.go, taskcollection.go)

Accepted values include comments, commentcount, reactions, timeentriescount. addMoreInfoToTasks (pkg/models/tasks.go, ~line 683) loads and embeds that data. At that layer only a web.Auth (the plain owner user, resolved by auth.GetAuthFromClaims) is available, so the token's scopes cannot be enforced there either.

Result: the taskscomments, reactions, and timeentries permission groups are advisory for any data reachable through a task expansion.

Impact

A holder of an API token scoped to tasks: [readone] or tasks: [readall] (or projectsviewstasks: [readall]) can read the full bodies of task comments, all reactions, and time entry counts on every task the token owner can access, despite GET /api/v1|v2/tasks/:task/comments and the reactions endpoints correctly returning 401 for the same token.

The leak is limited to data the token owner can already see, and is read-only. The realistic victim is a user who grants a narrowly scoped token to a third-party integration expecting comments to stay private.

Proof of Concept

Against the test fixtures (pkg/db/fixtures/apitokens.yml, token 1 has {"tasks":["readall","update"]}, plaintext tk2eef46f40ebab3304919ab2e7e39993f75f29d2e):

GET /api/v2/tasks/1/comments Authorization: Bearer tk2eef46f40ebab3304919ab2e7e39993f75f29d2e -> 401 {"code":11,"message":"missing, malformed, expired or otherwise invalid token provided"}

GET /api/v2/tasks?expand=comments&filter=id%3D1 Authorization: Bearer tk2eef46f40ebab3304919ab2e7e39993f75f29d2e -> 200, response items[0].comments contains the full comment objects

Same behaviour with expand=reactions, and on the v1 endpoints GET /api/v1/tasks?expand=comments / GET /api/v1/tasks/1?expand=comments. Any user-created token with only tasks read permissions reproduces this.

Recommended Fix

Enforce expansions in the single existing choke point, models.CanDoAPIRoute: after the method/path match succeeds, read c.QueryParams()["expand"] and require the token to hold the owning group's read permission for each value, e.g.

- comments, commentcount -> taskscomments.readall - reactions -> reactions.readall - timeentriescount -> timeentries.readall

(subtasks, buckets, isunread are task-level data and need nothing extra.) Doing this in the middleware covers both v1 and v2 without handler or model changes. Add a table-driven test alongside pkg/webtests/apitokenmethodmatchingtest.go asserting a tasks-only token gets 401 with expand=comments and 200 once taskscomments.readall is added.

Affected Software

1 affected componentFixes available
go/code.vikunja.io/api>=1.0.0<=2.5.0
2.6.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.6.0
  2. Configuration

    After the method/path match succeeds, inspect c.QueryParams()["expand"] and require tasks_comments.read_all for comments or comment_count, reactions.read_all for reactions, and time_entries.read_all for time_entries_count on task read endpoints, covering both v1 and v2.

    models.CanDoAPIRoute expand authorization = Require the token's owning-group read permission for each expanded field

Event History

Oct 9, 2026
Advisory Published
via GitHub·08:51 PM
Data Sourced
via GitHub·08:51 PM
DescriptionWeaknessAffected Software

Frequently Asked Questions

1

Which API tokens are exposed to this permission bypass?

Tokens with read access to tasks are exposed when they can call the affected task read endpoints. Such a token can request expanded task data even if it was not granted the permission groups for comments, reactions, or time-entry counts.

2

What requests should be reviewed for possible unauthorized data access?

Review GET requests to the v1 and v2 task endpoints, including project task collection requests, that use the expand query parameter. Relevant expansion values are comments, comment_count, reactions, and time_entries_count.

3

How can an affected deployment be tested?

Use a token limited to task read permissions and request an affected task endpoint with an expand value for data outside that token's granted scopes, such as comments or reactions. If the response includes that embedded data, the token permission boundary is not being enforced for the expansion.

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