Part of #3848 · severity: high · related: #3833
Where
modules/organizations/policies/organizations.policy.js:19-25 and modules/organizations/controllers/organizations.membership.controller.js:76.
Problem
organizations.policy.js registers a document subject Organization whose guard returns true for any path under /api/organizations — which also matches POST /api/organizations/:id/members. In policy.js isAllowed(), document-subject resolution runs first and wins (because req.organization is set app-wide via app.param('organizationId', …)), so the intended Membership path-subject (owner/admin gate) is never consulted. create on Organization is unconditionally allowed → horizontal privilege escalation.
Exploit
Any authenticated user (not a member of org X) posts {"userId":"<self>","role":"member"} to /api/organizations/<X>/members; the document-subject grants create, the elevated-role guard is skipped for member, and a pending owner_add membership is created — self-join into any organization. Org ObjectIds are discoverable via the search/listByUser endpoints.
Fix
Tighten the Organization document-subject guard to exclude membership sub-routes (return false when the path contains /members or /requests) so the dedicated Membership owner/admin gate applies. Defense in depth: in addMember, require the actor to be an org owner/admin or a global admin before any add.
Created via /dev:issue
Part of #3848 · severity: high · related: #3833
Where
modules/organizations/policies/organizations.policy.js:19-25andmodules/organizations/controllers/organizations.membership.controller.js:76.Problem
organizations.policy.jsregisters a document subjectOrganizationwhose guard returns true for any path under/api/organizations— which also matchesPOST /api/organizations/:id/members. Inpolicy.jsisAllowed(), document-subject resolution runs first and wins (becausereq.organizationis set app-wide viaapp.param('organizationId', …)), so the intendedMembershippath-subject (owner/admin gate) is never consulted.createonOrganizationis unconditionally allowed → horizontal privilege escalation.Exploit
Any authenticated user (not a member of org X) posts
{"userId":"<self>","role":"member"}to/api/organizations/<X>/members; the document-subject grantscreate, the elevated-role guard is skipped formember, and a pendingowner_addmembership is created — self-join into any organization. Org ObjectIds are discoverable via the search/listByUser endpoints.Fix
Tighten the
Organizationdocument-subject guard to exclude membership sub-routes (return false when the path contains/membersor/requests) so the dedicatedMembershipowner/admin gate applies. Defense in depth: inaddMember, require the actor to be an org owner/admin or a global admin before any add.Created via /dev:issue