{"record":{"id":"933539b2d46cefb9","repo":"activerecord-hackery/ransack","slug":"invalid-search-term-key","errorCode":null,"errorMessage":"Invalid search term #{key}","messagePattern":"Invalid search term #(.+?)","errorType":"exception","errorClass":"InvalidSearchError","httpStatus":null,"severity":"error","filePath":"lib/ransack/search.rb","lineNumber":57,"sourceCode":"      @sorts ||= []\n      @ignore_unknown_conditions = options[:ignore_unknown_conditions] == false ? false : true\n      build(params.with_indifferent_access)\n    end\n\n    def result(opts = {})\n      @context.evaluate(self, opts)\n    end\n\n    def build(params)\n      collapse_multiparameter_attributes!(params).each do |key, value|\n        if ['s'.freeze, 'sorts'.freeze].freeze.include?(key)\n          send(\"#{key}=\", value)\n        elsif @context.ransackable_scope?(key, @context.object)\n          add_scope(key, value)\n        elsif base.attribute_method?(key)\n          base.send(\"#{key}=\", value)\n        elsif !Ransack.options[:ignore_unknown_conditions] || !@ignore_unknown_conditions\n          raise InvalidSearchError, \"Invalid search term #{key}\"\n        end\n      end\n      self\n    end\n\n    def sorts=(args)\n      case args\n      when Array\n        args.each do |sort|\n          if sort.kind_of? Hash\n            sort = Nodes::Sort.new(@context).build(sort)\n          else\n            sort = Nodes::Sort.extract(@context, sort)\n          end\n          self.sorts << sort if sort\n        end\n      when Hash\n        args.each do |index, attrs|","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/activerecord-hackery/ransack/blob/e82f6bab3956c5597e7debbefa218d9e94e58ceb/lib/ransack/search.rb#L39-L75","documentation":"Search#build iterates every key of the q params hash. A key must be 's'/'sorts', a whitelisted ransackable scope, or an attribute method recognized by the context (attribute + predicate over a searchable association). Otherwise, when unknown conditions are not ignored — Model.ransack! sets ignore_unknown_conditions: false per search, and the global config can too — Ransack raises InvalidSearchError with 'Invalid search term <key>'.","triggerScenarios":"Person.ransack!(params[:q]) where params include q[full_name_eq]=x but full_name is not in Person.ransackable_attributes; q[enabled]=true where enabled is neither attribute-with-predicate nor whitelisted scope; stray keys like q[commit] or q[utf8] leaking in; an attribute on an association whose ransackable_associations whitelist excludes it.","commonSituations":"Adopting the strict ransack! convention after a security review; adding new searchable columns or ransackers but forgetting to update ransackable_attributes/ransackable_scopes allowlists; forms that submit extra fields (submit buttons, hidden CSRF-derived keys) inside the q namespace; users hand-editing URLs.","solutions":["Add the attribute to the model allowlist: def self.ransackable_attributes(auth_object = nil); %w[name ... full_name] ... end","For scopes, add the scope name to ransackable_scopes whitelist (as symbols) and define the scope","Use non-strict Model.ransack(params[:q]) instead of ransack! so unknown terms are silently dropped (default ignore_unknown_conditions: true)","Rescue Ransack::InvalidSearchError in the controller, notify the user, and re-render with a clean search"],"exampleFix":"# before\n# params: q => { \"full_name_eq\" => \"Alice\" }\nPerson.ransack!(params[:q])\n# => Ransack::InvalidSearchError: Invalid search term full_name_eq\n\n# after\nclass Person < ApplicationRecord\n  def self.ransackable_attributes(auth_object = nil)\n    %w[name full_name created_at]\n  end\nend\nPerson.ransack!(params[:q])","handlingStrategy":"try-catch","validationCode":"# reject keys that are neither sorts, whitelisted scopes, nor searchable attributes\nALLOWED_SCOPES = %i[active].freeze\n\ndef strict_q_params(raw, klass)\n  raw.select do |key, _|\n    next true if %w[s sorts].include?(key)\n    next true if klass.ransackable_scopes.map(&:to_s).include?(key)\n    klass.ransackable_attributes.any? do |a|\n      key.start_with?(a) && key != a # has a predicate suffix\n    end\n  end\nend\n\n@q = Person.ransack!(strict_q_params(params[:q], Person))","typeGuard":"def known_search_term?(key, klass)\n  %w[s sorts].include?(key) ||\n    klass.method_defined?(nil) ||\n    klass.ransackable_attributes.any? { |a| key.to_s.start_with?(a + '_') } ||\n    klass.ransackable_scopes.any? { |s| s.to_s == key }\nend","tryCatchPattern":"begin\n  @q = Person.ransack!(params[:q])\nrescue Ransack::InvalidSearchError => e\n  Rails.logger.warn(\"Rejected search term: #{e.message}\")\n  @q = Person.ransack  # empty search, user re-enters filters\nend","preventionTips":["Keep ransackable_attributes/ransackable_scopes allowlists in sync with every form field you ship","Use ransack! only on internal endpoints; for public endpoints use ransack and validate params anyway","Scope the q param in Strong Parameters (permit specific q keys when feasible) so stray fields never reach the search"],"tags":["ruby","rails","ransack","allowlist","search-params","invalidsearcherror"],"backgroundTag":"unknown-search-parameter","analyzedSha":"e82f6bab3956c5597e7debbefa218d9e94e58ceb","analyzedAt":"2026-08-21T19:30:23.639Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}