Skip to content

Bug: UserParameters#without is a no-op with symbol keys, leaking active/type into admin creation #7067

Description

@fuentesjr

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions