CVE-2026-33676: Code.vikunja.io/api Vikunja Has Cross-Project Information Disclosure Vulnerability

MODERATE
Advisory Identifier: GHSA-8cmm-j6c4-rr8v
CVSS 6.5
CWE-863
Published
Advisory Summary

CVE-2026-33676 is a MODERATE (CVSS 6.5) vikunja has cross-project information disclosure vulnerability (CWE-863: Incorrect Authorization) affecting Code.vikunja.io/api libraries in the Go ecosystem. The issue involves vikunja has cross-project information disclosure via task relations — missing authorization check on related task read. Remediation is available by upgrading affected packages to version 2.2.1.

CVE-2026-33676 at a Glance

CVE Identifier CVE-2026-33676
GHSA Identifier GHSA-8cmm-j6c4-rr8v
Severity MODERATE
CVSS v3.1 Base Score 6.5
CWE Classification CWE-863: Incorrect Authorization
Affected Product Code.vikunja.io/api
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 6.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity None (I:N)
Availability None (A:N)

How to Fix CVE-2026-33676

To remediate this vulnerability, upgrade all affected Code.vikunja.io/api dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

code.vikunja.io/api fixed: 2.2.1
Upgrade code.vikunja.io/api to 2.2.1
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for code.vikunja.io/api

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-33676, inspect your project dependencies for Code.vikunja.io/api:

inspect dependencies for code.vikunja.io/api

Analyze the terminal output to locate any version of Code.vikunja.io/api in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.2.1), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-863: Incorrect Authorization, where the product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. In Code.vikunja.io/api, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary When the Vikunja API returns tasks, it populates the `related_tasks` field with full task objects for all related tasks without checking whether the requesting user has read permission on those tasks' projects. An authenticated user who can read a task that has cross-project relations will receive full details (title, description, due dates, priority, percent completion, project ID, etc.) of tasks in projects they have no access to. ## Details The vulnerability is in `addRelatedTasksToTasks()` at `pkg/models/tasks.go:496-548`. This function is called by `addMoreInfoToTasks()` (line 773) during every task read operation — both project task listings (`GET /api/v1/projects/{id}/views/{id}/tasks`) and single task reads (`GET /api/v1/tasks/{id}`). The function fetches all related tasks directly from the database without any permission filtering: ```go // pkg/models/tasks.go:496-548 func addRelatedTasksToTasks(s *xorm.Session, taskIDs []int64, taskMap map[int64]*Task, a web.Auth) (err error) { relatedTasks := []*TaskRelation{} err = s.In("task_id", taskIDs).Find(&relatedTasks) // ... fullRelatedTasks := make(map[int64]*Task) err = s.In("id", relatedTaskIDs).Find(&fullRelatedTasks) // Line 514: NO permission check // ... for _, rt := range relatedTasks { // Directly adds to response without checking if user can read the related task taskMap[rt.TaskID].RelatedTasks[rt.RelationKind] = append( taskMap[rt.TaskID].RelatedTasks[rt.RelationKind], otherTask) } } ``` The `a web.Auth` parameter is received but only used for determining favorites (line 519), never for access control on the related tasks themselves. In contrast, `addBucketsToTasks()` (line 550+) in the same file correctly filters enrichment data by calling `getAllRawProjects(s, a, ...)` to scope results to projects the requesting user can access. While task relation **creation** properly enforces authorization (`task_relation_permissions.go:32-52` checks write access on the base task and read access on the other task), the relation **display** path does not re-check permissions for the current reader. This means a privileged user can create a relation that then leaks data to all other users who can read the base task. ## PoC **Setup:** Two users (User A, User B), two projects (Project-Shared, Project-Private). - User A has access to both projects. - User B has access only to Project-Shared. - Task 1 exists in Project-Shared, Task 2 exists in Project-Private. **Step 1: User A creates a relation between the two tasks** ```bash # As User A (who has access to both projects) curl -X PUT "http://localhost:3456/api/v1/tasks/TASK1_ID/relations" \ -H "Authorization: Bearer USER_A_TOKEN" \ -H "Content-Type: application/json" \ -d '{"other_task_id": TASK2_ID, "relation_kind": "related"}' ``` Expected: 201 Created (User A has write on Task 1, read on Task 2). **Step 2: User B reads tasks from the shared project** ```bash # As User B (who has NO access to Project-Private) curl "http://localhost:3456/api/v1/projects/PROJECT_SHARED_ID/views/VIEW_ID/tasks" \ -H "Authorization: Bearer USER_B_TOKEN" ``` Expected: Task 1 should be returned, but related_tasks should NOT include Task 2. **Actual result:** The response includes Task 1 with the `related_tasks` field containing the full Task 2 object, including its `title`, `description`, `due_date`, `priority`, `percent_done`, `project_id`, and other metadata — despite User B having no access to Project-Private. ## Impact - **Information disclosure**: Any authenticated user can read the full metadata of tasks in projects they do not have access to, as long as a relation exists from a task they can read. - **Leaked fields include**: title, description, due dates, start dates, priority, percent completion, project ID, hex color, task index, done status, repeat configuration, cover image attachment ID, and creation/update timestamps. - **Project structure disclosure**: The `project_id` field reveals the existence and IDs of private projects. - **No user interaction required**: Once a privileged user creates a cross-project relation (which is intentionally allowed), the data leak is automatic for all readers of the base task. - **Blast radius**: Affects all Vikunja instances with cross-project task relations. In multi-tenant or team environments where projects have different access scopes, this undermines project-level access control. ## Recommended Fix Filter related tasks by the requesting user's read permissions before adding them to the response. In `addRelatedTasksToTasks()`, after fetching full task objects, check that the user can read each related task's project: ```go func addRelatedTasksToTasks(s *xorm.Session, taskIDs []int64, taskMap map[int64]*Task, a web.Auth) (err error) { relatedTasks := []*TaskRelation{} err = s.In("task_id", taskIDs).Find(&relatedTasks) if err != nil { return } var relatedTaskIDs []int64 for _, rt := range relatedTasks { relatedTaskIDs = append(relatedTaskIDs, rt.OtherTaskID) } if len(relatedTaskIDs) == 0 { return } fullRelatedTasks := make(map[int64]*Task) err = s.In("id", relatedTaskIDs).Find(&fullRelatedTasks) if err != nil { return } // Filter related tasks by user's read permission allowedProjectIDs := make(map[int64]bool) checkedProjectIDs := make(map[int64]bool) for _, t := range fullRelatedTasks { if checkedProjectIDs[t.ProjectID] { continue } checkedProjectIDs[t.ProjectID] = true p := &Project{ID: t.ProjectID} canRead, _, err := p.CanRead(s, a) if err != nil { log.Errorf("Could not check project read permission: %v", err) continue } if canRead { allowedProjectIDs[t.ProjectID] = true } } taskFavorites, err := getFavorites(s, relatedTaskIDs, a, FavoriteKindTask) if err != nil { return err } for _, rt := range relatedTasks { task, has := fullRelatedTasks[rt.OtherTaskID] if !has { continue } // Skip related tasks the user cannot access if !allowedProjectIDs[task.ProjectID] { continue } fullRelatedTasks[rt.OtherTaskID].IsFavorite = taskFavorites[rt.OtherTaskID] otherTask := &Task{} err = copier.Copy(otherTask, fullRelatedTasks[rt.OtherTaskID]) if err != nil { log.Errorf("Could not duplicate task object: %v", err) continue } otherTask.RelatedTasks = nil taskMap[rt.TaskID].RelatedTasks[rt.RelationKind] = append( taskMap[rt.TaskID].RelatedTasks[rt.RelationKind], otherTask) } return } ``` This checks project-level read permission once per unique project ID (cached in `allowedProjectIDs`) and skips related tasks from projects the user cannot access.

CVE-2026-33676 Affected Code.vikunja.io/api Versions

1 component(s)
Fixed Versions:
2.2.1
Product / Package Ecosystem Affected Versions Fixed Version
code.vikunja.io/api
Go
< 2.2.1
2.2.1

References

Frequently Asked Questions

What is CVE-2026-33676?
CVE-2026-33676 is a security vulnerability in Code.vikunja.io/api involving Vikunja has Cross-Project Information Disclosure via Task Relations — Missing Authorization Check on Related Task Read under CWE-863: Incorrect Authorization.
Which Code.vikunja.io/api versions are affected?
Affected versions include releases prior to 2.2.1 across affected packages: code.vikunja.io/api.
What version fixes CVE-2026-33676?
Upgrading to 2.2.1 (or the latest patched release for your respective branch) resolves the vulnerability.
Is code.vikunja.io/api affected?
Yes, code.vikunja.io/api is affected in releases prior to 2.2.1. Upgrade your dependency to 2.2.1 or later.
What is the CVSS score of CVE-2026-33676?
CVE-2026-33676 has a CVSS v3.1 base score of 6.5 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N.
What CWE is associated with CVE-2026-33676?
CVE-2026-33676 is classified under CWE-863: Incorrect Authorization (The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.).
How can I check Go dependencies for CVE-2026-33676?
Run the dependency tree command: 'inspect dependencies for code.vikunja.io/api'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-8cmm-j6c4-rr8v",
  "modified" : "2026-03-25T21:17:12Z",
  "published" : "2026-03-25T21:17:12Z",
  "aliases" : [ "CVE-2026-33676" ],
  "summary" : "Vikunja has Cross-Project Information Disclosure via Task Relations — Missing Authorization Check on Related Task Read",
  "details" : "## Summary\n\nWhen the Vikunja API returns tasks, it populates the `related_tasks` field with full task objects for all related tasks without checking whether the requesting user has read permission on those tasks' projects. An authenticated user who can read a task that has cross-project relations will receive full details (title, description, due dates, priority, percent completion, project ID, etc.) of tasks in projects they have no access to.\n\n## Details\n\nThe vulnerability is in `addRelatedTasksToTasks()` at `pkg/models/tasks.go:496-548`. This function is called by `addMoreInfoToTasks()` (line 773) during every task read operation — both project task listings (`GET /api/v1/projects/{id}/views/{id}/tasks`) and single task reads (`GET /api/v1/tasks/{id}`).\n\nThe function fetches all related tasks directly from the database without any permission filtering:\n\n```go\n// pkg/models/tasks.go:496-548\nfunc addRelatedTasksToTasks(s *xorm.Session, taskIDs []int64, taskMap map[int64]*Task, a web.Auth) (err error) {\n    relatedTasks := []*TaskRelation{}\n    err = s.In(\"task_id\", taskIDs).Find(&relatedTasks)\n    // ...\n    fullRelatedTasks := make(map[int64]*Task)\n    err = s.In(\"id\", relatedTaskIDs).Find(&fullRelatedTasks)  // Line 514: NO permission check\n    // ...\n    for _, rt := range relatedTasks {\n        // Directly adds to response without checking if user can read the related task\n        taskMap[rt.TaskID].RelatedTasks[rt.RelationKind] = append(\n            taskMap[rt.TaskID].RelatedTasks[rt.RelationKind], otherTask)\n    }\n}\n```\n\nThe `a web.Auth` parameter is received but only used for determining favorites (line 519), never for access control on the related tasks themselves.\n\nIn contrast, `addBucketsToTasks()` (line 550+) in the same file correctly filters enrichment data by calling `getAllRawProjects(s, a, ...)` to scope results to projects the requesting user can access.\n\nWhile task relation **creation** properly enforces authorization (`task_relation_permissions.go:32-52` checks write access on the base task and read access on the other task), the relation **display** path does not re-check permissions for the current reader. This means a privileged user can create a relation that then leaks data to all other users who can read the base task.\n\n## PoC\n\n**Setup:** Two users (User A, User B), two projects (Project-Shared, Project-Private).\n- User A has access to both projects.\n- User B has access only to Project-Shared.\n- Task 1 exists in Project-Shared, Task 2 exists in Project-Private.\n\n**Step 1: User A creates a relation between the two tasks**\n\n```bash\n# As User A (who has access to both projects)\ncurl -X PUT \"http://localhost:3456/api/v1/tasks/TASK1_ID/relations\" \\\n  -H \"Authorization: Bearer USER_A_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"other_task_id\": TASK2_ID, \"relation_kind\": \"related\"}'\n```\n\nExpected: 201 Created (User A has write on Task 1, read on Task 2).\n\n**Step 2: User B reads tasks from the shared project**\n\n```bash\n# As User B (who has NO access to Project-Private)\ncurl \"http://localhost:3456/api/v1/projects/PROJECT_SHARED_ID/views/VIEW_ID/tasks\" \\\n  -H \"Authorization: Bearer USER_B_TOKEN\"\n```\n\nExpected: Task 1 should be returned, but related_tasks should NOT include Task 2.\n\n**Actual result:** The response includes Task 1 with the `related_tasks` field containing the full Task 2 object, including its `title`, `description`, `due_date`, `priority`, `percent_done`, `project_id`, and other metadata — despite User B having no access to Project-Private.\n\n## Impact\n\n- **Information disclosure**: Any authenticated user can read the full metadata of tasks in projects they do not have access to, as long as a relation exists from a task they can read.\n- **Leaked fields include**: title, description, due dates, start dates, priority, percent completion, project ID, hex color, task index, done status, repeat configuration, cover image attachment ID, and creation/update timestamps.\n- **Project structure disclosure**: The `project_id` field reveals the existence and IDs of private projects.\n- **No user interaction required**: Once a privileged user creates a cross-project relation (which is intentionally allowed), the data leak is automatic for all readers of the base task.\n- **Blast radius**: Affects all Vikunja instances with cross-project task relations. In multi-tenant or team environments where projects have different access scopes, this undermines project-level access control.\n\n## Recommended Fix\n\nFilter related tasks by the requesting user's read permissions before adding them to the response. In `addRelatedTasksToTasks()`, after fetching full task objects, check that the user can read each related task's project:\n\n```go\nfunc addRelatedTasksToTasks(s *xorm.Session, taskIDs []int64, taskMap map[int64]*Task, a web.Auth) (err error) {\n    relatedTasks := []*TaskRelation{}\n    err = s.In(\"task_id\", taskIDs).Find(&relatedTasks)\n    if err != nil {\n        return\n    }\n\n    var relatedTaskIDs []int64\n    for _, rt := range relatedTasks {\n        relatedTaskIDs = append(relatedTaskIDs, rt.OtherTaskID)\n    }\n\n    if len(relatedTaskIDs) == 0 {\n        return\n    }\n\n    fullRelatedTasks := make(map[int64]*Task)\n    err = s.In(\"id\", relatedTaskIDs).Find(&fullRelatedTasks)\n    if err != nil {\n        return\n    }\n\n    // Filter related tasks by user's read permission\n    allowedProjectIDs := make(map[int64]bool)\n    checkedProjectIDs := make(map[int64]bool)\n    for _, t := range fullRelatedTasks {\n        if checkedProjectIDs[t.ProjectID] {\n            continue\n        }\n        checkedProjectIDs[t.ProjectID] = true\n        p := &Project{ID: t.ProjectID}\n        canRead, _, err := p.CanRead(s, a)\n        if err != nil {\n            log.Errorf(\"Could not check project read permission: %v\", err)\n            continue\n        }\n        if canRead {\n            allowedProjectIDs[t.ProjectID] = true\n        }\n    }\n\n    taskFavorites, err := getFavorites(s, relatedTaskIDs, a, FavoriteKindTask)\n    if err != nil {\n        return err\n    }\n\n    for _, rt := range relatedTasks {\n        task, has := fullRelatedTasks[rt.OtherTaskID]\n        if !has {\n            continue\n        }\n        // Skip related tasks the user cannot access\n        if !allowedProjectIDs[task.ProjectID] {\n            continue\n        }\n        fullRelatedTasks[rt.OtherTaskID].IsFavorite = taskFavorites[rt.OtherTaskID]\n        otherTask := &Task{}\n        err = copier.Copy(otherTask, fullRelatedTasks[rt.OtherTaskID])\n        if err != nil {\n            log.Errorf(\"Could not duplicate task object: %v\", err)\n            continue\n        }\n        otherTask.RelatedTasks = nil\n        taskMap[rt.TaskID].RelatedTasks[rt.RelationKind] = append(\n            taskMap[rt.TaskID].RelatedTasks[rt.RelationKind], otherTask)\n    }\n\n    return\n}\n```\n\nThis checks project-level read permission once per unique project ID (cached in `allowedProjectIDs`) and skips related tasks from projects the user cannot access.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "code.vikunja.io/api"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2.2.1"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 2.2.0"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/go-vikunja/vikunja/security/advisories/GHSA-8cmm-j6c4-rr8v"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-33676"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-vikunja/vikunja/pull/2449"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-vikunja/vikunja/commit/833f2aec006ac0f6643c41872e45dd79220b9174"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/go-vikunja/vikunja"
  }, {
    "type" : "WEB",
    "url" : "https://vikunja.io/changelog/vikunja-v2.2.2-was-released"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-863" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-03-25T21:17:12Z",
    "nvd_published_at" : "2026-03-24T16:16:34Z"
  }
}