brakeman-lib 5.0.4 → 5.1.0

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: 2bc22b69b0b137fe9f223c2469fe6e3857054b0b98621645b52ed94af7fa4886
4
- data.tar.gz: 183f206e691c8251adef49319ca76939a4bf079cf5b14ead1f3f7923754ff9ff
3
+ metadata.gz: 425078e2c4abfb5dc629bd5b70fcbaa1de59be69093097ad5ca78c3f425f575c
4
+ data.tar.gz: 1ddaf7c9084213dcc7db6772dc164095800de50897d157345a01c234d09fe778
5
5
  SHA512:
6
- metadata.gz: b902bcfbc2be499f0a892534bf443d88ce92f5a0b47edd31b7ac01a964e9ec13230f03f5ba8bb246dcdee27a7e6bd72b873d2826ce2f6b2486f64999f45b52e8
7
- data.tar.gz: 62f878559fd4aa1f2d96c35742d0c490c0d57c53e07d9a3619675f6519c0fafbaace29e0b616c217ea796132e7810b51b3f06cf1ff60a3a28a34ae9482069f32
6
+ metadata.gz: 4a0a910c6859f389eeaf21253dc8d33f7f0d199e2289bc3e6145b7d9eecaf7dd0793dad3a2a013ec3a4c64c681cfbbf88647e21566ea3b7269bf485f29ef10ee
7
+ data.tar.gz: 2d5845a9bd98a86f3af891122d9fe410da8586aa8aa45ccb2e05bcf25b8fdf6b6702d6ac396f866a4b53b9659cceface62a03997c30954d20d2e32b73cffab5c
data/CHANGES.md CHANGED
@@ -1,3 +1,30 @@
1
+ # 5.1.0 - 2021-07-19
2
+
3
+ * Initial support for ActiveRecord enums
4
+ * Support `Hash#include?`
5
+ * Interprocedural dataflow from very simple class methods
6
+ * Fix SARIF report when checks have no description (Eli Block)
7
+ * Add ignored warnings to SARIF report (Eli Block)
8
+ * Add `--sql-safe-methods` option (Esty Scheiner)
9
+ * Update SQL injection check for Rails 6.0/6.1
10
+ * Fix false positive in command injection with `Open3.capture` (Richard Fitzgerald)
11
+ * Fix infinite loop on mixin self-includes (Andrew Szczepanski)
12
+ * Ignore dates in SQL
13
+ * Refactor `cookie?`/`param?` methods (Keenan Brock)
14
+ * Ignore renderables in dynamic render path check (Brad Parker)
15
+ * Support `Array#push`
16
+ * Better `Array#join` support
17
+ * Adjust copy of `--interactive` menu (Elia Schito)
18
+ * Support `Array#*`
19
+ * Better method definition tracking and lookup
20
+ * Support `Hash#values` and `Hash#values_at`
21
+ * Check for user-controlled evaluation even if it's a call target
22
+ * Support `Array#fetch` and `Hash#fetch`
23
+ * Ignore `sanitize_sql_like` in SQL
24
+ * Ignore method calls on numbers in SQL
25
+ * Add GitHub Actions format (Klaus Badelt)
26
+ * Read and parse files in parallel
27
+
1
28
  # 5.0.4 - 2021-06-08
2
29
 
3
30
  (brakeman gem release only)
data/lib/brakeman.rb CHANGED
@@ -65,6 +65,7 @@ module Brakeman
65
65
  # * :report_routes - show found routes on controllers (default: false)
66
66
  # * :run_checks - array of checks to run (run all if not specified)
67
67
  # * :safe_methods - array of methods to consider safe
68
+ # * :sql_safe_methods - array of sql sanitization methods to consider safe
68
69
  # * :skip_libs - do not process lib/ directory (default: false)
69
70
  # * :skip_vendor - do not process vendor/ directory (default: true)
70
71
  # * :skip_checks - checks not to run (run all if not specified)
@@ -198,6 +199,7 @@ module Brakeman
198
199
  :relative_path => false,
199
200
  :report_progress => true,
200
201
  :safe_methods => Set.new,
202
+ :sql_safe_methods => Set.new,
201
203
  :skip_checks => Set.new,
202
204
  :skip_vendor => true,
203
205
  }
@@ -250,6 +252,8 @@ module Brakeman
250
252
  [:to_sarif]
251
253
  when :sonar, :to_sonar
252
254
  [:to_sonar]
255
+ when :github, :to_github
256
+ [:to_github]
253
257
  else
254
258
  [:to_text]
255
259
  end
@@ -283,6 +287,8 @@ module Brakeman
283
287
  :to_sarif
284
288
  when /\.sonar$/i
285
289
  :to_sonar
290
+ when /\.github$/i
291
+ :to_github
286
292
  else
287
293
  :to_text
288
294
  end
@@ -521,12 +527,14 @@ module Brakeman
521
527
 
522
528
  # Returns an array of alert fingerprints for any ignored warnings without
523
529
  # notes found in the specified ignore file (if it exists).
524
- def self.ignore_file_entries_with_empty_notes file
530
+ def self.ignore_file_entries_with_empty_notes file, options
525
531
  return [] unless file
526
532
 
527
533
  require 'brakeman/report/ignore/config'
528
534
 
529
- config = IgnoreConfig.new(file, nil)
535
+ app_tree = Brakeman::AppTree.from_options(options)
536
+
537
+ config = IgnoreConfig.new(Brakeman::FilePath.from_app_tree(app_tree, file), nil)
530
538
  config.read_from_file
531
539
  config.already_ignored_entries_with_empty_notes.map { |i| i[:fingerprint] }
532
540
  end
@@ -537,9 +545,9 @@ module Brakeman
537
545
  app_tree = Brakeman::AppTree.from_options(options)
538
546
 
539
547
  if options[:ignore_file]
540
- file = options[:ignore_file]
548
+ file = Brakeman::FilePath.from_app_tree(app_tree, options[:ignore_file])
541
549
  elsif app_tree.exists? "config/brakeman.ignore"
542
- file = app_tree.expand_path("config/brakeman.ignore")
550
+ file = Brakeman::FilePath.from_app_tree(app_tree, "config/brakeman.ignore")
543
551
  elsif not options[:interactive_ignore]
544
552
  return
545
553
  end
@@ -26,7 +26,7 @@ class Brakeman::CheckDetailedExceptions < Brakeman::BaseCheck
26
26
  def check_detailed_exceptions
27
27
  tracker.controllers.each do |_name, controller|
28
28
  controller.methods_public.each do |method_name, definition|
29
- src = definition[:src]
29
+ src = definition.src
30
30
  body = src.body.last
31
31
  next unless body
32
32
 
@@ -10,7 +10,7 @@ class Brakeman::CheckEvaluation < Brakeman::BaseCheck
10
10
  #Process calls
11
11
  def run_check
12
12
  Brakeman.debug "Finding eval-like calls"
13
- calls = tracker.find_call :method => [:eval, :instance_eval, :class_eval, :module_eval]
13
+ calls = tracker.find_call methods: [:eval, :instance_eval, :class_eval, :module_eval], nested: true
14
14
 
15
15
  Brakeman.debug "Processing eval-like calls"
16
16
  calls.each do |call|
@@ -87,6 +87,16 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
87
87
  dangerous_interp?(first_arg) ||
88
88
  dangerous_string_building?(first_arg)
89
89
  end
90
+ when :capture2, :capture2e, :capture3
91
+ # Open3 capture methods can take a :stdin_data argument which is used as the
92
+ # the input to the called command so it is not succeptable to command injection.
93
+ # As such if the last argument is a hash (and therefore execution options) it
94
+ # should be ignored
95
+
96
+ args.pop if hash?(args.last) && args.length > 2
97
+ failure = include_user_input?(args) ||
98
+ dangerous_interp?(args) ||
99
+ dangerous_string_building?(args)
90
100
  else
91
101
  failure = include_user_input?(args) ||
92
102
  dangerous_interp?(args) ||
@@ -33,6 +33,7 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
33
33
  view = result[:call][2]
34
34
 
35
35
  if sexp? view and original? result
36
+ return if renderable?(view)
36
37
 
37
38
  if input = has_immediate_user_input?(view)
38
39
  if string_interp? view
@@ -94,4 +95,17 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
94
95
  end
95
96
  end
96
97
  end
97
- end
98
+
99
+ def renderable? exp
100
+ return false unless call?(exp) and constant?(exp.target)
101
+
102
+ target_class_name = class_name(exp.target)
103
+ known_renderable_class?(target_class_name) or tracker.find_method(:render_in, target_class_name)
104
+ end
105
+
106
+ def known_renderable_class? class_name
107
+ klass = tracker.find_class(class_name)
108
+ return false if klass.nil?
109
+ klass.ancestor? :"ViewComponent::Base"
110
+ end
111
+ end
@@ -22,7 +22,19 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
22
22
  :find_by_sql, :maximum, :minimum, :pluck, :sum, :update_all]
23
23
  @sql_targets.concat [:from, :group, :having, :joins, :lock, :order, :reorder, :where] if tracker.options[:rails3]
24
24
  @sql_targets.concat [:find_by, :find_by!, :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, :not] if tracker.options[:rails4]
25
- @sql_targets << :delete_by << :destroy_by if tracker.options[:rails6]
25
+
26
+ if tracker.options[:rails6]
27
+ @sql_targets.concat [:delete_by, :destroy_by, :rewhere, :reselect]
28
+
29
+ @sql_targets.delete :delete_all
30
+ @sql_targets.delete :destroy_all
31
+ end
32
+
33
+ if version_between?("6.1.0", "9.9.9")
34
+ @sql_targets.delete :order
35
+ @sql_targets.delete :reorder
36
+ @sql_targets.delete :pluck
37
+ end
26
38
 
27
39
  if version_between?("2.0.0", "3.9.9") or tracker.config.rails_version.nil?
28
40
  @sql_targets << :first << :last << :all
@@ -185,7 +197,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
185
197
  else
186
198
  check_find_arguments call.last_arg
187
199
  end
188
- when :where, :having, :find_by, :find_by!, :find_or_create_by, :find_or_create_by!, :find_or_initialize_by,:not, :delete_by, :destroy_by
200
+ when :where, :rewhere, :having, :find_by, :find_by!, :find_or_create_by, :find_or_create_by!, :find_or_initialize_by,:not, :delete_by, :destroy_by
189
201
  check_query_arguments call.arglist
190
202
  when :order, :group, :reorder
191
203
  check_order_arguments call.arglist
@@ -199,7 +211,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
199
211
  unsafe_sql? call.first_arg
200
212
  when :sql
201
213
  unsafe_sql? call.first_arg
202
- when :update_all, :select
214
+ when :update_all, :select, :reselect
203
215
  check_update_all_arguments call.args
204
216
  when *@connection_calls
205
217
  check_by_sql_arguments call.first_arg
@@ -572,13 +584,17 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
572
584
  end
573
585
 
574
586
  IGNORE_METHODS_IN_SQL = Set[:id, :merge_conditions, :table_name, :quoted_table_name,
575
- :quoted_primary_key, :to_i, :to_f, :sanitize_sql, :sanitize_sql_array,
587
+ :quoted_primary_key, :to_i, :to_f, :sanitize_sql, :sanitize_sql_array, :sanitize_sql_like,
576
588
  :sanitize_sql_for_assignment, :sanitize_sql_for_conditions, :sanitize_sql_hash,
577
589
  :sanitize_sql_hash_for_assignment, :sanitize_sql_hash_for_conditions,
578
590
  :to_sql, :sanitize, :primary_key, :table_name_prefix, :table_name_suffix,
579
591
  :where_values_hash, :foreign_key, :uuid
580
592
  ]
581
593
 
594
+ def ignore_methods_in_sql
595
+ @ignore_methods_in_sql ||= IGNORE_METHODS_IN_SQL + (tracker.options[:sql_safe_methods] || [])
596
+ end
597
+
582
598
  def safe_value? exp
583
599
  return true unless sexp? exp
584
600
 
@@ -589,10 +605,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
589
605
  if exp.method == :to_s or exp.method == :to_sym
590
606
  safe_value? exp.target
591
607
  else
592
- IGNORE_METHODS_IN_SQL.include? exp.method or
593
- quote_call? exp or
594
- arel? exp or
595
- exp.method.to_s.end_with? "_id"
608
+ ignore_call? exp
596
609
  end
597
610
  when :if
598
611
  safe_value? exp.then_clause and safe_value? exp.else_clause
@@ -607,6 +620,17 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
607
620
  end
608
621
  end
609
622
 
623
+ def ignore_call? exp
624
+ return unless call? exp
625
+
626
+ ignore_methods_in_sql.include? exp.method or
627
+ quote_call? exp or
628
+ arel? exp or
629
+ exp.method.to_s.end_with? "_id" or
630
+ number_target? exp or
631
+ date_target? exp
632
+ end
633
+
610
634
  QUOTE_METHODS = [:quote, :quote_column_name, :quoted_date, :quote_string, :quote_table_name]
611
635
 
612
636
  def quote_call? exp
@@ -695,4 +719,30 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
695
719
  active_record_models.include? klass
696
720
  end
697
721
  end
722
+
723
+ def number_target? exp
724
+ return unless call? exp
725
+
726
+ if number? exp.target
727
+ true
728
+ elsif call? exp.target
729
+ number_target? exp.target
730
+ else
731
+ false
732
+ end
733
+ end
734
+
735
+ DATE_CLASS = s(:const, :Date)
736
+
737
+ def date_target? exp
738
+ return unless call? exp
739
+
740
+ if exp.target == DATE_CLASS
741
+ true
742
+ elsif call? exp.target
743
+ date_target? exp.target
744
+ else
745
+ false
746
+ end
747
+ end
698
748
  end
@@ -32,7 +32,7 @@ class Brakeman::CheckVerbConfusion < Brakeman::BaseCheck
32
32
  return
33
33
  end
34
34
 
35
- process method[:src]
35
+ process method.src
36
36
  end
37
37
 
38
38
  def process_if exp
@@ -126,7 +126,7 @@ module Brakeman
126
126
 
127
127
  ensure_ignore_notes_failed = false
128
128
  if tracker.options[:ensure_ignore_notes]
129
- fingerprints = Brakeman::ignore_file_entries_with_empty_notes tracker.ignored_filter&.file
129
+ fingerprints = Brakeman::ignore_file_entries_with_empty_notes tracker.ignored_filter&.file, options
130
130
 
131
131
  unless fingerprints.empty?
132
132
  ensure_ignore_notes_failed = true
@@ -1,3 +1,5 @@
1
+ require 'parallel'
2
+
1
3
  module Brakeman
2
4
  ASTFile = Struct.new(:path, :ast)
3
5
 
@@ -5,29 +7,62 @@ module Brakeman
5
7
  class FileParser
6
8
  attr_reader :file_list, :errors
7
9
 
8
- def initialize app_tree, timeout
10
+ def initialize app_tree, timeout, parallel = true
9
11
  @app_tree = app_tree
10
12
  @timeout = timeout
11
13
  @file_list = []
12
14
  @errors = []
15
+ @parallel = parallel
13
16
  end
14
17
 
15
18
  def parse_files list
16
- read_files list do |path, contents|
17
- if ast = parse_ruby(contents, path.relative)
18
- ASTFile.new(path, ast)
19
+ if @parallel
20
+ parallel_options = {}
21
+ else
22
+ # Disable parallelism
23
+ parallel_options = { in_threads: 0 }
24
+ end
25
+
26
+ # Parse the files in parallel.
27
+ # By default, the parsing will be in separate processes.
28
+ # So we map the result to ASTFiles and/or Exceptions
29
+ # then partition them into ASTFiles and Exceptions
30
+ # and add the Exceptions to @errors
31
+ #
32
+ # Basically just a funky way to deal with two possible
33
+ # return types that are returned from isolated processes.
34
+ #
35
+ # Note this method no longer uses read_files
36
+ @file_list, new_errors = Parallel.map(list, parallel_options) do |file_name|
37
+ file_path = @app_tree.file_path(file_name)
38
+ contents = file_path.read
39
+
40
+ begin
41
+ if ast = parse_ruby(contents, file_path.relative)
42
+ ASTFile.new(file_name, ast)
43
+ end
44
+ rescue Exception => e
45
+ e
19
46
  end
47
+ end.compact.partition do |result|
48
+ result.is_a? ASTFile
20
49
  end
50
+
51
+ errors.concat new_errors
21
52
  end
22
53
 
23
54
  def read_files list
24
55
  list.each do |path|
25
56
  file = @app_tree.file_path(path)
26
57
 
27
- result = yield file, file.read
58
+ begin
59
+ result = yield file, file.read
28
60
 
29
- if result
30
- @file_list << result
61
+ if result
62
+ @file_list << result
63
+ end
64
+ rescue Exception => e
65
+ @errors << e
31
66
  end
32
67
  end
33
68
  end
@@ -42,17 +77,12 @@ module Brakeman
42
77
  Brakeman.debug "Parsing #{path}"
43
78
  RubyParser.new.parse input, path, @timeout
44
79
  rescue Racc::ParseError => e
45
- error e.exception(e.message + "\nCould not parse #{path}")
80
+ raise e.exception(e.message + "\nCould not parse #{path}")
46
81
  rescue Timeout::Error => e
47
- error Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout")
82
+ raise Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout")
48
83
  rescue => e
49
- error e.exception(e.message + "\nWhile processing #{path}")
84
+ raise e.exception(e.message + "\nWhile processing #{path}")
50
85
  end
51
86
  end
52
-
53
- def error exception
54
- @errors << exception
55
- nil
56
- end
57
87
  end
58
88
  end
@@ -39,7 +39,7 @@ module Brakeman::Options
39
39
  OptionParser.new do |opts|
40
40
  opts.banner = "Usage: brakeman [options] rails/root/path"
41
41
 
42
- opts.on "-n", "--no-threads", "Run checks sequentially" do
42
+ opts.on "-n", "--no-threads", "Run checks and file parsing sequentially" do
43
43
  options[:parallel_checks] = false
44
44
  end
45
45
 
@@ -151,6 +151,11 @@ module Brakeman::Options
151
151
  options[:safe_methods].merge methods.map {|e| e.to_sym }
152
152
  end
153
153
 
154
+ opts.on "--sql-safe-methods meth1,meth2,etc", Array, "Do not warn of SQL if the input is wrapped in a safe method" do |methods|
155
+ options[:sql_safe_methods] ||= Set.new
156
+ options[:sql_safe_methods].merge methods.map {|e| e.to_sym }
157
+ end
158
+
154
159
  opts.on "--url-safe-methods method1,method2,etc", Array, "Do not warn of XSS if the link_to href parameter is wrapped in a safe method" do |methods|
155
160
  options[:url_safe_methods] ||= Set.new
156
161
  options[:url_safe_methods].merge methods.map {|e| e.to_sym }
@@ -233,7 +238,7 @@ module Brakeman::Options
233
238
 
234
239
  opts.on "-f",
235
240
  "--format TYPE",
236
- [:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif, :sonar],
241
+ [:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif, :sonar, :github],
237
242
  "Specify output formats. Default is text" do |type|
238
243
 
239
244
  type = "s" if type == :text
@@ -208,6 +208,15 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
208
208
  return Sexp.new(:false).line(exp.line)
209
209
  end
210
210
 
211
+ # For the simplest case of `Foo.thing`
212
+ if node_type? target, :const and first_arg.nil?
213
+ if tracker and (klass = tracker.find_class(class_name(target.value)))
214
+ if return_value = klass.get_simple_method_return_value(:class, method)
215
+ return return_value.deep_clone(exp.line)
216
+ end
217
+ end
218
+ end
219
+
211
220
  #See if it is possible to simplify some basic cases
212
221
  #of addition/concatenation.
213
222
  case method
@@ -220,13 +229,28 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
220
229
  exp = math_op(:+, target, first_arg, exp)
221
230
  end
222
231
  when :-, :*, :/
223
- exp = math_op(method, target, first_arg, exp)
232
+ if method == :* and array? target
233
+ if string? first_arg
234
+ exp = process_array_join(target, first_arg)
235
+ end
236
+ else
237
+ exp = math_op(method, target, first_arg, exp)
238
+ end
224
239
  when :[]
225
240
  if array? target
226
241
  exp = process_array_access(target, exp.args, exp)
227
242
  elsif hash? target
228
243
  exp = process_hash_access(target, first_arg, exp)
229
244
  end
245
+ when :fetch
246
+ if array? target
247
+ # Not dealing with default value
248
+ # so just pass in first argument, but process_array_access expects
249
+ # an array of arguments.
250
+ exp = process_array_access(target, [first_arg], exp)
251
+ elsif hash? target
252
+ exp = process_hash_access(target, first_arg, exp)
253
+ end
230
254
  when :merge!, :update
231
255
  if hash? target and hash? first_arg
232
256
  target = process_hash_merge! target, first_arg
@@ -266,6 +290,12 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
266
290
  target = find_push_target(target_var)
267
291
  env[target] = exp unless target.nil? # Happens in TemplateAliasProcessor
268
292
  end
293
+ when :push
294
+ if array? target
295
+ target << first_arg
296
+ env[target_var] = target
297
+ return target
298
+ end
269
299
  when :first
270
300
  if array? target and first_arg.nil? and sexp? target[1]
271
301
  exp = target[1]
@@ -279,7 +309,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
279
309
  exp = target
280
310
  end
281
311
  when :join
282
- if array? target and target.length > 2 and (string? first_arg or first_arg.nil?)
312
+ if array? target and (string? first_arg or first_arg.nil?)
283
313
  exp = process_array_join(target, first_arg)
284
314
  end
285
315
  when :!
@@ -287,6 +317,15 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
287
317
  if call? target and target.method == :!
288
318
  exp = s(:or, s(:true).line(exp.line), s(:false).line(exp.line)).line(exp.line)
289
319
  end
320
+ when :values
321
+ # Hash literal
322
+ if node_type? target, :hash
323
+ exp = hash_values(target)
324
+ end
325
+ when :values_at
326
+ if node_type? target, :hash
327
+ exp = hash_values_at target, exp.args
328
+ end
290
329
  end
291
330
 
292
331
  exp
@@ -294,6 +333,11 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
294
333
 
295
334
  # Painful conversion of Array#join into string interpolation
296
335
  def process_array_join array, join_str
336
+ # Empty array
337
+ if array.length == 1
338
+ return s(:str, '').line(array.line)
339
+ end
340
+
297
341
  result = s().line(array.line)
298
342
 
299
343
  join_value = if string? join_str
@@ -302,8 +346,10 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
302
346
  nil
303
347
  end
304
348
 
305
- array[1..-2].each do |e|
306
- result << join_item(e, join_value)
349
+ if array.length > 2
350
+ array[1..-2].each do |e|
351
+ result << join_item(e, join_value)
352
+ end
307
353
  end
308
354
 
309
355
  result << join_item(array.last, nil)
@@ -332,7 +378,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
332
378
  result.unshift combined_first
333
379
 
334
380
  # Have to fix up strings that follow interpolation
335
- result.reduce(s(:dstr).line(array.line)) do |memo, e|
381
+ string = result.reduce(s(:dstr).line(array.line)) do |memo, e|
336
382
  if string? e and node_type? memo.last, :evstr
337
383
  e.value = "#{join_value}#{e.value}"
338
384
  elsif join_value and node_type? memo.last, :evstr and node_type? e, :evstr
@@ -341,6 +387,14 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
341
387
 
342
388
  memo << e
343
389
  end
390
+
391
+ # Convert (:dstr, "hello world")
392
+ # to (:str, "hello world")
393
+ if string.length == 2 and string.last.is_a? String
394
+ string[0] = :str
395
+ end
396
+
397
+ string
344
398
  end
345
399
 
346
400
  def join_item item, join_value
@@ -749,6 +803,18 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
749
803
  exp
750
804
  end
751
805
 
806
+ def hash_or_array_include_all_literals? exp
807
+ return unless call? exp and sexp? exp.target
808
+ target = exp.target
809
+
810
+ case target.node_type
811
+ when :hash
812
+ hash_include_all_literals? exp
813
+ else
814
+ array_include_all_literals? exp
815
+ end
816
+ end
817
+
752
818
  # Check if exp is a call to Array#include? on an array literal
753
819
  # that contains all literal values. For example:
754
820
  #
@@ -767,6 +833,16 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
767
833
  (all_literals? exp.target or dir_glob? exp.target)
768
834
  end
769
835
 
836
+ # Check if exp is a call to Hash#include? on a hash literal
837
+ # that contains all literal values. For example:
838
+ #
839
+ # {x: 1}.include? x
840
+ def hash_include_all_literals? exp
841
+ call? exp and
842
+ exp.method == :include? and
843
+ all_literals? exp.target, :hash
844
+ end
845
+
770
846
  #Sets @inside_if = true
771
847
  def process_if exp
772
848
  if @ignore_ifs.nil?
@@ -807,7 +883,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
807
883
  scope do
808
884
  @branch_env = env.current
809
885
  branch_index = 2 + i # s(:if, condition, then_branch, else_branch)
810
- if i == 0 and array_include_all_literals? condition
886
+ if i == 0 and hash_or_array_include_all_literals? condition
811
887
  # If the condition is ["a", "b"].include? x
812
888
  # set x to "a" inside the true branch
813
889
  var = condition.first_arg
@@ -815,7 +891,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
815
891
  env.current[var] = safe_literal(var.line)
816
892
  exp[branch_index] = process_if_branch branch
817
893
  env.current[var] = previous_value
818
- elsif i == 1 and array_include_all_literals? condition and early_return? branch
894
+ elsif i == 1 and hash_or_array_include_all_literals? condition and early_return? branch
819
895
  var = condition.first_arg
820
896
  env.current[var] = safe_literal(var.line)
821
897
  exp[branch_index] = process_if_branch branch
@@ -1013,8 +1089,8 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
1013
1089
  method_name = call.method
1014
1090
 
1015
1091
  #Look for helper methods and see if we can get a return value
1016
- if found_method = find_method(method_name, @current_class)
1017
- helper = found_method[:method]
1092
+ if found_method = tracker.find_method(method_name, @current_class)
1093
+ helper = found_method.src
1018
1094
 
1019
1095
  if sexp? helper
1020
1096
  value = process_helper_method helper, call.args