Every CVE Search/ Get API key

CVE-2026-91984

Vikunja before 2.6.0 Broken Object-Level Authorization via task-position

5.3 MediumFix available

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

0.26%Up 0.1 points since Sep 16, 2026
90%50%10%1%

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 MediumCVSS: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 MediumCVSS: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 MediumCVSS: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 MediumCVSS: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 MediumCVSS: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 only Task.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.
  • ProjectViewID is 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.

  1. 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
  1. 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,...}
  1. Baseline — attacker positions their task in their own view (succeeds, expected):
curl -X…
Record
GHSA-w39f-h553-h2mx
CVSS 4.0
5.3 MediumCVSS: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 MediumCVSS: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 MediumCVSS: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 MediumCVSS: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 MediumCVSS: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 MediumCVSS: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 only Task.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.
  • ProjectViewID is 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.

  1. 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
  1. 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,...}
  1. Baseline — attacker positions their task in their own view (succeeds, expected):
curl -X…
Record
GHSA-w39f-h553-h2mx
CVSS 4.0
5.3 MediumCVSS: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

Affected packages
PackageAffectedFixed
code.vikunja.io/api
Go
through 2.5.0
2.6.0
code.vikunja.io/api
Go
before 2.6.0
2.6.0
code.vikunja.io/api
Go
All versions None known

History

History
RecordedChangedSources
affected
first indexedCVE 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

Show 4 moreShow less
This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open