CVE-2026-91984
Vikunja before 2.6.0 Broken Object-Level Authorization via task-position
Vikunja before 2.6.0 fails to validate that user-supplied project_view_id in task-position requests belongs to the task's project. Authenticated attackers can insert task position rows into arbitrary other tenant project views via POST or PUT task-position endpoints.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
5.3
Vikunja before 2.6.0 Broken Object-Level Authorization via task-position
Vikunja before 2.6.0 fails to validate that user-supplied project_view_id in task-position requests belongs to the task's project. Authenticated attackers can insert task position rows into arbitrary other tenant project views via POST or PUT task-position endpoints.
- Record
CVE-2026-91984- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-639
- Updated
- 2026-11-02
EUVDEUVD-2026-78655
5.3
Vikunja before 2.6.0 fails to validate that user-supplied project_view_id in task-position requests belongs to the task's project. Authenticated attackers can insert task position rows into arbitrary other tenant project views via POST or PUT task-position endpoints.
- Record
EUVD-2026-78655- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-ccgq-fx7h-2v4c
5.3
Duplicate Advisory: Vikunja: Cross-tenant task-position rows can be injected into arbitrary project views via the unvalidated project_view_id in the task position endpoint (v1 and v2)
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-w39f-h553-h2mx. This link is maintained to preserve external references.
Original Description
Vikunja before 2.6.0 fails to validate that user-supplied project_view_id in task-position requests belongs to the task's project. Authenticated attackers can insert task position rows into arbitrary other tenant project views via POST or PUT task-position endpoints.
- Record
GHSA-ccgq-fx7h-2v4c- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-639
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-w39f-h553-h2mx
5.3
Vikunja: Cross-tenant task-position rows can be injected into arbitrary project views via the unvalidated project_view_id in the task position endpoint (v1 and v2)
Summary
The task-position endpoint authorizes only the task side of the write: TaskPosition.CanUpdate delegates to Task.CanUpdate (write access to the task's own project) and the request body's project_view_id is never validated to belong to the task's project, nor is any access to that view required. Any authenticated user with a single writable task of their own can persist (task_id, project_view_id, position) rows into any other tenant's project view (view IDs are small sequential integers and enumerable). Low position values (below MinPositionSpacing, 0.01) enter the recalculation branch, but RecalculateTaskPositions aborts on its own project read-access check before recalculating and the whole transaction rolls back, so no cross-tenant recalculation actually executes — only the plain row insert (position ≥ 0.01) persists. This is the same root-cause pattern as GHSA-569v-q83c-3j3g (kanban bucket relocation via project_view_id mass-assignment, fixed with a dual-side check for buckets in 2.4.0) surviving in the sibling position endpoint. Verified on Vikunja 2.5.0.
Details
Affected endpoints (both verified):
- v1:
POST /api/v1/tasks/{id}/position - v2:
PUT /api/v2/tasks/{id}/position
Root cause in pkg/models/task_position.go:
CanUpdate(lines 61-64) checks onlyTask.CanUpdate— i.e. the caller's write access to the task's own project.updateTaskPosition(lines 174-232) upserts(task_id, project_view_id, position)directly; there is no check that the view belongs to the task's project and no access check on the view's project.ProjectViewIDis body-bindable (json:"project_view_id", no readOnly/param restriction, line 42).
Contrast with the fixed sibling: the bucket-move endpoint (POST /projects/{project}/views/{view}/buckets/{bucket}/tasks, pkg/models/kanban_task_bucket.go:53-68) validates both the bucket and the task after the GHSA-5pg6/GHSA-569v family fixes — the position endpoint was never given the equivalent view-side check.
Observed behavior (two independent verification runs): an attacker with zero access to the victim project receives 200 {"task_id": ..., "project_view_id": <victim view>, "position": ...} on both v1 and v2, and the row is persisted (verified directly in the task_positions table: rows for the victim view went 0 to 1). Positioning the victim's own task is correctly refused with 403, isolating the missing view-side check. The victim's listings do not surface the foreign task (no confidentiality impact demonstrated), and a low-position (< 0.01) injection enters the recalculation branch but RecalculateTaskPositions returns 403 (getRelevantProjectsFromCollection → CanRead on the victim project) before any recalculation runs, rolling the whole request back — so the low-position variant persists nothing and never rewrites the victim's ordering.
PoC
Steps use the owner of the victim project (token $OWNER) and an attacker with only their own project (token $ATTACKER). All requests were executed against a local Vikunja 2.5.0 instance.
- Owner creates a private project (default views included) and lists its views to obtain a victim view ID:
curl -X PUT "$BASE/api/v1/projects" -H "Authorization: Bearer $OWNER" \
-H 'Content-Type: application/json' -d '{"title":"victim"}'
# -> {"id":200,...}
curl "$BASE/api/v1/projects/200/views" -H "Authorization: Bearer $OWNER"
# -> [{"id":771,"view_kind":"kanban",...}, ...] # remember VIEW=771
- Attacker creates their own project and a task in it:
curl -X PUT "$BASE/api/v1/projects" -H "Authorization: Bearer $ATTACKER" \
-H 'Content-Type: application/json' -d '{"title":"attacker"}'
# -> {"id":201,...}
curl -X PUT "$BASE/api/v1/projects/201/tasks" -H "Authorization: Bearer $ATTACKER" \
-H 'Content-Type: application/json' -d '{"title":"evil"}'
# -> {"id":105,...}
- Baseline — attacker positions their task in their own view (succeeds, expected):
curl -X…
- Record
GHSA-w39f-h553-h2mx- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-639, CWE-863
- Updated
- 2026-11-02
NVD
5.3
Vikunja before 2.6.0 fails to validate that user-supplied project_view_id in task-position requests belongs to the task's project. Authenticated attackers can insert task position rows into arbitrary other tenant project views via POST or PUT task-position endpoints.
- Record
CVE-2026-91984- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-639
- Updated
- 2026-11-02
OSV
5.3
Vikunja before 2.6.0 Broken Object-Level Authorization via task-position
Vikunja before 2.6.0 fails to validate that user-supplied project_view_id in task-position requests belongs to the task's project. Authenticated attackers can insert task position rows into arbitrary other tenant project views via POST or PUT task-position endpoints.
- Record
CVE-2026-91984- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-639
- Updated
- 2026-11-02
OSVGHSA-ccgq-fx7h-2v4c
5.3
Duplicate Advisory: Vikunja: Cross-tenant task-position rows can be injected into arbitrary project views via the unvalidated project_view_id in the task position endpoint (v1 and v2)
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-w39f-h553-h2mx. This link is maintained to preserve external references.
Original Description
Vikunja before 2.6.0 fails to validate that user-supplied project_view_id in task-position requests belongs to the task's project. Authenticated attackers can insert task position rows into arbitrary other tenant project views via POST or PUT task-position endpoints.
- Record
GHSA-ccgq-fx7h-2v4c- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 4.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N - Weakness
- CWE-639
- Updated
- 2026-11-02
OSVGHSA-w39f-h553-h2mx
5.3
Vikunja: Cross-tenant task-position rows can be injected into arbitrary project views via the unvalidated project_view_id in the task position endpoint (v1 and v2)
Summary
The task-position endpoint authorizes only the task side of the write: TaskPosition.CanUpdate delegates to Task.CanUpdate (write access to the task's own project) and the request body's project_view_id is never validated to belong to the task's project, nor is any access to that view required. Any authenticated user with a single writable task of their own can persist (task_id, project_view_id, position) rows into any other tenant's project view (view IDs are small sequential integers and enumerable). Low position values (below MinPositionSpacing, 0.01) enter the recalculation branch, but RecalculateTaskPositions aborts on its own project read-access check before recalculating and the whole transaction rolls back, so no cross-tenant recalculation actually executes — only the plain row insert (position ≥ 0.01) persists. This is the same root-cause pattern as GHSA-569v-q83c-3j3g (kanban bucket relocation via project_view_id mass-assignment, fixed with a dual-side check for buckets in 2.4.0) surviving in the sibling position endpoint. Verified on Vikunja 2.5.0.
Details
Affected endpoints (both verified):
- v1:
POST /api/v1/tasks/{id}/position - v2:
PUT /api/v2/tasks/{id}/position
Root cause in pkg/models/task_position.go:
CanUpdate(lines 61-64) checks onlyTask.CanUpdate— i.e. the caller's write access to the task's own project.updateTaskPosition(lines 174-232) upserts(task_id, project_view_id, position)directly; there is no check that the view belongs to the task's project and no access check on the view's project.ProjectViewIDis body-bindable (json:"project_view_id", no readOnly/param restriction, line 42).
Contrast with the fixed sibling: the bucket-move endpoint (POST /projects/{project}/views/{view}/buckets/{bucket}/tasks, pkg/models/kanban_task_bucket.go:53-68) validates both the bucket and the task after the GHSA-5pg6/GHSA-569v family fixes — the position endpoint was never given the equivalent view-side check.
Observed behavior (two independent verification runs): an attacker with zero access to the victim project receives 200 {"task_id": ..., "project_view_id": <victim view>, "position": ...} on both v1 and v2, and the row is persisted (verified directly in the task_positions table: rows for the victim view went 0 to 1). Positioning the victim's own task is correctly refused with 403, isolating the missing view-side check. The victim's listings do not surface the foreign task (no confidentiality impact demonstrated), and a low-position (< 0.01) injection enters the recalculation branch but RecalculateTaskPositions returns 403 (getRelevantProjectsFromCollection → CanRead on the victim project) before any recalculation runs, rolling the whole request back — so the low-position variant persists nothing and never rewrites the victim's ordering.
PoC
Steps use the owner of the victim project (token $OWNER) and an attacker with only their own project (token $ATTACKER). All requests were executed against a local Vikunja 2.5.0 instance.
- Owner creates a private project (default views included) and lists its views to obtain a victim view ID:
curl -X PUT "$BASE/api/v1/projects" -H "Authorization: Bearer $OWNER" \
-H 'Content-Type: application/json' -d '{"title":"victim"}'
# -> {"id":200,...}
curl "$BASE/api/v1/projects/200/views" -H "Authorization: Bearer $OWNER"
# -> [{"id":771,"view_kind":"kanban",...}, ...] # remember VIEW=771
- Attacker creates their own project and a task in it:
curl -X PUT "$BASE/api/v1/projects" -H "Authorization: Bearer $ATTACKER" \
-H 'Content-Type: application/json' -d '{"title":"attacker"}'
# -> {"id":201,...}
curl -X PUT "$BASE/api/v1/projects/201/tasks" -H "Authorization: Bearer $ATTACKER" \
-H 'Content-Type: application/json' -d '{"title":"evil"}'
# -> {"id":105,...}
- Baseline — attacker positions their task in their own view (succeeds, expected):
curl -X…
- Record
GHSA-w39f-h553-h2mx- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-639, CWE-863
- Updated
- 2026-11-02
OSVGO-2026-6755 No score
Vikunja: Cross-tenant task-position rows can be injected into arbitrary project views via the unvalidated project_view_id in the task position endpoint (v1 and v2) in code.vikunja.io/api
Vikunja: Cross-tenant task-position rows can be injected into arbitrary project views via the unvalidated project_view_id in the task position endpoint (v1 and v2) in code.vikunja.io/api.
NOTE: The source advisory for this report contains additional versions that could not be automatically mapped to standard Go module versions.
(If this is causing false-positive reports from vulnerability scanners, please suggest an edit to the report.)
The additional affected modules and versions are: code.vikunja.io/api before v2.6.0.
- Record
GO-2026-6755- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
code.vikunja.io/apiGo |
through 2.5.0 | 2.6.0 |
code.vikunja.io/apiGo |
before 2.6.0 | 2.6.0 |
code.vikunja.io/apiGo |
All versions | None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-91984 is a medium vulnerability (CVSS 5.3) in Go-vikunja Vikunja, published September 15, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.26%, the 18th percentile. Fixed in code.vikunja.io/api 2.6.0.
References
github.com/go-vikunja/vikunja/security/advisories/GHSA-w39f-h553-h2mx
vulncheck.com/advisories/vikunja-before-2.6.0-broken-object-level-authorization-via-task-position
nvd.nist.gov/vuln/detail/CVE-2026-91984
github.com/advisories/GHSA-ccgq-fx7h-2v4c
github.com/go-vikunja/vikunja/pull/3688
github.com/go-vikunja/vikunja/commit/077dc4de79ce6f1ab59215a2c7bf9b30423685f2
Left out here: the rest of 2 source descriptions. The JSON record has them.