fix(security): scope task + per-user REST routes to the caller's projects (WPScan #42604, #43987) - #660
Merged
arifulhoque7 merged 1 commit intoAug 19, 2026
Conversation
Closes cross-project data-access reports in WPScan #42604 and #43987
(WP Project Manager <= 4.0.6). Both let any logged-in user read or modify
data outside the projects they belong to, because these routes were gated
by Authentic (logged-in only) with no membership constraint.
#42604 - cross-project task disclosure and modification
- tasks / advanced/tasks / advanced/taskscsv run Task::get_results(),
which only constrains by project when a project_id param is present.
The intended hook (wedevs_pm_task_where) had no listener, so a member
of no project read every project's tasks, descriptions and the
creators'/assignees' emails, and exported them as CSV. Added
where_membership() to the query builder: managers/admins query across
projects as before, everyone else is bound to the projects they are a
member of (a member of none sees nothing).
- projects/{project_id}/tasks/sorting accepted task and list ids in the
body while gating only the project in the URL, so a member of one
project could reorder/relocate another project's tasks. It now requires
Access_Project and binds every list/task id in the body to that project.
#43987 - any user's activity feed, tasks and email
- users/{id}/user-activities and users/{id}/tasks used the {id} from the
URL with no check that it is the caller. New Access_User permission
allows a caller to read only their own resources; reading another
user's requires a project-management capability. Applied to those two
routes plus users/{id}/tasks/calender.
The named routes are the reported instances of one property: every route
in this namespace must constrain its query to the caller's projects and
bind request ids to the project it gated on. assigned_users already
scopes correctly and is unchanged.
Non-breaking: the My Tasks screen always requests the current user's id,
and reports/advanced views are manager-facing (manage cap bypasses the
membership clause). No line-ending or unrelated changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs weDevsOfficial/pm-pro#461 (WPScan #42604 and #43987).
Two of the three reports in #461 are cross-project data-access defects in the Free plugin: routes gated only by
Authentic(logged-in only), with no constraint tying the query/ids to the projects the caller belongs to. This PR closes both. The third report (#45258, unauthenticated account creation) is handled by #658 and is not touched here.#42604 — cross-project task disclosure & modification
tasks,advanced/tasks,advanced/taskscsvrunTask::get_results(), which only constrains by project when aproject_idparam is present. The one hook that could add a membership constraint,wedevs_pm_task_where, has no registered listener — so a subscriber belonging to no project read every project's tasks, their descriptions and the creators'/assignees' email addresses, and exported the same set as CSV.where_membership()to the query builder. Managers/admins query across projects as before; everyone else is bound to the projects they are a member of (a member of none matches nothing). Applied in the sharedwhere()chain, so every caller ofget_results()(tasks, advanced tasks, CSV, kanban) is covered.projects/{project_id}/tasks(index) already scopes byproject_idon its own query and is unaffected.projects/{project_id}/tasks/sortingacceptedlist_id/task_id/orders[].idfrom the body while checking only the project in the URL, so a member of one project could reorder and relocate another project's tasks.Access_Project, and binds every list/task id in the body to thatproject_idbefore mutating (rejects with 403 on mismatch).#43987 — any user's activity feed, tasks & email
users/{id}/user-activitiesandusers/{id}/tasksused the{id}from the URL with no check that it is the caller, leaking the target's activity feed (with actor email) and task list (with the target's user object).Access_Userpermission: a caller may read only their own resources; reading another user's requires a project-management capability. Applied touser-activities,users/{id}/tasks, andusers/{id}/tasks/calender.assigned_usersalready scopes to the caller's projects and is left unchanged.The property
The named routes are the reported instances of one property: every route in this namespace must constrain its query to the caller's projects, and bind any request-supplied id to the project it gated on.
Access_Projectand the scopedFile_Controller::indexalready follow it; this PR extends it to the task and per-user routes.Non-breaking
PM_Vars.current_user.ID) →Access_Userpasses.pm_role_user, so their own projects remain visible.Verification
php -lclean on all changed files.Files
core/Permissions/Access_User.php(new)routes/mytask.php—user-activities/tasks/tasks/calender→Access_Userroutes/task.php—tasks/sorting→Access_Projectsrc/Task/Helper/Task.php—where_membership()src/Task/Controllers/Task_Controller.php— bind sorting ids to project