Revoked project members keep reading assigned tasks via getMyDashboard/getMyOverdueTasks (no membership filter in getUserQuery)
Environment
Kanboard v1.2.53 (Docker image kanboard/kanboard:v1.2.53), JSON-RPC API (POST /jsonrpc.php, HTTP Basic auth).
Summary
After a user is removed from a project, the API procedures getMyDashboard and getMyOverdueTasks keep returning the full task records (title, description, due dates, private project name) of that project's tasks which are assigned to the removed user, while direct access (getTask) is correctly denied. The leak is permanent because membership removal does not clear task assignments.
Both procedures were verified empirically on v1.2.53 with two regular user accounts.
Root cause
TaskFinderModel::getUserQuery() (app/Model/TaskFinderModel.php:60-70) filters tasks only by:
owner_id = current userORid IN (subtasks assigned to current user)- task/project status and
hide_in_dashboard
There is no project-membership constraint. getOverdueTasksByUser() (TaskFinderModel.php:235-238) has the same shape (eq owner_id only).
ProjectUserRoleModel::removeUser() (app/Model/ProjectUserRoleModel.php:226-229) is a bare delete of the membership row: it neither unassigns tasks nor fires an event, so the owner_id linkage survives revocation.
Contrast: the web dashboard overview (DashboardPagination::getOverview) iterates only over getActiveProjectsByUser($userId), i.e. it applies membership scoping; resource-level API procedures (getTask etc.) re-check membership at call time and correctly return 403 after revocation.
Steps to reproduce
Two regular users, owner (creator of a private project) and victim (no other relation to it):
- As
owner:createMyPrivateProjectwith name "secret-project" -> project_id 1 (owner becomes project-manager). - As
owner:addProjectUser(project_id=1, user_id=<victim_id>, role="project-member"). - As
owner:createTask(project_id=1, title="secret task", owner_id=<victim_id>, date_due=<yesterday as Y-m-d>)-> task_id 1. - As
victim:getMyDashboard-> the task is listed (legitimate, victim is still a member). Same forgetMyOverdueTasks. - As
owner:removeProjectUser(project_id=1, user_id=<victim_id>). - As
victim:getTask(task_id=1)-> JSON-RPC error 403 "Forbidden" (membership check works). - As
victim:getMyDashboardandgetMyOverdueTasks-> the task is STILL returned with all fields, includingproject_nameof the private project.
Example request for step 7:
POST /jsonrpc.php
Authorization: Basic <victim credentials>
{"jsonrpc":"2.0","method":"getMyDashboard","id":1}Impact
Broken Access Control / information disclosure (CWE-863). Any user who was ever assigned a task (or subtask) of a project keeps permanent read access to those task records after losing membership. Task descriptions may contain confidential project content. Exploitation requires a prior assignment relationship; the leak is read-only. CVSS:3.1 AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N (6.5, Medium).
Suggested fix
Add a membership constraint to getUserQuery() (and getOverdueTasksByUser()), e.g. project_id IN (SELECT project_id FROM project_has_users WHERE user_id = ...) via projectPermissionModel->getProjectIds(), mirroring the scoping already used by the web dashboard overview.
Source: kanboard/kanboard