# UNDF: UNDF-2026-000000092 Fixes grape-0002: Grape::Validations::Validators::ExceptValuesValidator#validate_param! — excepts Array#include? inside param_array.any? O(P×E). --- a/lib/grape/validations/validators/except_values_validator.rb +++ b/lib/grape/validations/validators/except_values_validator.rb @@ DEFECT grape-0002: line 19 validate_param! def validate_param!(attr_name, params) return unless params.respond_to?(:key?) && params.key?(attr_name) excepts = @except.is_a?(Proc) ? @except.call : @except return if excepts.nil? param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name]) - raise Grape::Exceptions::Validation.new(...) if param_array.any? { |param| excepts.include?(param) } + excepts_set = excepts.is_a?(Set) ? excepts : excepts.to_set # FIX: O(1) lookup + raise Grape::Exceptions::Validation.new(...) if param_array.any? { |param| excepts_set.include?(param) } end # BEFORE: excepts is a plain Array (the blocklist defined in params do ... except_values: [...] end) # param_array.any? { |param| excepts.include?(param) } # For a multi-value param: P submitted values × E excluded values = O(P×E) per request # # AFTER: excepts_set = excepts.to_set — O(E) once # excepts_set.include? is O(1) per submitted param # Total: O(P + E) per validation call # # Severity: MEDIUM — executed on every API request containing a multi-value param # with except_values configured as a long blocklist # At P=30 submitted, E=150 excluded: 4,500 → 180 ops per request — 25x improvement # # Note: grape-0001 (ValuesValidator) and grape-0002 (ExceptValuesValidator) are companion defects — # same pattern, opposite semantic (allowlist vs blocklist)