brakeman 5.0.1 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +41 -0
  3. data/bundle/load.rb +3 -2
  4. data/bundle/ruby/2.7.0/gems/parallel-1.20.1/MIT-LICENSE.txt +20 -0
  5. data/bundle/ruby/2.7.0/gems/parallel-1.20.1/lib/parallel.rb +523 -0
  6. data/bundle/ruby/2.7.0/gems/parallel-1.20.1/lib/parallel/processor_count.rb +42 -0
  7. data/bundle/ruby/2.7.0/gems/parallel-1.20.1/lib/parallel/version.rb +3 -0
  8. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/History.rdoc +19 -0
  9. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/Manifest.txt +2 -0
  10. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/README.rdoc +0 -0
  11. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/compare/normalize.rb +2 -2
  12. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/debugging.md +0 -0
  13. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/rp_extensions.rb +0 -0
  14. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/rp_stringscanner.rb +0 -0
  15. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby20_parser.rb +0 -0
  16. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby20_parser.y +0 -0
  17. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby21_parser.rb +0 -0
  18. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby21_parser.y +0 -0
  19. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby22_parser.rb +0 -0
  20. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby22_parser.y +0 -0
  21. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby23_parser.rb +0 -0
  22. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby23_parser.y +0 -0
  23. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby24_parser.rb +0 -0
  24. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby24_parser.y +0 -0
  25. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby25_parser.rb +0 -0
  26. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby25_parser.y +0 -0
  27. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby26_parser.rb +0 -0
  28. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby26_parser.y +0 -0
  29. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby27_parser.rb +0 -0
  30. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby27_parser.y +0 -0
  31. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby30_parser.rb +7358 -0
  32. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby30_parser.y +2703 -0
  33. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby_lexer.rb +0 -0
  34. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby_lexer.rex +0 -0
  35. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby_lexer.rex.rb +0 -0
  36. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby_parser.rb +2 -0
  37. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby_parser.yy +2 -0
  38. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/lib/ruby_parser_extras.rb +2 -2
  39. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/tools/munge.rb +0 -0
  40. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.1 → ruby_parser-3.16.0}/tools/ripper.rb +0 -0
  41. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.2 → sexp_processor-4.15.3}/History.rdoc +6 -0
  42. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.2 → sexp_processor-4.15.3}/Manifest.txt +0 -0
  43. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.2 → sexp_processor-4.15.3}/README.rdoc +0 -0
  44. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.2 → sexp_processor-4.15.3}/lib/composite_sexp_processor.rb +0 -0
  45. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.2 → sexp_processor-4.15.3}/lib/pt_testcase.rb +2 -2
  46. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.2 → sexp_processor-4.15.3}/lib/sexp.rb +0 -0
  47. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.2 → sexp_processor-4.15.3}/lib/sexp_matcher.rb +0 -0
  48. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.2 → sexp_processor-4.15.3}/lib/sexp_processor.rb +1 -1
  49. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.2 → sexp_processor-4.15.3}/lib/strict_sexp.rb +0 -0
  50. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.2 → sexp_processor-4.15.3}/lib/unique.rb +0 -0
  51. data/lib/brakeman.rb +6 -0
  52. data/lib/brakeman/checks/check_detailed_exceptions.rb +1 -1
  53. data/lib/brakeman/checks/check_evaluation.rb +1 -1
  54. data/lib/brakeman/checks/check_execute.rb +10 -0
  55. data/lib/brakeman/checks/check_render.rb +15 -1
  56. data/lib/brakeman/checks/check_sanitize_methods.rb +2 -1
  57. data/lib/brakeman/checks/check_sql.rb +58 -8
  58. data/lib/brakeman/checks/check_verb_confusion.rb +1 -1
  59. data/lib/brakeman/file_parser.rb +45 -15
  60. data/lib/brakeman/options.rb +7 -2
  61. data/lib/brakeman/processors/alias_processor.rb +85 -9
  62. data/lib/brakeman/processors/controller_alias_processor.rb +6 -43
  63. data/lib/brakeman/processors/lib/call_conversion_helper.rb +10 -6
  64. data/lib/brakeman/processors/library_processor.rb +9 -0
  65. data/lib/brakeman/processors/model_processor.rb +31 -0
  66. data/lib/brakeman/report.rb +4 -1
  67. data/lib/brakeman/report/ignore/interactive.rb +1 -1
  68. data/lib/brakeman/report/report_github.rb +31 -0
  69. data/lib/brakeman/report/report_sarif.rb +21 -2
  70. data/lib/brakeman/rescanner.rb +1 -1
  71. data/lib/brakeman/scanner.rb +4 -1
  72. data/lib/brakeman/tracker.rb +33 -4
  73. data/lib/brakeman/tracker/collection.rb +57 -7
  74. data/lib/brakeman/tracker/method_info.rb +70 -0
  75. data/lib/brakeman/util.rb +34 -18
  76. data/lib/brakeman/version.rb +1 -1
  77. data/lib/ruby_parser/bm_sexp.rb +14 -0
  78. metadata +51 -43
@@ -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
@@ -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
@@ -1,11 +1,5 @@
1
1
  module Brakeman
2
2
  module CallConversionHelper
3
- def all_literals? exp, expected_type = :array
4
- node_type? exp, expected_type and
5
- exp.length > 1 and
6
- exp.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
7
- end
8
-
9
3
  # Join two array literals into one.
10
4
  def join_arrays lhs, rhs, original_exp = nil
11
5
  if array? lhs and array? rhs
@@ -76,6 +70,8 @@ module Brakeman
76
70
 
77
71
  #Have to do this because first element is :array and we have to skip it
78
72
  array[1..-1][index] or original_exp
73
+ elsif all_literals? array
74
+ safe_literal(array.line)
79
75
  else
80
76
  original_exp
81
77
  end
@@ -92,5 +88,13 @@ module Brakeman
92
88
  original_exp
93
89
  end
94
90
  end
91
+
92
+ def hash_values_at hash, keys
93
+ values = keys.map do |key|
94
+ process_hash_access hash, key
95
+ end
96
+
97
+ Sexp.new(:array).concat(values).line(hash.line)
98
+ end
95
99
  end
96
100
  end
@@ -54,6 +54,15 @@ class Brakeman::LibraryProcessor < Brakeman::BaseProcessor
54
54
 
55
55
  def process_call exp
56
56
  if process_call_defn? exp
57
+ exp
58
+ elsif @current_method.nil? and exp.target.nil? and (@current_class or @current_module)
59
+ # Methods called inside class / module
60
+ case exp.method
61
+ when :include
62
+ module_name = class_name(exp.first_arg)
63
+ (@current_class || @current_module).add_include module_name
64
+ end
65
+
57
66
  exp
58
67
  else
59
68
  process_default exp
@@ -73,6 +73,8 @@ class Brakeman::ModelProcessor < Brakeman::BaseProcessor
73
73
  @current_class.set_attr_accessible exp
74
74
  when :attr_protected
75
75
  @current_class.set_attr_protected exp
76
+ when :enum
77
+ add_enum_method exp
76
78
  else
77
79
  if @current_class
78
80
  @current_class.add_option method, exp
@@ -87,4 +89,33 @@ class Brakeman::ModelProcessor < Brakeman::BaseProcessor
87
89
  call
88
90
  end
89
91
  end
92
+
93
+ def add_enum_method call
94
+ arg = call.first_arg
95
+ return unless hash? arg
96
+
97
+ enum_name = arg[1].value # first key
98
+ enums = arg[2] # first value
99
+ enums_name = pluralize(enum_name.to_s).to_sym
100
+
101
+ call_line = call.line
102
+
103
+ if hash? enums
104
+ enum_values = enums
105
+ elsif array? enums
106
+ # Build hash for enum values like Rails does
107
+ enum_values = s(:hash).line(call_line)
108
+
109
+ enums.each_sexp.with_index do |v, index|
110
+ enum_values << v
111
+ enum_values << s(:lit, index).line(call_line)
112
+ end
113
+ end
114
+
115
+ enum_method = s(:defn, enum_name, s(:args), safe_literal(call_line))
116
+ enums_method = s(:defs, s(:self), enums_name, s(:args), enum_values)
117
+
118
+ @current_class.add_method :public, enum_name, enum_method, @current_file
119
+ @current_class.add_method :public, enums_name, enums_method, @current_file
120
+ end
90
121
  end
@@ -6,7 +6,7 @@ require 'brakeman/report/report_base'
6
6
  class Brakeman::Report
7
7
  attr_reader :tracker
8
8
 
9
- VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s, :to_markdown, :to_codeclimate, :to_plain, :to_text, :to_junit]
9
+ VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s, :to_markdown, :to_codeclimate, :to_plain, :to_text, :to_junit, :to_github]
10
10
 
11
11
  def initialize tracker
12
12
  @app_tree = tracker.app_tree
@@ -48,6 +48,9 @@ class Brakeman::Report
48
48
  when :to_sonar
49
49
  require_report 'sonar'
50
50
  Brakeman::Report::Sonar
51
+ when :to_github
52
+ require_report 'github'
53
+ Brakeman::Report::Github
51
54
  else
52
55
  raise "Invalid format: #{format}. Should be one of #{VALID_FORMATS.inspect}"
53
56
  end
@@ -62,7 +62,7 @@ module Brakeman
62
62
  process_warnings
63
63
  end
64
64
 
65
- m.choice "Hide previously ignored warnings" do
65
+ m.choice "Inspect new warnings" do
66
66
  @skip_ignored = true
67
67
  pre_show_help
68
68
  process_warnings
@@ -0,0 +1,31 @@
1
+ # Github Actions Formatter
2
+ # Formats warnings as workflow commands to create annotations in GitHub UI
3
+ class Brakeman::Report::Github < Brakeman::Report::Base
4
+ def generate_report
5
+ # @see https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message
6
+ errors.concat(warnings).join("\n")
7
+ end
8
+
9
+ def warnings
10
+ all_warnings
11
+ .map { |warning| "::warning file=#{warning_file(warning)},line=#{warning.line}::#{warning.message}" }
12
+ end
13
+
14
+ def errors
15
+ tracker.errors.map do |error|
16
+ if error[:exception].is_a?(Racc::ParseError)
17
+ # app/services/balance.rb:4 :: parse error on value "..." (tDOT3)
18
+ file, line = error[:exception].message.split(':').map(&:strip)[0,2]
19
+ "::error file=#{file},line=#{line}::#{clean_message(error[:error])}"
20
+ else
21
+ "::error ::#{clean_message(error[:error])}"
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def clean_message(msg)
29
+ msg.gsub('::','').squeeze(' ')
30
+ end
31
+ end
@@ -48,7 +48,7 @@ class Brakeman::Report::SARIF < Brakeman::Report::Base
48
48
  end
49
49
 
50
50
  def results
51
- @results ||= all_warnings.map do |warning|
51
+ @results ||= tracker.checks.all_warnings.map do |warning|
52
52
  rule_id = render_id warning
53
53
  result_level = infer_level warning
54
54
  message_text = render_message warning.message.to_s
@@ -72,6 +72,23 @@ class Brakeman::Report::SARIF < Brakeman::Report::Base
72
72
  ],
73
73
  }
74
74
 
75
+ if @ignore_filter && @ignore_filter.ignored?(warning)
76
+ result[:suppressions] = [
77
+ {
78
+ :kind => 'external',
79
+ :justification => @ignore_filter.note_for(warning),
80
+ :location => {
81
+ :physicalLocation => {
82
+ :artifactLocation => {
83
+ :uri => Brakeman::FilePath.from_app_tree(@app_tree, @ignore_filter.file).relative,
84
+ :uriBaseId => '%SRCROOT%',
85
+ },
86
+ },
87
+ },
88
+ }
89
+ ]
90
+ end
91
+
75
92
  result
76
93
  end
77
94
  end
@@ -85,7 +102,7 @@ class Brakeman::Report::SARIF < Brakeman::Report::Base
85
102
 
86
103
  # Returns a de-duplicated set of warnings, used to generate rules
87
104
  def unique_warnings_by_warning_code
88
- @unique_warnings_by_warning_code ||= all_warnings.uniq { |w| w.warning_code }
105
+ @unique_warnings_by_warning_code ||= tracker.checks.all_warnings.uniq { |w| w.warning_code }
89
106
  end
90
107
 
91
108
  def render_id warning
@@ -94,6 +111,8 @@ class Brakeman::Report::SARIF < Brakeman::Report::Base
94
111
  end
95
112
 
96
113
  def render_message message
114
+ return message if message.nil?
115
+
97
116
  # Ensure message ends with a period
98
117
  if message.end_with? "."
99
118
  message