Summary
Multiple controllers use Model.find(params[:id]) without scoping to current_organization, allowing authenticated users from one organization to read and modify data belonging to other organizations.
Affected Controllers
1. RequestsController (state-changing)
show (line 31): Request.find(params[:id]) — cross-org read
start (line 45): Request.find(params[:id]) + request.status_started! — cross-org state change
Compare with index (line 6) which properly uses current_organization.ordered_requests.
2. DonationsController (data modification)
print (line 6): Donation.find(params[:id]) — cross-org PDF generation
show (line 66): Donation.find(params[:id]) — cross-org read
edit (line 56): Donation.find(params[:id]) — cross-org read
update (line 71): Donation.find(params[:id]) — cross-org modification
Compare with destroy (line 90) which properly uses current_organization.id.
3. KitsController (inventory manipulation)
deactivate (line 47): Kit.find(params[:id]) — cross-org state change
reactivate (line 53): Kit.find(params[:id]) — cross-org state change
allocations (line 63): Kit.find(params[:id]) — cross-org read
allocate (line 71): Kit.find(params[:id]) — cross-org inventory modification
Compare with index (line 7) which properly uses current_organization.kits.
4. BroadcastAnnouncementsController
set_broadcast_announcement (line 48): BroadcastAnnouncement.find(params[:id]) — affects edit/update/destroy
Compare with index (line 5) which properly filters by organization_id: current_organization.id.
5. DistributionsController
print (line 17): Distribution.find(params[:id]) — cross-org PDF with partner data
Root Cause
The authorize_user method in ApplicationController checks that the user has a valid role for some organization, but doesn't enforce that the accessed record belongs to the user's organization. Controllers that use Model.find(params[:id]) instead of current_organization.model_name.find(params[:id]) bypass organization isolation.
Fix
Replace Model.find(params[:id]) with current_organization.model_name.find(params[:id]) in all affected actions:
# Before (vulnerable)
@request = Request.find(params[:id])
# After (fixed)
@request = current_organization.requests.find(params[:id])
This pattern is already correctly used in many other controllers and some actions within the affected controllers themselves.
Found via automated security audit. CWE-639: Authorization Bypass Through User-Controlled Key.
Summary
Multiple controllers use
Model.find(params[:id])without scoping tocurrent_organization, allowing authenticated users from one organization to read and modify data belonging to other organizations.Affected Controllers
1. RequestsController (state-changing)
show(line 31):Request.find(params[:id])— cross-org readstart(line 45):Request.find(params[:id])+request.status_started!— cross-org state changeCompare with
index(line 6) which properly usescurrent_organization.ordered_requests.2. DonationsController (data modification)
print(line 6):Donation.find(params[:id])— cross-org PDF generationshow(line 66):Donation.find(params[:id])— cross-org readedit(line 56):Donation.find(params[:id])— cross-org readupdate(line 71):Donation.find(params[:id])— cross-org modificationCompare with
destroy(line 90) which properly usescurrent_organization.id.3. KitsController (inventory manipulation)
deactivate(line 47):Kit.find(params[:id])— cross-org state changereactivate(line 53):Kit.find(params[:id])— cross-org state changeallocations(line 63):Kit.find(params[:id])— cross-org readallocate(line 71):Kit.find(params[:id])— cross-org inventory modificationCompare with
index(line 7) which properly usescurrent_organization.kits.4. BroadcastAnnouncementsController
set_broadcast_announcement(line 48):BroadcastAnnouncement.find(params[:id])— affects edit/update/destroyCompare with
index(line 5) which properly filters byorganization_id: current_organization.id.5. DistributionsController
print(line 17):Distribution.find(params[:id])— cross-org PDF with partner dataRoot Cause
The
authorize_usermethod inApplicationControllerchecks that the user has a valid role for some organization, but doesn't enforce that the accessed record belongs to the user's organization. Controllers that useModel.find(params[:id])instead ofcurrent_organization.model_name.find(params[:id])bypass organization isolation.Fix
Replace
Model.find(params[:id])withcurrent_organization.model_name.find(params[:id])in all affected actions:This pattern is already correctly used in many other controllers and some actions within the affected controllers themselves.
Found via automated security audit. CWE-639: Authorization Bypass Through User-Controlled Key.