Vikunja before 2.6.0 Broken Object-Level Authorization via task-position
Collected exploitation and prediction evidence
Risk source coverageCISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.
No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.
cve-program · CVE-2026-91984
PublishedVikunja 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.
- Source published
- 15 Sep 2026 15:18:27 UTC
- Source modified
- 17 Sep 2026 19:27:58 UTC
- Observed here
- 23 Oct 2026 15:48:27 UTC
- Source terms
- CVE Program Terms of Use
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 4.0 · 5.3 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 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
euvd · EUVD-2026-78655
PublishedVikunja 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.
EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.
Original publisher date fields
datePublished: Sep 15, 2026, 3:18:27 PM
dateUpdated: Sep 17, 2026, 7:27:58 PM
- Source published
- Sep 15, 2026, 3:18:27 PM (timezone not stated)
- Source modified
- Sep 17, 2026, 7:27:58 PM (timezone not stated)
- Observed here
- 7 Nov 2026 15:25:12 UTC
- Source terms
- ENISA EUVD · source terms and attribution apply
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.
EUVD CVSS 4.0 · 5.3 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
nvd · CVE-2026-91984
PublishedVikunja 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.
NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.
Source analysis status: Deferred
Weakness classifications: CWE-639
Original publisher date fields
published: 2026-09-15T16:17:55.903
lastModified: 2026-09-17T20:18:54.770
- Source published
- 15 Sep 2026 16:17:55 UTC
- Source modified
- 17 Sep 2026 20:18:54 UTC
- Observed here
- 25 Oct 2026 16:19:22 UTC
- Source terms
- NIST NVD usage terms; underlying source attribution retained
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 4.0 · [email protected] · Secondary · 5.3 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 · [email protected] · Secondary · 4.3 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
osv · GHSA-ccgq-fx7h-2v4c
Withdrawn### 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.
- Source published
- 15 Sep 2026 18:32:30 UTC
- Source modified
- 9 Oct 2026 21:00:06 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- GitHub Advisory Database · CC-BY-4.0
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS_V3 · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
CVSS_V4 · 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
Publisher rating · MODERATE
osv · GHSA-w39f-h553-h2mx
Published## 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 ``` 2. 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,...} ``` 3. Baseline — attacker positions their task in their own view (succeeds, expected): ``` curl -X POST "$BASE/api/v1/tasks/105/position" -H "Authorization: Bearer $ATTACKER" \ -H 'Content-Type: application/json' -d '{"project_view_id": <OWN_VIEW>, "position": 100}' # -> 200 ``` 4. Mutation — attacker writes their task's position into the victim's view (no access to it whatsoever): ``` curl -X POST "$BASE/api/v1/tasks/105/position" -H "Authorization: Bearer $ATTACKER" \ -H 'Content-Type: application/json' -d '{"project_view_id": 771, "position": 100}' # -> 200 {"task_id":105,"project_view_id":771,"position":100} curl -X PUT "$BASE/api/v2/tasks/105/position" -H "Authorization: Bearer $ATTACKER" \ -H 'Content-Type: application/json' -d '{"project_view_id": 771, "position": 101}' # -> 200 (v2 twin behaves identically) ``` 5. Control — attacker positioning the victim's own task is correctly refused (the task-level gate works; only the view side is missing): ``` curl -X POST "$BASE/api/v1/tasks/<VICTIM_TASK>/position" -H "Authorization: Bearer $ATTACKER" \ -H 'Content-Type: application/json' -d '{"project_view_id": 771, "position": 1}' # -> 403 Forbidden ``` 6. Persistence proof — the cross-tenant row exists in the database: ``` docker exec <db> psql -U vikunja -d vikunja -t -A -c \ "SELECT task_id, position FROM task_positions WHERE project_view_id=771 AND task_id=105;" # -> 105|100 (row for the attacker's task inside the victim's view namespace) ``` Observed result: steps 4 and 6 succeed — a `task_positions` row `(attacker_task, victim_view)` is created and persisted; step 5 is refused with 403. Note: `"position": 1` is above `MinPositionSpacing` (0.01), so it never enters the recalculation branch — it is a plain upsert. A position < 0.01 does enter the branch but is refused with 403 and rolled back (see Impact). ## Impact **Impact summary:** a genuine broken-object-level-authorization defect (CWE-639) with no demonstrated confidentiality, integrity, or availability impact. The injected rows are invisible to the victim (view listings filter by project), order-preserving (any position collision is respaced between the same neighbours), and self-healing (any legitimate recalculation of the view deletes and rebuilds all its position rows). No read access, no privilege gain, no data destruction, and no usable DoS. Rated **low** — the fix is defense-in-depth, and its main value is preventing this wrong-object authorization from becoming a real cross-tenant leak should any future read path join `task_positions` without re-checking task-project access. Broken object-level authorization on the write side (CWE-639, CWE-863: the authorization decision uses one resource dimension — the task — while the write targets another — the view). Any authenticated user holding a single writable task can persist rows into any other tenant's view state instance-wide (view IDs sequential and enumerable). The recalculation/lock path is not usable cross-tenant: a low position (< 0.01) enters the branch but `RecalculateTaskPositions` fails its own project read-access check and rolls back before recalculating (on MySQL/PostgreSQL a `FOR UPDATE` lock on the view row is taken one statement earlier in the same transaction, but released immediately on that rollback; SQLite takes no lock), so there is no meaningful availability angle. No confidentiality breach was demonstrated (victim listings filter by project), and the pollution is recoverable. Suggested fix: in `CanUpdate` or before the upsert, load the view by `tp.ProjectViewID` and require `view.ProjectID == task.ProjectID` (and/or caller access to the view's project), mirroring the bucket-side fix for GHSA-569v.
- Source published
- 9 Oct 2026 20:51:41 UTC
- Source modified
- 19 Oct 2026 21:40:58 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- GitHub Advisory Database · CC-BY-4.0
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS_V4 · 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
Publisher rating · MODERATE
osv · GO-2026-6755
PublishedVikunja: 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.
- Source published
- 19 Oct 2026 21:08:26 UTC
- Source modified
- 19 Oct 2026 21:31:44 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- OSV · upstream source license applies
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · OSV · upstream source license applies. Source text is retained; field grouping and change labels are provided by ChangeIntel.