brakeman-min 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 +4 -4
- data/CHANGES.md +46 -1
- data/README.md +1 -1
- data/lib/brakeman/app_tree.rb +1 -1
- data/lib/brakeman/checks/check_execute.rb +10 -0
- data/lib/brakeman/checks/check_json_parsing.rb +1 -1
- data/lib/brakeman/checks/check_render.rb +15 -1
- data/lib/brakeman/checks/check_sql.rb +45 -8
- data/lib/brakeman/file_parser.rb +10 -2
- data/lib/brakeman/options.rb +6 -1
- data/lib/brakeman/processors/alias_processor.rb +41 -4
- data/lib/brakeman/processors/haml_template_processor.rb +9 -0
- data/lib/brakeman/processors/lib/call_conversion_helper.rb +2 -6
- data/lib/brakeman/processors/model_processor.rb +32 -0
- data/lib/brakeman/report/ignore/config.rb +1 -1
- data/lib/brakeman/report/report_csv.rb +1 -1
- data/lib/brakeman/report/report_sarif.rb +22 -3
- data/lib/brakeman/report/report_text.rb +1 -1
- data/lib/brakeman/rescanner.rb +1 -1
- data/lib/brakeman/scanner.rb +13 -13
- data/lib/brakeman/tracker/collection.rb +30 -2
- data/lib/brakeman/tracker/method_info.rb +41 -0
- data/lib/brakeman/util.rb +26 -18
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman.rb +4 -2
- data/lib/ruby_parser/bm_sexp.rb +24 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7c5bc7fade73f62510eca765f098971bb32244376a61bc20f4dd4160294262a
|
4
|
+
data.tar.gz: bb2b7d7d8f2840387e9de4161551f766b6db882b3eb334d3bcdb8142a5b47806
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f342348930fa65986272ad5bef129d7ef318b630571692b3b6709253124ec5f421692a3545e4dd7d97014f8f200b54c5f98d8434e99ee4eaef79b67bef779f17
|
7
|
+
data.tar.gz: a07cc95cd61003db2ce86f1fd02a9de8ecf72bfc9a75f6a5492a450759c37cd600371b139cf160f5f8cd25845209de809b6796924ed2dd6d7d0e18cb5b972d06
|
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
|
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.
|
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
|
|
data/lib/brakeman/app_tree.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/brakeman/file_parser.rb
CHANGED
@@ -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
|
|
data/lib/brakeman/options.rb
CHANGED
@@ -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
|
318
|
-
|
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
|
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
|
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
|
@@ -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
|
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
|
data/lib/brakeman/rescanner.rb
CHANGED
@@ -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
|
data/lib/brakeman/scanner.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
254
|
+
return true if yield(exp)
|
259
255
|
|
260
256
|
if call? exp
|
261
|
-
if
|
257
|
+
if recurse_check? exp[1], &check
|
262
258
|
return true
|
263
259
|
elsif exp[2] == :[]
|
264
|
-
return
|
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
|
-
|
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
|
data/lib/brakeman/version.rb
CHANGED
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 "
|
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
|
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'
|
data/lib/ruby_parser/bm_sexp.rb
CHANGED
@@ -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-min
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
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-
|
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.
|
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.
|
96
|
+
version: '3.18'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: ruby_parser-legacy
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|