Impacted User Types
- admins — can create a CasaAdmin with
active=false via an extra field in the create request
- volunteers / supervisors — can trigger an unhandled 500 on
POST /casa_admins
Environment
main @ b7b87cd, Rails 8.0.5. Reproduced locally in both dev and test. Not a UI bug, so no screenshot — the repro below is request-level.
Current Behavior
UserParameters#without (app/values/user_parameters.rb#L51-L53) never removes anything when called with symbols:
def without(*keys)
params.reject! { |key| keys.include?(key) }
end
ActionController::Parameters#reject! yields string keys, so keys.include?(key) compares [:active, :type].include?("active") → always false.
Its only caller passes symbols — CreateCasaAdminService#build (app/services/create_casa_admin_service.rb#L10-L17):
CasaAdminParameters.new(@params)
.with_password(SecureRandom.hex(10))
.with_organization(@current_organization)
.without(:active, :type)
Both controllers hand the service raw params, and :active/:type are both on the UserParameters permit allowlist — so both survive into CasaAdmin.new:
active=false persists (DB default is true).
type=<sibling class> raises ActiveRecord::SubclassNotFound, which nothing rescues → 500. Rails' STI guard is what prevents this being a type-escalation; only type=CasaAdmin is accepted.
Separately, without is the only builder in the class that doesn't return self, so it can't be chained.
Expected Behavior
.without(:active, :type) strips both keys, so a new admin always gets active from the DB default and type from STI, regardless of the request body.
How to Replicate
# 1. active leaks — as an admin
sign_in create(:casa_admin, casa_org: org)
post casa_admins_path, params: {casa_admin: {email: "leak@example.com", display_name: "L", active: "false"}}
CasaAdmin.find_by(email: "leak@example.com").active # => false, expected true
# 2. type raises — as a volunteer
sign_in create(:volunteer, casa_org: org)
post casa_admins_path, params: {casa_admin: {email: "x@e.com", display_name: "X", type: "Volunteer"}}
# => ActiveRecord::SubclassNotFound
Note (2) surfaces as SubclassNotFound rather than Pundit::NotAuthorizedError because CasaAdminsController#create calls service.build before authorize (app/controllers/casa_admins_controller.rb#L41-L43). Nothing is persisted, but any authenticated user can trigger the error.
Suggested Fix
def without(*keys)
keys = keys.map(&:to_s)
params.reject! { |key| keys.include?(key) }
self
end
Happy to open a PR. Found while replacing the spec/values stub specs for #5563; those specs currently characterize the buggy behavior with a NOTE pointing here.
Impacted User Types
active=falsevia an extra field in the create requestPOST /casa_adminsEnvironment
main@ b7b87cd, Rails 8.0.5. Reproduced locally in both dev and test. Not a UI bug, so no screenshot — the repro below is request-level.Current Behavior
UserParameters#without(app/values/user_parameters.rb#L51-L53) never removes anything when called with symbols:ActionController::Parameters#reject!yields string keys, sokeys.include?(key)compares[:active, :type].include?("active")→ alwaysfalse.Its only caller passes symbols —
CreateCasaAdminService#build(app/services/create_casa_admin_service.rb#L10-L17):Both controllers hand the service raw
params, and:active/:typeare both on theUserParameterspermit allowlist — so both survive intoCasaAdmin.new:active=falsepersists (DB default istrue).type=<sibling class>raisesActiveRecord::SubclassNotFound, which nothing rescues → 500. Rails' STI guard is what prevents this being a type-escalation; onlytype=CasaAdminis accepted.Separately,
withoutis the only builder in the class that doesn't returnself, so it can't be chained.Expected Behavior
.without(:active, :type)strips both keys, so a new admin always getsactivefrom the DB default andtypefrom STI, regardless of the request body.How to Replicate
Note (2) surfaces as
SubclassNotFoundrather thanPundit::NotAuthorizedErrorbecauseCasaAdminsController#createcallsservice.buildbeforeauthorize(app/controllers/casa_admins_controller.rb#L41-L43). Nothing is persisted, but any authenticated user can trigger the error.Suggested Fix
Happy to open a PR. Found while replacing the
spec/valuesstub specs for #5563; those specs currently characterize the buggy behavior with aNOTEpointing here.