Vikunja: Task relation deletion does not check read access to the other task, allowing cross-project relation removalGHSA-w2ch-4xgr-22ww
Fix: go-vikunja/vikunja#3688GHSA-w2ch-4xgr-22ww is a security vulnerability in code.vikunja.io/api. A fix is available for code.vikunja.io/api — see the affected versions and patch details below.
Real-World Exposure
code.vikunja.io/apiReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
Description
Summary
Deleting a task relation only requires write access on the base task. Unlike relation creation, it does not verify that the caller can read the other task. A user with write access to one project can therefore delete relations whose other end lives in a project they have no access to. Since the delete removes both the forward and inverse rows, the relation also disappears for the other project's members.
Details
TaskRelation.CanCreate (pkg/models/task_relation_permissions.go:32-52) requires write access on TaskID and read access on OtherTaskID.
TaskRelation.CanDelete (pkg/models/task_relation_permissions.go:25-29) only checks Task{ID: rel.TaskID}.CanUpdate(s, a); OtherTaskID is never authorized.
TaskRelation.Delete (pkg/models/task_relation.go:314-354) then deletes both the (task_id, other_task_id, kind) row and its inverse, so the relation is removed from the far task as well.
Affects DELETE /api/v1/tasks/{id}/relations/{kind}/{otherTaskId} and the equivalent v2 endpoint.
Impact
Low, integrity only. An authenticated user holding write permission on a shared project can remove task relations that link into projects they cannot read. No data is disclosed and no privilege is gained; the attacker cannot recreate the relation. The far project's owner sees the relation vanish without indication of who removed it.
Preconditions: the attacker has write access to a project containing a task that is already related to a task in a project they cannot access.
Proof of Concept
- As
owner, create projectP_nearwith tasknearand projectP_farwith taskfar. - Share
P_nearwithattackerat write permission (permission: 1). Do not shareP_far. - As
owner:PUT /api/v1/tasks/{near}/relationswith{"other_task_id": far, "relation_kind": "related"}-> 200. - As
attacker:GET /api/v1/tasks/{far}-> 403 (confirms no access). - As
attacker:PUT /api/v1/tasks/{near}/relationswith the same body -> 403 (create path is enforced). - As
attacker:DELETE /api/v1/tasks/{near}/relations/related/{far}-> 200"Successfully deleted." - As
owner:GET /api/v1/tasks/{far}->related_tasksis now empty.
Reproduced against vikunja/vikunja:2.5.0.
Recommended Fix
Make CanDelete mirror CanCreate: after checking CanUpdate on the base task, also require CanRead on OtherTaskID.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | code.vikunja.io/api | ≥ 0.9&&< 2.6.0 | 2.6.0go get code.vikunja.io/api@v2.6.0 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for code.vikunja.io/api, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update code.vikunja.io/api to 2.6.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-w2ch-4xgr-22ww is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
Frequently Asked Questions
Is GHSA-w2ch-4xgr-22ww in your dependencies?
Find it across Go, including transitive dependencies.