ransack 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0251527dc22cca8cb55ff672b598d74fef0ec4c2b1c6336cb0b8d52d69dabafc
4
- data.tar.gz: fd218c05d4697cca5b9a8b4111af7a5fa4ddc559071e9a3194aca2f2b2da6c9d
3
+ metadata.gz: e1da5ebd35a1352f55541bdc21342c512b6209e385546e8e217c38c4dea79ad5
4
+ data.tar.gz: 61525c16d6c5e02c57575e72edd28eb9b6376a53bf791e1fda60b2581a95049d
5
5
  SHA512:
6
- metadata.gz: bc524a7f9a4fac90d6f2e3b9818ccf76bab0f44c872c33db043f049f759f2641c03b338c2cfa28c70a20f6d1d2c4569390e43ec9e5da9daf2537002c519ad3fc
7
- data.tar.gz: 3a599c3c9472f0033976ed0301cc0545b62711cccb36817db373c0f19ba0b828640a89bc3784b533346633112fb017353c63c490fa792b3eb3e7aea82f41e527
6
+ metadata.gz: 0bcdd2aeda080a80a469e591a27b5a4dfa5dadf36b3de1e62a037de661c01e3d8092d4f60d5d458097cace0a1ce87434590a9914651dfd74f899f7d03a144367
7
+ data.tar.gz: 7aea600100c4ea0001ffed5c84729e950ecb7ccc5421fb853886031585c1a3486377c15facef12c43796a9c5072069972303aa74c868daf24ebfc235e7a49900
@@ -64,6 +64,8 @@ module Ransack
64
64
  end
65
65
 
66
66
  def attribute_method?(str, klass = @klass)
67
+ return false unless key_within_depth_limit?(str)
68
+
67
69
  exists = false
68
70
  if ransackable_attribute?(str, klass) ||
69
71
  ransortable_attribute?(str, klass)
@@ -238,6 +240,8 @@ module Ransack
238
240
  end
239
241
 
240
242
  def get_parent_and_attribute_name(str, parent = @base)
243
+ return [parent, nil] unless key_within_depth_limit?(str)
244
+
241
245
  attr_name = nil
242
246
 
243
247
  if ransackable_attribute?(str, klassify(parent)) ||
@@ -50,6 +50,15 @@ module Ransack
50
50
 
51
51
  DISTINCT = 'DISTINCT '.freeze
52
52
 
53
+ # The most `_`-separated segments a single condition or sort key may have
54
+ # before Ransack rejects it as invalid instead of parsing it. Working out
55
+ # whether a key is an attribute, an `_and_`/`_or_` compound or a path
56
+ # through associations is superlinear in this count, and the key comes
57
+ # straight from the query string, so an unbounded one is a CPU-exhaustion
58
+ # denial of service (GHSA-j3f8-w227-4hh8). No real search key is this
59
+ # long: OR-ing every column of a very wide table stays well under it.
60
+ MAX_KEY_DEPTH = 200
61
+
53
62
  DERIVED_PREDICATES = [
54
63
  [CONT, {
55
64
  arel_predicate: 'matches'.freeze,
@@ -52,6 +52,15 @@ module Ransack
52
52
  @base = @join_dependency.instance_variable_get(:@join_root)
53
53
  end
54
54
 
55
+ # False when a key has more `_`-separated segments than Ransack will
56
+ # parse. The attribute / association / predicate parsing that follows is
57
+ # superlinear in the segment count and the key is attacker-controlled, so
58
+ # an over-long key is treated as unknown rather than parsed
59
+ # (GHSA-j3f8-w227-4hh8).
60
+ def key_within_depth_limit?(key)
61
+ key.to_s.count(Constants::UNDERSCORE) <= Constants::MAX_KEY_DEPTH
62
+ end
63
+
55
64
  def bind_pair_for(key)
56
65
  @bind_pairs ||= {}
57
66
 
@@ -137,6 +146,8 @@ module Ransack
137
146
  end
138
147
 
139
148
  def association_path(str, base = @base)
149
+ return ''.freeze unless key_within_depth_limit?(str)
150
+
140
151
  base = klassify(base)
141
152
  str ||= ''.freeze
142
153
  path = []
@@ -126,6 +126,8 @@ module Ransack
126
126
  end
127
127
 
128
128
  def attribute_method?(name)
129
+ return false unless @context.key_within_depth_limit?(name)
130
+
129
131
  stripped_name = strip_predicate_and_index(name)
130
132
  return true if @context.attribute_method?(stripped_name) ||
131
133
  @context.attribute_method?(name)
@@ -147,19 +147,31 @@ module Ransack
147
147
  @context.chain_scope(key, sanitized_args)
148
148
  end
149
149
 
150
+ # The largest position a multiparameter key (`created_at(1i)`) may carry.
151
+ # Rails' date and time selects emit at most six, year to second. The
152
+ # position indexes an array, so an unbounded one from a crafted query
153
+ # string would allocate an array that size (GHSA-vxc9-rm8f-p56j).
154
+ MULTIPARAMETER_POSITION_LIMIT = 16
155
+
156
+ # Folds `created_at(1i)`, `created_at(2i)`, ... into `created_at` as an
157
+ # array of cast values, the way Active Record does for form input. The
158
+ # keys are untrusted, so a malformed one is dropped rather than raised
159
+ # on: no position (`created_at(`), a position outside 1 to the limit,
160
+ # or a fragment for an attribute that was also given as a plain value.
150
161
  def collapse_multiparameter_attributes!(attrs)
151
162
  attrs.keys.each do |k|
152
163
  if k.include?(Constants::LEFT_PARENTHESIS)
153
164
  real_attribute, position = k.split(/\(|\)/)
154
- cast =
155
- if Constants::A_S_I.include?(position.last)
156
- position.last
157
- else
158
- nil
159
- end
160
- position = position.to_i - 1
161
165
  value = attrs.delete(k)
166
+ next if position.nil?
167
+
168
+ cast = Constants::A_S_I.include?(position.last) ? position.last : nil
169
+ position = position.to_i - 1
170
+ next if position < 0 || position >= MULTIPARAMETER_POSITION_LIMIT
171
+
162
172
  attrs[real_attribute] ||= []
173
+ next unless attrs[real_attribute].is_a?(Array)
174
+
163
175
  attrs[real_attribute][position] =
164
176
  if cast
165
177
  if value.blank? && cast == Constants::I
@@ -1,3 +1,3 @@
1
1
  module Ransack
2
- VERSION = '5.0.0'
2
+ VERSION = '5.0.2'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ransack
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 5.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ernie Miller