# UNDF: UNDF-2026-000000091 Fixes grape-0001: Grape::Validations::Validators::ValuesValidator#check_values? — values Array#include? inside param_array.all? O(P×V). --- a/lib/grape/validations/validators/values_validator.rb +++ b/lib/grape/validations/validators/values_validator.rb @@ DEFECT grape-0001: lines 36-39 check_values? def check_values?(val, attr_name) values = @values.is_a?(Proc) && @values.arity.zero? ? @values.call : @values return true if values.nil? param_array = val.nil? ? [nil] : Array.wrap(val) - return param_array.all? { |param| values.include?(param) } unless values.is_a?(Proc) + unless values.is_a?(Proc) # FIX: build Set once for O(1) lookup + values_set = values.is_a?(Set) ? values : values.to_set + return param_array.all? { |param| values_set.include?(param) } + end begin param_array.all? { |param| values.call(param) } rescue StandardError => e warn "Error '#{e}' raised while validating attribute '#{attr_name}'" false end end # BEFORE: values is a plain Array (the allowlist defined in params do ... values: [...] end) # param_array.all? { |param| values.include?(param) } # For a multi-value param (e.g., tags[]=a&tags[]=b&...): P params × V values = O(P×V) # Every validated request incurs this cost. # # AFTER: values_set = values.to_set — O(V) once (or reuse if already a Set) # values_set.include? is O(1) per param # Total: O(P + V) per validation call # # Severity: HIGH — executed on every API request that includes a multi-value param # with a large allowlist (e.g., enums with 100+ values, multi-select filters) # At P=50 submitted params, V=200 allowed values: 10,000 → 250 ops per request — 40x # # Speedup: ~V× where V = allowlist size