brakeman-lib 5.0.0 → 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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +46 -0
  3. data/README.md +10 -1
  4. data/lib/brakeman.rb +23 -8
  5. data/lib/brakeman/checks/check_detailed_exceptions.rb +1 -1
  6. data/lib/brakeman/checks/check_evaluation.rb +1 -1
  7. data/lib/brakeman/checks/check_execute.rb +10 -0
  8. data/lib/brakeman/checks/check_mass_assignment.rb +4 -6
  9. data/lib/brakeman/checks/check_render.rb +15 -1
  10. data/lib/brakeman/checks/check_sanitize_methods.rb +2 -1
  11. data/lib/brakeman/checks/check_sql.rb +58 -8
  12. data/lib/brakeman/checks/check_verb_confusion.rb +1 -1
  13. data/lib/brakeman/commandline.rb +1 -1
  14. data/lib/brakeman/file_parser.rb +45 -15
  15. data/lib/brakeman/options.rb +7 -2
  16. data/lib/brakeman/parsers/template_parser.rb +24 -0
  17. data/lib/brakeman/processors/alias_processor.rb +105 -18
  18. data/lib/brakeman/processors/base_processor.rb +4 -4
  19. data/lib/brakeman/processors/controller_alias_processor.rb +6 -43
  20. data/lib/brakeman/processors/lib/call_conversion_helper.rb +10 -6
  21. data/lib/brakeman/processors/lib/rails4_config_processor.rb +2 -1
  22. data/lib/brakeman/processors/library_processor.rb +9 -0
  23. data/lib/brakeman/processors/model_processor.rb +31 -0
  24. data/lib/brakeman/report.rb +4 -1
  25. data/lib/brakeman/report/ignore/config.rb +4 -4
  26. data/lib/brakeman/report/ignore/interactive.rb +1 -1
  27. data/lib/brakeman/report/report_github.rb +31 -0
  28. data/lib/brakeman/report/report_sarif.rb +21 -2
  29. data/lib/brakeman/rescanner.rb +1 -1
  30. data/lib/brakeman/scanner.rb +4 -1
  31. data/lib/brakeman/tracker.rb +33 -4
  32. data/lib/brakeman/tracker/collection.rb +57 -7
  33. data/lib/brakeman/tracker/method_info.rb +70 -0
  34. data/lib/brakeman/util.rb +34 -18
  35. data/lib/brakeman/version.rb +1 -1
  36. data/lib/ruby_parser/bm_sexp.rb +14 -0
  37. metadata +18 -2
@@ -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
@@ -9,6 +9,7 @@ module Brakeman
9
9
  def initialize tracker, file_parser
10
10
  @tracker = tracker
11
11
  @file_parser = file_parser
12
+ @slim_smart = nil # Load slim/smart ?
12
13
  end
13
14
 
14
15
  def parse_template path, text
@@ -88,6 +89,14 @@ module Brakeman
88
89
 
89
90
  def parse_slim path, text
90
91
  Brakeman.load_brakeman_dependency 'slim'
92
+
93
+ if @slim_smart.nil? and load_slim_smart?
94
+ @slim_smart = true
95
+ Brakeman.load_brakeman_dependency 'slim/smart'
96
+ else
97
+ @slim_smart = false
98
+ end
99
+
91
100
  require_relative 'slim_embedded'
92
101
 
93
102
  Slim::Template.new(path,
@@ -95,6 +104,21 @@ module Brakeman
95
104
  :generator => Temple::Generators::RailsOutputBuffer) { text }.precompiled_template
96
105
  end
97
106
 
107
+ def load_slim_smart?
108
+ return !@slim_smart unless @slim_smart.nil?
109
+
110
+ # Terrible hack to find
111
+ # gem "slim", "~> 3.0.1", require: ["slim", "slim/smart"]
112
+ if tracker.app_tree.exists? 'Gemfile'
113
+ gemfile_contents = tracker.app_tree.file_path('Gemfile').read
114
+ if gemfile_contents.include? 'slim/smart'
115
+ return true
116
+ end
117
+ end
118
+
119
+ false
120
+ end
121
+
98
122
  def self.parse_inline_erb tracker, text
99
123
  fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
100
124
  tp = self.new(tracker, fp)
@@ -183,6 +183,12 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
183
183
  return exp
184
184
  end
185
185
 
186
+ # If x(*[1,2,3]) change to x(1,2,3)
187
+ # if that's the only argument
188
+ if splat_array? exp.first_arg and exp.second_arg.nil?
189
+ exp.arglist = exp.first_arg[1].sexp_body
190
+ end
191
+
186
192
  target = exp.target
187
193
  method = exp.method
188
194
  first_arg = exp.first_arg
@@ -195,11 +201,20 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
195
201
  res = process_or_simple_operation(exp)
196
202
  return res if res
197
203
  elsif target == ARRAY_CONST and method == :new
198
- return Sexp.new(:array, *exp.args)
204
+ return Sexp.new(:array, *exp.args).line(exp.line)
199
205
  elsif target == HASH_CONST and method == :new and first_arg.nil? and !node_type?(@exp_context.last, :iter)
200
- return Sexp.new(:hash)
206
+ return Sexp.new(:hash).line(exp.line)
201
207
  elsif exp == RAILS_TEST or exp == RAILS_DEV
202
- return Sexp.new(:false)
208
+ return Sexp.new(:false).line(exp.line)
209
+ end
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
203
218
  end
204
219
 
205
220
  #See if it is possible to simplify some basic cases
@@ -214,13 +229,28 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
214
229
  exp = math_op(:+, target, first_arg, exp)
215
230
  end
216
231
  when :-, :*, :/
217
- 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
218
239
  when :[]
219
240
  if array? target
220
241
  exp = process_array_access(target, exp.args, exp)
221
242
  elsif hash? target
222
243
  exp = process_hash_access(target, first_arg, exp)
223
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
224
254
  when :merge!, :update
225
255
  if hash? target and hash? first_arg
226
256
  target = process_hash_merge! target, first_arg
@@ -237,7 +267,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
237
267
  env[target_var] = target
238
268
  return target
239
269
  elsif string? target and string_interp? first_arg
240
- exp = Sexp.new(:dstr, target.value + first_arg[1]).concat(first_arg.sexp_body(2))
270
+ exp = Sexp.new(:dstr, target.value + first_arg[1]).concat(first_arg.sexp_body(2)).line(exp.line)
241
271
  env[target_var] = exp
242
272
  elsif string? first_arg and string_interp? target
243
273
  if string? target.last
@@ -260,6 +290,12 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
260
290
  target = find_push_target(target_var)
261
291
  env[target] = exp unless target.nil? # Happens in TemplateAliasProcessor
262
292
  end
293
+ when :push
294
+ if array? target
295
+ target << first_arg
296
+ env[target_var] = target
297
+ return target
298
+ end
263
299
  when :first
264
300
  if array? target and first_arg.nil? and sexp? target[1]
265
301
  exp = target[1]
@@ -273,7 +309,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
273
309
  exp = target
274
310
  end
275
311
  when :join
276
- 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?)
277
313
  exp = process_array_join(target, first_arg)
278
314
  end
279
315
  when :!
@@ -281,6 +317,15 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
281
317
  if call? target and target.method == :!
282
318
  exp = s(:or, s(:true).line(exp.line), s(:false).line(exp.line)).line(exp.line)
283
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
284
329
  end
285
330
 
286
331
  exp
@@ -288,7 +333,12 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
288
333
 
289
334
  # Painful conversion of Array#join into string interpolation
290
335
  def process_array_join array, join_str
291
- result = s()
336
+ # Empty array
337
+ if array.length == 1
338
+ return s(:str, '').line(array.line)
339
+ end
340
+
341
+ result = s().line(array.line)
292
342
 
293
343
  join_value = if string? join_str
294
344
  join_str.value
@@ -296,8 +346,10 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
296
346
  nil
297
347
  end
298
348
 
299
- array[1..-2].each do |e|
300
- 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
301
353
  end
302
354
 
303
355
  result << join_item(array.last, nil)
@@ -326,24 +378,32 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
326
378
  result.unshift combined_first
327
379
 
328
380
  # Have to fix up strings that follow interpolation
329
- result.reduce(s(:dstr)) do |memo, e|
381
+ string = result.reduce(s(:dstr).line(array.line)) do |memo, e|
330
382
  if string? e and node_type? memo.last, :evstr
331
383
  e.value = "#{join_value}#{e.value}"
332
384
  elsif join_value and node_type? memo.last, :evstr and node_type? e, :evstr
333
- memo << s(:str, join_value)
385
+ memo << s(:str, join_value).line(e.line)
334
386
  end
335
387
 
336
388
  memo << e
337
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
338
398
  end
339
399
 
340
400
  def join_item item, join_value
341
401
  if item.is_a? String
342
402
  "#{item}#{join_value}"
343
403
  elsif string? item or symbol? item or number? item
344
- s(:str, "#{item.value}#{join_value}")
404
+ s(:str, "#{item.value}#{join_value}").line(item.line)
345
405
  else
346
- s(:evstr, item)
406
+ s(:evstr, item).line(item.line)
347
407
  end
348
408
  end
349
409
 
@@ -359,6 +419,11 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
359
419
  s(:call, TEMP_FILE_CLASS, :new).line(line)
360
420
  end
361
421
 
422
+ def splat_array? exp
423
+ node_type? exp, :splat and
424
+ node_type? exp[1], :array
425
+ end
426
+
362
427
  def process_iter exp
363
428
  @exp_context.push exp
364
429
  exp[1] = process exp.block_call
@@ -679,7 +744,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
679
744
  end
680
745
  end
681
746
  else
682
- new_value = process s(:call, s(:call, target_var, :[], index), exp[3], value)
747
+ new_value = process s(:call, s(:call, target_var, :[], index), exp[3], value).line(exp.line)
683
748
 
684
749
  env[match] = new_value
685
750
  end
@@ -738,6 +803,18 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
738
803
  exp
739
804
  end
740
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
+
741
818
  # Check if exp is a call to Array#include? on an array literal
742
819
  # that contains all literal values. For example:
743
820
  #
@@ -756,6 +833,16 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
756
833
  (all_literals? exp.target or dir_glob? exp.target)
757
834
  end
758
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
+
759
846
  #Sets @inside_if = true
760
847
  def process_if exp
761
848
  if @ignore_ifs.nil?
@@ -796,7 +883,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
796
883
  scope do
797
884
  @branch_env = env.current
798
885
  branch_index = 2 + i # s(:if, condition, then_branch, else_branch)
799
- if i == 0 and array_include_all_literals? condition
886
+ if i == 0 and hash_or_array_include_all_literals? condition
800
887
  # If the condition is ["a", "b"].include? x
801
888
  # set x to "a" inside the true branch
802
889
  var = condition.first_arg
@@ -804,7 +891,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
804
891
  env.current[var] = safe_literal(var.line)
805
892
  exp[branch_index] = process_if_branch branch
806
893
  env.current[var] = previous_value
807
- 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
808
895
  var = condition.first_arg
809
896
  env.current[var] = safe_literal(var.line)
810
897
  exp[branch_index] = process_if_branch branch
@@ -1002,8 +1089,8 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
1002
1089
  method_name = call.method
1003
1090
 
1004
1091
  #Look for helper methods and see if we can get a return value
1005
- if found_method = find_method(method_name, @current_class)
1006
- helper = found_method[:method]
1092
+ if found_method = tracker.find_method(method_name, @current_class)
1093
+ helper = found_method.src
1007
1094
 
1008
1095
  if sexp? helper
1009
1096
  value = process_helper_method helper, call.args
@@ -8,7 +8,7 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
8
8
  include Brakeman::SafeCallHelper
9
9
  include Brakeman::Util
10
10
 
11
- IGNORE = Sexp.new :ignore
11
+ IGNORE = Sexp.new(:ignore).line(0)
12
12
 
13
13
  #Return a new Processor.
14
14
  def initialize tracker
@@ -216,7 +216,7 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
216
216
  #
217
217
  #And also :layout for inside templates
218
218
  def find_render_type call, in_view = false
219
- rest = Sexp.new(:hash)
219
+ rest = Sexp.new(:hash).line(call.line)
220
220
  type = nil
221
221
  value = nil
222
222
  first_arg = call.first_arg
@@ -236,7 +236,7 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
236
236
  end
237
237
  elsif first_arg.is_a? Symbol or first_arg.is_a? String
238
238
  type = :action
239
- value = Sexp.new(:lit, first_arg.to_sym)
239
+ value = Sexp.new(:lit, first_arg.to_sym).line(call.line)
240
240
  elsif first_arg.nil?
241
241
  type = :default
242
242
  elsif not hash? first_arg
@@ -293,6 +293,6 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
293
293
  @tracker.processor.process_template(template_name, ast, type, nil, @current_file)
294
294
  @tracker.processor.process_template_alias(@tracker.templates[template_name])
295
295
 
296
- return s(:lit, template_name), options
296
+ return s(:lit, template_name).line(value.line), options
297
297
  end
298
298
  end
@@ -51,7 +51,7 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
51
51
  #Need to process the method like it was in a controller in order
52
52
  #to get the renders set
53
53
  processor = Brakeman::ControllerProcessor.new(@tracker, mixin.file)
54
- method = mixin.get_method(name)[:src].deep_clone
54
+ method = mixin.get_method(name).src.deep_clone
55
55
 
56
56
  if node_type? method, :defn
57
57
  method = processor.process_defn method
@@ -143,16 +143,16 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
143
143
  #Basically, adds any instance variable assignments to the environment.
144
144
  #TODO: method arguments?
145
145
  def process_before_filter name
146
- filter = find_method name, @current_class
146
+ filter = tracker.find_method name, @current_class
147
147
 
148
148
  if filter.nil?
149
149
  Brakeman.debug "[Notice] Could not find filter #{name}"
150
150
  return
151
151
  end
152
152
 
153
- method = filter[:method]
153
+ method = filter.src
154
154
 
155
- if ivars = @tracker.filter_cache[[filter[:controller], name]]
155
+ if ivars = @tracker.filter_cache[[filter.owner, name]]
156
156
  ivars.each do |variable, value|
157
157
  env[variable] = value
158
158
  end
@@ -162,7 +162,7 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
162
162
 
163
163
  ivars = processor.only_ivars(:include_request_vars).all
164
164
 
165
- @tracker.filter_cache[[filter[:controller], name]] = ivars
165
+ @tracker.filter_cache[[filter.owner, name]] = ivars
166
166
 
167
167
  ivars.each do |variable, value|
168
168
  env[variable] = value
@@ -182,7 +182,7 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
182
182
  # method as the line number
183
183
  if line.nil? and controller = @tracker.controllers[@current_class]
184
184
  if meth = controller.get_method(@current_method)
185
- if line = meth[:src] && meth[:src].last && meth[:src].last.line
185
+ if line = meth.src && meth.src.last && meth.src.last.line
186
186
  line += 1
187
187
  else
188
188
  line = 1
@@ -241,41 +241,4 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
241
241
  []
242
242
  end
243
243
  end
244
-
245
- #Finds a method in the given class or a parent class
246
- #
247
- #Returns nil if the method could not be found.
248
- #
249
- #If found, returns hash table with controller name and method sexp.
250
- def find_method method_name, klass
251
- return nil if sexp? method_name
252
- method_name = method_name.to_sym
253
-
254
- if method = @method_cache[method_name]
255
- return method
256
- end
257
-
258
- controller = @tracker.controllers[klass]
259
- controller ||= @tracker.libs[klass]
260
-
261
- if klass and controller
262
- method = controller.get_method method_name
263
-
264
- if method.nil?
265
- controller.includes.each do |included|
266
- method = find_method method_name, included
267
- if method
268
- @method_cache[method_name] = method
269
- return method
270
- end
271
- end
272
-
273
- @method_cache[method_name] = find_method method_name, controller.parent
274
- else
275
- @method_cache[method_name] = { :controller => controller.name, :method => method[:src] }
276
- end
277
- else
278
- nil
279
- end
280
- end
281
244
  end