GitHub Security Advisory: GHSA-8cmm-j6c4-rr8v
Package: code.vikunja.io/api (GO)
Severity: MODERATE (CVSS 6.5)
CWE: CWE-863: Incorrect Authorization
Vulnerable versions: <= 2.2.0
Patched in: 2.2.1

Summary: Vikunja has Cross-Project Information Disclosure via Task Relations — Missing Authorization Check on Related Task Read

Details:
## 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**

```ba