brakeman-lib 5.0.2 → 5.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d16867dd5c48de9ec2975e1dc420e3a5154939361988d70c4217f251881452ed
4
- data.tar.gz: f5cae624d83a1298fb3f07108c76d1f55c756404d6998fccfd9e5fcfe69a068e
3
+ metadata.gz: 650df7997ecbf4c9bf7c1ea47ef851ec2eac1593d0c9fed8197ca5aa78f8fded
4
+ data.tar.gz: 9582c10b7cd30496793d5d1b3ddbd88fbf610fa834bdab402267c4ad73962622
5
5
  SHA512:
6
- metadata.gz: f8724b266165ef9ed4ad926432e0786b955cb2e98b56e7100354b0ad04a51cc0eaa139343a769980852ae615bd209e8854f6f83616b0703d3a2aaf08229860c6
7
- data.tar.gz: 963f46a856d6f943c74c6aca8ec3f3dc61ae3d82758fbfdda63bb4fc789ff95535f49cc73f9abef4cf1553323606d4fd50c8a03082178a6dd3cea0883e544a40
6
+ metadata.gz: bdcc242df0b6e60ba87e1d4445c56bf7ed6c2c2a0dfdd34904fc41369f9b05ed9e370a1c2cf80a6d45d9b1cedcc1f1e56600b96f47437a9ffb6f343a01c41385
7
+ data.tar.gz: d623285512f64799f9e230289f6c864bcb937770d781a974c1c4ac224ff1a89ac104fd0fc4fcc98c2b840ea68b5f8ddf1b67781b85cbc7257099995848b8f9ef
data/CHANGES.md CHANGED
@@ -1,3 +1,48 @@
1
+ # 5.1.2 - 2021-10-28
2
+
3
+ * Handle cases where enums are not symbols
4
+ * Support newer Haml with ::Haml::AttributeBuilder.build
5
+ * Fix issue where the previous output is still visible (Jason Frey)
6
+ * Fix warning sorting with nil line numbers
7
+ * Update for latest RubyParser (Ryan Davis)
8
+
9
+ # 5.1.1 - 2021-07-19
10
+
11
+ * Unrefactor IgnoreConfig's use of `Brakeman::FilePath`
12
+
13
+ # 5.1.0 - 2021-07-19
14
+
15
+ * Initial support for ActiveRecord enums
16
+ * Support `Hash#include?`
17
+ * Interprocedural dataflow from very simple class methods
18
+ * Fix SARIF report when checks have no description (Eli Block)
19
+ * Add ignored warnings to SARIF report (Eli Block)
20
+ * Add `--sql-safe-methods` option (Esty Scheiner)
21
+ * Update SQL injection check for Rails 6.0/6.1
22
+ * Fix false positive in command injection with `Open3.capture` (Richard Fitzgerald)
23
+ * Fix infinite loop on mixin self-includes (Andrew Szczepanski)
24
+ * Ignore dates in SQL
25
+ * Refactor `cookie?`/`param?` methods (Keenan Brock)
26
+ * Ignore renderables in dynamic render path check (Brad Parker)
27
+ * Support `Array#push`
28
+ * Better `Array#join` support
29
+ * Adjust copy of `--interactive` menu (Elia Schito)
30
+ * Support `Array#*`
31
+ * Better method definition tracking and lookup
32
+ * Support `Hash#values` and `Hash#values_at`
33
+ * Check for user-controlled evaluation even if it's a call target
34
+ * Support `Array#fetch` and `Hash#fetch`
35
+ * Ignore `sanitize_sql_like` in SQL
36
+ * Ignore method calls on numbers in SQL
37
+ * Add GitHub Actions format (Klaus Badelt)
38
+ * Read and parse files in parallel
39
+
40
+ # 5.0.4 - 2021-06-08
41
+
42
+ (brakeman gem release only)
43
+
44
+ * Update bundled `ruby_parser` to include argument forwarding support
45
+
1
46
  # 5.0.2 - 2021-06-07
2
47
 
3
48
  * Fix Loofah version check
@@ -412,7 +457,7 @@
412
457
  * Delay loading vendored gems and modifying load path
413
458
  * Avoid warning about SQL injection with `quoted_primary_key`
414
459
  * Support more safe `&.` operations
415
- * Allow multile line regex in `validates_format_of` (Dmitrij Fedorenko)
460
+ * Allow multiple line regex in `validates_format_of` (Dmitrij Fedorenko)
416
461
  * Only consider `if` branches in templates
417
462
  * Avoid overwriting instance/class methods with same name (Tim Wade)
418
463
  * Add `--force-scan` option (Neil Matatall)
data/README.md CHANGED
@@ -66,7 +66,7 @@ Outside of Rails root (note that the output file is relative to path/to/rails/ap
66
66
 
67
67
  Brakeman should work with any version of Rails from 2.3.x to 6.x.
68
68
 
69
- Brakeman can analyze code written with Ruby 1.8 syntax and newer, but requires at least Ruby 2.3.0 to run.
69
+ Brakeman can analyze code written with Ruby 1.8 syntax and newer, but requires at least Ruby 2.4.0 to run.
70
70
 
71
71
  # Basic Options
72
72
 
@@ -28,7 +28,7 @@ module Brakeman
28
28
  # Accepts an array of filenames and paths with the following format and
29
29
  # returns a Regexp to match them:
30
30
  # * "path1/file1.rb" - Matches a specific filename in the project directory.
31
- # * "path1/" - Matches any path that conatains "path1" in the project directory.
31
+ # * "path1/" - Matches any path that contains "path1" in the project directory.
32
32
  # * "/path1/ - Matches any path that is rooted at "path1" in the project directory.
33
33
  #
34
34
  def self.regex_for_paths(paths)
@@ -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) ||
@@ -74,7 +74,7 @@ class Brakeman::CheckJSONParsing < Brakeman::BaseCheck
74
74
  warning_type = "Denial of Service"
75
75
  confidence = :medium
76
76
  gem_name = "#{name} gem"
77
- message = msg(msg_version(version, gem_name), " has a symbol creation vulnerablity. Upgrade to ")
77
+ message = msg(msg_version(version, gem_name), " has a symbol creation vulnerability. Upgrade to ")
78
78
 
79
79
  if version >= "1.7.0"
80
80
  confidence = :high
@@ -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
@@ -579,6 +591,10 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
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,11 +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" or
596
- number_target? exp
608
+ ignore_call? exp
597
609
  end
598
610
  when :if
599
611
  safe_value? exp.then_clause and safe_value? exp.else_clause
@@ -608,6 +620,17 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
608
620
  end
609
621
  end
610
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
+
611
634
  QUOTE_METHODS = [:quote, :quote_column_name, :quoted_date, :quote_string, :quote_table_name]
612
635
 
613
636
  def quote_call? exp
@@ -708,4 +731,18 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
708
731
  false
709
732
  end
710
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
711
748
  end
@@ -7,14 +7,22 @@ module Brakeman
7
7
  class FileParser
8
8
  attr_reader :file_list, :errors
9
9
 
10
- def initialize app_tree, timeout
10
+ def initialize app_tree, timeout, parallel = true
11
11
  @app_tree = app_tree
12
12
  @timeout = timeout
13
13
  @file_list = []
14
14
  @errors = []
15
+ @parallel = parallel
15
16
  end
16
17
 
17
18
  def parse_files list
19
+ if @parallel
20
+ parallel_options = {}
21
+ else
22
+ # Disable parallelism
23
+ parallel_options = { in_threads: 0 }
24
+ end
25
+
18
26
  # Parse the files in parallel.
19
27
  # By default, the parsing will be in separate processes.
20
28
  # So we map the result to ASTFiles and/or Exceptions
@@ -25,7 +33,7 @@ module Brakeman
25
33
  # return types that are returned from isolated processes.
26
34
  #
27
35
  # Note this method no longer uses read_files
28
- @file_list, new_errors = Parallel.map(list) do |file_name|
36
+ @file_list, new_errors = Parallel.map(list, parallel_options) do |file_name|
29
37
  file_path = @app_tree.file_path(file_name)
30
38
  contents = file_path.read
31
39
 
@@ -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 }
@@ -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
@@ -314,8 +323,14 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
314
323
  exp = hash_values(target)
315
324
  end
316
325
  when :values_at
317
- if hash? target
318
- exp = hash_values_at target, exp.args
326
+ if node_type? target, :hash
327
+ res = hash_values_at target, exp.args
328
+
329
+ # Only convert to array of values if _all_ keys
330
+ # are present in the hash.
331
+ unless res.any?(&:nil?)
332
+ exp = res
333
+ end
319
334
  end
320
335
  end
321
336
 
@@ -794,6 +809,18 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
794
809
  exp
795
810
  end
796
811
 
812
+ def hash_or_array_include_all_literals? exp
813
+ return unless call? exp and sexp? exp.target
814
+ target = exp.target
815
+
816
+ case target.node_type
817
+ when :hash
818
+ hash_include_all_literals? exp
819
+ else
820
+ array_include_all_literals? exp
821
+ end
822
+ end
823
+
797
824
  # Check if exp is a call to Array#include? on an array literal
798
825
  # that contains all literal values. For example:
799
826
  #
@@ -812,6 +839,16 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
812
839
  (all_literals? exp.target or dir_glob? exp.target)
813
840
  end
814
841
 
842
+ # Check if exp is a call to Hash#include? on a hash literal
843
+ # that contains all literal values. For example:
844
+ #
845
+ # {x: 1}.include? x
846
+ def hash_include_all_literals? exp
847
+ call? exp and
848
+ exp.method == :include? and
849
+ all_literals? exp.target, :hash
850
+ end
851
+
815
852
  #Sets @inside_if = true
816
853
  def process_if exp
817
854
  if @ignore_ifs.nil?
@@ -852,7 +889,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
852
889
  scope do
853
890
  @branch_env = env.current
854
891
  branch_index = 2 + i # s(:if, condition, then_branch, else_branch)
855
- if i == 0 and array_include_all_literals? condition
892
+ if i == 0 and hash_or_array_include_all_literals? condition
856
893
  # If the condition is ["a", "b"].include? x
857
894
  # set x to "a" inside the true branch
858
895
  var = condition.first_arg
@@ -860,7 +897,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
860
897
  env.current[var] = safe_literal(var.line)
861
898
  exp[branch_index] = process_if_branch branch
862
899
  env.current[var] = previous_value
863
- elsif i == 1 and array_include_all_literals? condition and early_return? branch
900
+ elsif i == 1 and hash_or_array_include_all_literals? condition and early_return? branch
864
901
  var = condition.first_arg
865
902
  env.current[var] = safe_literal(var.line)
866
903
  exp[branch_index] = process_if_branch branch
@@ -8,6 +8,7 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor
8
8
  HAML_HELPERS2 = s(:colon2, s(:colon3, :Haml), :Helpers)
9
9
  JAVASCRIPT_FILTER = s(:colon2, s(:colon2, s(:const, :Haml), :Filters), :Javascript)
10
10
  COFFEE_FILTER = s(:colon2, s(:colon2, s(:const, :Haml), :Filters), :Coffee)
11
+ ATTRIBUTE_BUILDER = s(:colon2, s(:colon3, :Haml), :AttributeBuilder)
11
12
 
12
13
  def initialize *args
13
14
  super
@@ -133,6 +134,8 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor
133
134
 
134
135
  get_pushed_value(exp.first_arg, default)
135
136
  @javascript = false
137
+ elsif haml_attribute_builder? exp
138
+ ignore # probably safe... seems escaped by default?
136
139
  else
137
140
  add_output exp, default
138
141
  end
@@ -154,6 +157,12 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor
154
157
  exp.method == :attributes
155
158
  end
156
159
 
160
+ def haml_attribute_builder? exp
161
+ call? exp and
162
+ exp.target == ATTRIBUTE_BUILDER and
163
+ exp.method == :build
164
+ end
165
+
157
166
  def fix_textareas? exp
158
167
  call? exp and
159
168
  exp.target == HAMLOUT and
@@ -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
@@ -95,6 +89,8 @@ module Brakeman
95
89
  end
96
90
  end
97
91
 
92
+ # You must check the return value for `nil`s -
93
+ # which indicate a key could not be found.
98
94
  def hash_values_at hash, keys
99
95
  values = keys.map do |key|
100
96
  process_hash_access hash, key
@@ -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,34 @@ 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
+ return unless symbol? arg[1]
97
+
98
+ enum_name = arg[1].value # first key
99
+ enums = arg[2] # first value
100
+ enums_name = pluralize(enum_name.to_s).to_sym
101
+
102
+ call_line = call.line
103
+
104
+ if hash? enums
105
+ enum_values = enums
106
+ elsif array? enums
107
+ # Build hash for enum values like Rails does
108
+ enum_values = s(:hash).line(call_line)
109
+
110
+ enums.each_sexp.with_index do |v, index|
111
+ enum_values << v
112
+ enum_values << s(:lit, index).line(call_line)
113
+ end
114
+ end
115
+
116
+ enum_method = s(:defn, enum_name, s(:args), safe_literal(call_line))
117
+ enums_method = s(:defs, s(:self), enums_name, s(:args), enum_values)
118
+
119
+ @current_class.add_method :public, enum_name, enum_method, @current_file
120
+ @current_class.add_method :public, enums_name, enums_method, @current_file
121
+ end
90
122
  end
@@ -126,7 +126,7 @@ module Brakeman
126
126
 
127
127
  w[:note] = @notes[w[:fingerprint]] || ""
128
128
  w
129
- end.sort_by { |w| [w[:fingerprint], w[:line]] }
129
+ end.sort_by { |w| [w[:fingerprint], w[:line] || 0] }
130
130
 
131
131
  output = {
132
132
  :ignored_warnings => warnings,
@@ -17,7 +17,7 @@ class Brakeman::Report::CSV < Brakeman::Report::Base
17
17
  ]
18
18
 
19
19
  rows = tracker.filtered_warnings.sort_by do |w|
20
- [w.confidence, w.warning_type, w.file, w.line, w.fingerprint]
20
+ [w.confidence, w.warning_type, w.file, w.line || 0, w.fingerprint]
21
21
  end.map do |warning|
22
22
  generate_row(headers, warning)
23
23
  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,11 +72,28 @@ 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
78
95
 
79
- # Returns a hash of all check descriptions, keyed by check namne
96
+ # Returns a hash of all check descriptions, keyed by check name
80
97
  def check_descriptions
81
98
  @check_descriptions ||= Brakeman::Checks.checks.map do |check|
82
99
  [check.name.gsub(/^Check/, ''), check.description]
@@ -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
@@ -92,7 +92,7 @@ class Brakeman::Report::Text < Brakeman::Report::Base
92
92
  HighLine.color("No warnings found", :bold, :green)
93
93
  else
94
94
  warnings = tracker.filtered_warnings.sort_by do |w|
95
- [w.confidence, w.warning_type, w.file, w.line, w.fingerprint]
95
+ [w.confidence, w.warning_type, w.file, w.line || 0, w.fingerprint]
96
96
  end.map do |w|
97
97
  output_warning w
98
98
  end
@@ -391,7 +391,7 @@ class Brakeman::Rescanner < Brakeman::Scanner
391
391
 
392
392
  def parse_ruby_files list
393
393
  paths = list.select(&:exists?)
394
- file_parser = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
394
+ file_parser = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout], tracker.options[:parallel_checks])
395
395
  file_parser.parse_files paths
396
396
  tracker.add_errors(file_parser.errors)
397
397
  file_parser.file_list
@@ -40,38 +40,38 @@ class Brakeman::Scanner
40
40
 
41
41
  #Process everything in the Rails application
42
42
  def process
43
- Brakeman.notify "Processing gems..."
43
+ Brakeman.notify "Processing gems... "
44
44
  process_gems
45
45
  guess_rails_version
46
- Brakeman.notify "Processing configuration..."
46
+ Brakeman.notify "Processing configuration... "
47
47
  process_config
48
- Brakeman.notify "Parsing files..."
48
+ Brakeman.notify "Parsing files... "
49
49
  parse_files
50
- Brakeman.notify "Detecting file types..."
50
+ Brakeman.notify "Detecting file types... "
51
51
  detect_file_types
52
- Brakeman.notify "Processing initializers..."
52
+ Brakeman.notify "Processing initializers... "
53
53
  process_initializers
54
- Brakeman.notify "Processing libs..."
54
+ Brakeman.notify "Processing libs... "
55
55
  process_libs
56
- Brakeman.notify "Processing routes... "
56
+ Brakeman.notify "Processing routes... "
57
57
  process_routes
58
- Brakeman.notify "Processing templates... "
58
+ Brakeman.notify "Processing templates... "
59
59
  process_templates
60
- Brakeman.notify "Processing data flow in templates..."
60
+ Brakeman.notify "Processing data flow in templates... "
61
61
  process_template_data_flows
62
- Brakeman.notify "Processing models... "
62
+ Brakeman.notify "Processing models... "
63
63
  process_models
64
- Brakeman.notify "Processing controllers... "
64
+ Brakeman.notify "Processing controllers... "
65
65
  process_controllers
66
66
  Brakeman.notify "Processing data flow in controllers..."
67
67
  process_controller_data_flows
68
- Brakeman.notify "Indexing call sites... "
68
+ Brakeman.notify "Indexing call sites... "
69
69
  index_call_sites
70
70
  tracker
71
71
  end
72
72
 
73
73
  def parse_files
74
- fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
74
+ fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout], tracker.options[:parallel_checks])
75
75
 
76
76
  fp.parse_files tracker.app_tree.ruby_file_paths
77
77
 
@@ -15,6 +15,7 @@ module Brakeman
15
15
  @includes = []
16
16
  @methods = { :public => {}, :private => {}, :protected => {} }
17
17
  @class_methods = {}
18
+ @simple_methods = { :class => {}, instance: {} }
18
19
  @options = {}
19
20
  @tracker = tracker
20
21
 
@@ -24,7 +25,7 @@ module Brakeman
24
25
  def ancestor? parent, seen={}
25
26
  seen[self.name] = true
26
27
 
27
- if self.parent == parent or seen[self.parent]
28
+ if self.parent == parent or self.name == parent or seen[self.parent]
28
29
  true
29
30
  elsif parent_model = collection[self.parent]
30
31
  parent_model.ancestor? parent, seen
@@ -39,7 +40,7 @@ module Brakeman
39
40
  end
40
41
 
41
42
  def add_include class_name
42
- @includes << class_name
43
+ @includes << class_name unless ancestor?(class_name)
43
44
  end
44
45
 
45
46
  def add_option name, exp
@@ -49,6 +50,7 @@ module Brakeman
49
50
 
50
51
  def add_method visibility, name, src, file_name
51
52
  meth_info = Brakeman::MethodInfo.new(name, src, self, file_name)
53
+ add_simple_method_maybe meth_info
52
54
 
53
55
  if src.node_type == :defs
54
56
  @class_methods[name] = meth_info
@@ -112,5 +114,31 @@ module Brakeman
112
114
  def methods_public
113
115
  @methods[:public]
114
116
  end
117
+
118
+ def get_simple_method_return_value type, name
119
+ @simple_methods[type][name]
120
+ end
121
+
122
+ private
123
+
124
+ def add_simple_method_maybe meth_info
125
+ if meth_info.very_simple_method?
126
+ add_simple_method meth_info
127
+ end
128
+ end
129
+
130
+ def add_simple_method meth_info
131
+ name = meth_info.name
132
+ value = meth_info.return_value
133
+
134
+ case meth_info.src.node_type
135
+ when :defn
136
+ @simple_methods[:instance][name] = value
137
+ when :defs
138
+ @simple_methods[:class][name] = value
139
+ else
140
+ raise "Expected sexp type: #{src.node_type}"
141
+ end
142
+ end
115
143
  end
116
144
  end
@@ -19,11 +19,52 @@ module Brakeman
19
19
  else
20
20
  raise "Expected sexp type: #{src.node_type}"
21
21
  end
22
+
23
+ @simple_method = nil
22
24
  end
23
25
 
24
26
  # To support legacy code that expected a Hash
25
27
  def [] attr
26
28
  self.send(attr)
27
29
  end
30
+
31
+ def very_simple_method?
32
+ return @simple_method == :very unless @simple_method.nil?
33
+
34
+ # Very simple methods have one (simple) expression in the body and
35
+ # no arguments
36
+ if src.formal_args.length == 1 # no args
37
+ if src.method_length == 1 # Single expression in body
38
+ value = first_body # First expression in body
39
+
40
+ if simple_literal? value or
41
+ (array? value and all_literals? value) or
42
+ (hash? value and all_literals? value, :hash)
43
+
44
+ @return_value = value
45
+ @simple_method = :very
46
+ end
47
+ end
48
+ end
49
+
50
+ @simple_method ||= false
51
+ end
52
+
53
+ def return_value env = nil
54
+ if very_simple_method?
55
+ return @return_value
56
+ else
57
+ nil
58
+ end
59
+ end
60
+
61
+ def first_body
62
+ case @type
63
+ when :class
64
+ src[4]
65
+ when :instance
66
+ src[3]
67
+ end
68
+ end
28
69
  end
29
70
  end
data/lib/brakeman/util.rb CHANGED
@@ -50,7 +50,11 @@ module Brakeman::Util
50
50
 
51
51
  # stupid simple, used to delegate to ActiveSupport
52
52
  def pluralize word
53
- word + "s"
53
+ if word.end_with? 's'
54
+ word + 'es'
55
+ else
56
+ word + 's'
57
+ end
54
58
  end
55
59
 
56
60
  #Returns a class name as a Symbol.
@@ -238,30 +242,22 @@ module Brakeman::Util
238
242
 
239
243
  #Check if _exp_ is a params hash
240
244
  def params? exp
241
- if exp.is_a? Sexp
242
- return true if exp.node_type == :params or ALL_PARAMETERS.include? exp
243
-
244
- if call? exp
245
- if params? exp[1]
246
- return true
247
- elsif exp[2] == :[]
248
- return params? exp[1]
249
- end
250
- end
251
- end
252
-
253
- false
245
+ recurse_check?(exp) { |child| child.node_type == :params or ALL_PARAMETERS.include? child }
254
246
  end
255
247
 
256
248
  def cookies? exp
249
+ recurse_check?(exp) { |child| child.node_type == :cookies or ALL_COOKIES.include? child }
250
+ end
251
+
252
+ def recurse_check? exp, &check
257
253
  if exp.is_a? Sexp
258
- return true if exp.node_type == :cookies or ALL_COOKIES.include? exp
254
+ return true if yield(exp)
259
255
 
260
256
  if call? exp
261
- if cookies? exp[1]
257
+ if recurse_check? exp[1], &check
262
258
  return true
263
259
  elsif exp[2] == :[]
264
- return cookies? exp[1]
260
+ return recurse_check? exp[1], &check
265
261
  end
266
262
  end
267
263
  end
@@ -301,12 +297,24 @@ module Brakeman::Util
301
297
  exp.is_a? Sexp and types.include? exp.node_type
302
298
  end
303
299
 
304
- LITERALS = [:lit, :false, :str, :true, :array, :hash]
300
+ SIMPLE_LITERALS = [:lit, :false, :str, :true]
301
+
302
+ def simple_literal? exp
303
+ exp.is_a? Sexp and SIMPLE_LITERALS.include? exp.node_type
304
+ end
305
+
306
+ LITERALS = [*SIMPLE_LITERALS, :array, :hash]
305
307
 
306
308
  def literal? exp
307
309
  exp.is_a? Sexp and LITERALS.include? exp.node_type
308
310
  end
309
311
 
312
+ def all_literals? exp, expected_type = :array
313
+ node_type? exp, expected_type and
314
+ exp.length > 1 and
315
+ exp.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
316
+ end
317
+
310
318
  DIR_CONST = s(:const, :Dir)
311
319
 
312
320
  # Dir.glob(...).whatever
@@ -1,3 +1,3 @@
1
1
  module Brakeman
2
- Version = "5.0.2"
2
+ Version = "5.1.2"
3
3
  end
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
  }
@@ -392,7 +394,7 @@ module Brakeman
392
394
  if options[:parallel_checks]
393
395
  notify "Running checks in parallel..."
394
396
  else
395
- notify "Runnning checks..."
397
+ notify "Running checks..."
396
398
  end
397
399
 
398
400
  tracker.run_checks
@@ -477,7 +479,7 @@ module Brakeman
477
479
  $stderr.puts message if @debug
478
480
  end
479
481
 
480
- # Compare JSON ouptut from a previous scan and return the diff of the two scans
482
+ # Compare JSON output from a previous scan and return the diff of the two scans
481
483
  def self.compare options
482
484
  require 'json'
483
485
  require 'brakeman/differ'
@@ -543,6 +543,20 @@ class Sexp
543
543
  self.body.unshift :rlist
544
544
  end
545
545
 
546
+ # Number of "statements" in a method.
547
+ # This is more efficient than `Sexp#body.length`
548
+ # because `Sexp#body` creates a new Sexp.
549
+ def method_length
550
+ expect :defn, :defs
551
+
552
+ case self.node_type
553
+ when :defn
554
+ self.length - 3
555
+ when :defs
556
+ self.length - 4
557
+ end
558
+ end
559
+
546
560
  def render_type
547
561
  expect :render
548
562
  self[1]
@@ -628,4 +642,14 @@ end
628
642
  RUBY
629
643
  end
630
644
 
645
+ class String
646
+ ##
647
+ # This is a hack used by the lexer to sneak in line numbers at the
648
+ # identifier level. This should be MUCH smaller than making
649
+ # process_token return [value, lineno] and modifying EVERYTHING that
650
+ # reduces tIDENTIFIER.
651
+
652
+ attr_accessor :lineno
653
+ end
654
+
631
655
  class WrongSexpError < RuntimeError; end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brakeman-lib
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.2
4
+ version: 5.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Collins
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-07 00:00:00.000000000 Z
11
+ date: 2021-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '3.13'
89
+ version: '3.18'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '3.13'
96
+ version: '3.18'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: ruby_parser-legacy
99
99
  requirement: !ruby/object:Gem::Requirement