brakeman-lib 5.0.4 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES.md +27 -0
- data/lib/brakeman.rb +12 -4
- data/lib/brakeman/checks/check_detailed_exceptions.rb +1 -1
- data/lib/brakeman/checks/check_evaluation.rb +1 -1
- data/lib/brakeman/checks/check_execute.rb +10 -0
- data/lib/brakeman/checks/check_render.rb +15 -1
- data/lib/brakeman/checks/check_sql.rb +58 -8
- data/lib/brakeman/checks/check_verb_confusion.rb +1 -1
- data/lib/brakeman/commandline.rb +1 -1
- data/lib/brakeman/file_parser.rb +45 -15
- data/lib/brakeman/options.rb +7 -2
- data/lib/brakeman/processors/alias_processor.rb +85 -9
- data/lib/brakeman/processors/controller_alias_processor.rb +6 -43
- data/lib/brakeman/processors/lib/call_conversion_helper.rb +10 -6
- data/lib/brakeman/processors/library_processor.rb +9 -0
- data/lib/brakeman/processors/model_processor.rb +31 -0
- data/lib/brakeman/report.rb +4 -1
- data/lib/brakeman/report/ignore/config.rb +4 -4
- data/lib/brakeman/report/ignore/interactive.rb +1 -1
- data/lib/brakeman/report/report_github.rb +31 -0
- data/lib/brakeman/report/report_sarif.rb +21 -2
- data/lib/brakeman/rescanner.rb +1 -1
- data/lib/brakeman/scanner.rb +4 -1
- data/lib/brakeman/tracker.rb +33 -4
- data/lib/brakeman/tracker/collection.rb +57 -7
- data/lib/brakeman/tracker/method_info.rb +70 -0
- data/lib/brakeman/util.rb +34 -18
- data/lib/brakeman/version.rb +1 -1
- data/lib/ruby_parser/bm_sexp.rb +14 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 425078e2c4abfb5dc629bd5b70fcbaa1de59be69093097ad5ca78c3f425f575c
|
4
|
+
data.tar.gz: 1ddaf7c9084213dcc7db6772dc164095800de50897d157345a01c234d09fe778
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a0a910c6859f389eeaf21253dc8d33f7f0d199e2289bc3e6145b7d9eecaf7dd0793dad3a2a013ec3a4c64c681cfbbf88647e21566ea3b7269bf485f29ef10ee
|
7
|
+
data.tar.gz: 2d5845a9bd98a86f3af891122d9fe410da8586aa8aa45ccb2e05bcf25b8fdf6b6702d6ac396f866a4b53b9659cceface62a03997c30954d20d2e32b73cffab5c
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,30 @@
|
|
1
|
+
# 5.1.0 - 2021-07-19
|
2
|
+
|
3
|
+
* Initial support for ActiveRecord enums
|
4
|
+
* Support `Hash#include?`
|
5
|
+
* Interprocedural dataflow from very simple class methods
|
6
|
+
* Fix SARIF report when checks have no description (Eli Block)
|
7
|
+
* Add ignored warnings to SARIF report (Eli Block)
|
8
|
+
* Add `--sql-safe-methods` option (Esty Scheiner)
|
9
|
+
* Update SQL injection check for Rails 6.0/6.1
|
10
|
+
* Fix false positive in command injection with `Open3.capture` (Richard Fitzgerald)
|
11
|
+
* Fix infinite loop on mixin self-includes (Andrew Szczepanski)
|
12
|
+
* Ignore dates in SQL
|
13
|
+
* Refactor `cookie?`/`param?` methods (Keenan Brock)
|
14
|
+
* Ignore renderables in dynamic render path check (Brad Parker)
|
15
|
+
* Support `Array#push`
|
16
|
+
* Better `Array#join` support
|
17
|
+
* Adjust copy of `--interactive` menu (Elia Schito)
|
18
|
+
* Support `Array#*`
|
19
|
+
* Better method definition tracking and lookup
|
20
|
+
* Support `Hash#values` and `Hash#values_at`
|
21
|
+
* Check for user-controlled evaluation even if it's a call target
|
22
|
+
* Support `Array#fetch` and `Hash#fetch`
|
23
|
+
* Ignore `sanitize_sql_like` in SQL
|
24
|
+
* Ignore method calls on numbers in SQL
|
25
|
+
* Add GitHub Actions format (Klaus Badelt)
|
26
|
+
* Read and parse files in parallel
|
27
|
+
|
1
28
|
# 5.0.4 - 2021-06-08
|
2
29
|
|
3
30
|
(brakeman gem release only)
|
data/lib/brakeman.rb
CHANGED
@@ -65,6 +65,7 @@ module Brakeman
|
|
65
65
|
# * :report_routes - show found routes on controllers (default: false)
|
66
66
|
# * :run_checks - array of checks to run (run all if not specified)
|
67
67
|
# * :safe_methods - array of methods to consider safe
|
68
|
+
# * :sql_safe_methods - array of sql sanitization methods to consider safe
|
68
69
|
# * :skip_libs - do not process lib/ directory (default: false)
|
69
70
|
# * :skip_vendor - do not process vendor/ directory (default: true)
|
70
71
|
# * :skip_checks - checks not to run (run all if not specified)
|
@@ -198,6 +199,7 @@ module Brakeman
|
|
198
199
|
:relative_path => false,
|
199
200
|
:report_progress => true,
|
200
201
|
:safe_methods => Set.new,
|
202
|
+
:sql_safe_methods => Set.new,
|
201
203
|
:skip_checks => Set.new,
|
202
204
|
:skip_vendor => true,
|
203
205
|
}
|
@@ -250,6 +252,8 @@ module Brakeman
|
|
250
252
|
[:to_sarif]
|
251
253
|
when :sonar, :to_sonar
|
252
254
|
[:to_sonar]
|
255
|
+
when :github, :to_github
|
256
|
+
[:to_github]
|
253
257
|
else
|
254
258
|
[:to_text]
|
255
259
|
end
|
@@ -283,6 +287,8 @@ module Brakeman
|
|
283
287
|
:to_sarif
|
284
288
|
when /\.sonar$/i
|
285
289
|
:to_sonar
|
290
|
+
when /\.github$/i
|
291
|
+
:to_github
|
286
292
|
else
|
287
293
|
:to_text
|
288
294
|
end
|
@@ -521,12 +527,14 @@ module Brakeman
|
|
521
527
|
|
522
528
|
# Returns an array of alert fingerprints for any ignored warnings without
|
523
529
|
# notes found in the specified ignore file (if it exists).
|
524
|
-
def self.ignore_file_entries_with_empty_notes file
|
530
|
+
def self.ignore_file_entries_with_empty_notes file, options
|
525
531
|
return [] unless file
|
526
532
|
|
527
533
|
require 'brakeman/report/ignore/config'
|
528
534
|
|
529
|
-
|
535
|
+
app_tree = Brakeman::AppTree.from_options(options)
|
536
|
+
|
537
|
+
config = IgnoreConfig.new(Brakeman::FilePath.from_app_tree(app_tree, file), nil)
|
530
538
|
config.read_from_file
|
531
539
|
config.already_ignored_entries_with_empty_notes.map { |i| i[:fingerprint] }
|
532
540
|
end
|
@@ -537,9 +545,9 @@ module Brakeman
|
|
537
545
|
app_tree = Brakeman::AppTree.from_options(options)
|
538
546
|
|
539
547
|
if options[:ignore_file]
|
540
|
-
file = options[:ignore_file]
|
548
|
+
file = Brakeman::FilePath.from_app_tree(app_tree, options[:ignore_file])
|
541
549
|
elsif app_tree.exists? "config/brakeman.ignore"
|
542
|
-
file =
|
550
|
+
file = Brakeman::FilePath.from_app_tree(app_tree, "config/brakeman.ignore")
|
543
551
|
elsif not options[:interactive_ignore]
|
544
552
|
return
|
545
553
|
end
|
@@ -26,7 +26,7 @@ class Brakeman::CheckDetailedExceptions < Brakeman::BaseCheck
|
|
26
26
|
def check_detailed_exceptions
|
27
27
|
tracker.controllers.each do |_name, controller|
|
28
28
|
controller.methods_public.each do |method_name, definition|
|
29
|
-
src = definition
|
29
|
+
src = definition.src
|
30
30
|
body = src.body.last
|
31
31
|
next unless body
|
32
32
|
|
@@ -10,7 +10,7 @@ class Brakeman::CheckEvaluation < Brakeman::BaseCheck
|
|
10
10
|
#Process calls
|
11
11
|
def run_check
|
12
12
|
Brakeman.debug "Finding eval-like calls"
|
13
|
-
calls = tracker.find_call :
|
13
|
+
calls = tracker.find_call methods: [:eval, :instance_eval, :class_eval, :module_eval], nested: true
|
14
14
|
|
15
15
|
Brakeman.debug "Processing eval-like calls"
|
16
16
|
calls.each do |call|
|
@@ -87,6 +87,16 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
|
|
87
87
|
dangerous_interp?(first_arg) ||
|
88
88
|
dangerous_string_building?(first_arg)
|
89
89
|
end
|
90
|
+
when :capture2, :capture2e, :capture3
|
91
|
+
# Open3 capture methods can take a :stdin_data argument which is used as the
|
92
|
+
# the input to the called command so it is not succeptable to command injection.
|
93
|
+
# As such if the last argument is a hash (and therefore execution options) it
|
94
|
+
# should be ignored
|
95
|
+
|
96
|
+
args.pop if hash?(args.last) && args.length > 2
|
97
|
+
failure = include_user_input?(args) ||
|
98
|
+
dangerous_interp?(args) ||
|
99
|
+
dangerous_string_building?(args)
|
90
100
|
else
|
91
101
|
failure = include_user_input?(args) ||
|
92
102
|
dangerous_interp?(args) ||
|
@@ -33,6 +33,7 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
|
|
33
33
|
view = result[:call][2]
|
34
34
|
|
35
35
|
if sexp? view and original? result
|
36
|
+
return if renderable?(view)
|
36
37
|
|
37
38
|
if input = has_immediate_user_input?(view)
|
38
39
|
if string_interp? view
|
@@ -94,4 +95,17 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
|
|
94
95
|
end
|
95
96
|
end
|
96
97
|
end
|
97
|
-
|
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
|
@@ -572,13 +584,17 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
572
584
|
end
|
573
585
|
|
574
586
|
IGNORE_METHODS_IN_SQL = Set[:id, :merge_conditions, :table_name, :quoted_table_name,
|
575
|
-
:quoted_primary_key, :to_i, :to_f, :sanitize_sql, :sanitize_sql_array,
|
587
|
+
:quoted_primary_key, :to_i, :to_f, :sanitize_sql, :sanitize_sql_array, :sanitize_sql_like,
|
576
588
|
:sanitize_sql_for_assignment, :sanitize_sql_for_conditions, :sanitize_sql_hash,
|
577
589
|
:sanitize_sql_hash_for_assignment, :sanitize_sql_hash_for_conditions,
|
578
590
|
:to_sql, :sanitize, :primary_key, :table_name_prefix, :table_name_suffix,
|
579
591
|
:where_values_hash, :foreign_key, :uuid
|
580
592
|
]
|
581
593
|
|
594
|
+
def ignore_methods_in_sql
|
595
|
+
@ignore_methods_in_sql ||= IGNORE_METHODS_IN_SQL + (tracker.options[:sql_safe_methods] || [])
|
596
|
+
end
|
597
|
+
|
582
598
|
def safe_value? exp
|
583
599
|
return true unless sexp? exp
|
584
600
|
|
@@ -589,10 +605,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
589
605
|
if exp.method == :to_s or exp.method == :to_sym
|
590
606
|
safe_value? exp.target
|
591
607
|
else
|
592
|
-
|
593
|
-
quote_call? exp or
|
594
|
-
arel? exp or
|
595
|
-
exp.method.to_s.end_with? "_id"
|
608
|
+
ignore_call? exp
|
596
609
|
end
|
597
610
|
when :if
|
598
611
|
safe_value? exp.then_clause and safe_value? exp.else_clause
|
@@ -607,6 +620,17 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
607
620
|
end
|
608
621
|
end
|
609
622
|
|
623
|
+
def ignore_call? exp
|
624
|
+
return unless call? exp
|
625
|
+
|
626
|
+
ignore_methods_in_sql.include? exp.method or
|
627
|
+
quote_call? exp or
|
628
|
+
arel? exp or
|
629
|
+
exp.method.to_s.end_with? "_id" or
|
630
|
+
number_target? exp or
|
631
|
+
date_target? exp
|
632
|
+
end
|
633
|
+
|
610
634
|
QUOTE_METHODS = [:quote, :quote_column_name, :quoted_date, :quote_string, :quote_table_name]
|
611
635
|
|
612
636
|
def quote_call? exp
|
@@ -695,4 +719,30 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
695
719
|
active_record_models.include? klass
|
696
720
|
end
|
697
721
|
end
|
722
|
+
|
723
|
+
def number_target? exp
|
724
|
+
return unless call? exp
|
725
|
+
|
726
|
+
if number? exp.target
|
727
|
+
true
|
728
|
+
elsif call? exp.target
|
729
|
+
number_target? exp.target
|
730
|
+
else
|
731
|
+
false
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
DATE_CLASS = s(:const, :Date)
|
736
|
+
|
737
|
+
def date_target? exp
|
738
|
+
return unless call? exp
|
739
|
+
|
740
|
+
if exp.target == DATE_CLASS
|
741
|
+
true
|
742
|
+
elsif call? exp.target
|
743
|
+
date_target? exp.target
|
744
|
+
else
|
745
|
+
false
|
746
|
+
end
|
747
|
+
end
|
698
748
|
end
|
data/lib/brakeman/commandline.rb
CHANGED
@@ -126,7 +126,7 @@ module Brakeman
|
|
126
126
|
|
127
127
|
ensure_ignore_notes_failed = false
|
128
128
|
if tracker.options[:ensure_ignore_notes]
|
129
|
-
fingerprints = Brakeman::ignore_file_entries_with_empty_notes tracker.ignored_filter&.file
|
129
|
+
fingerprints = Brakeman::ignore_file_entries_with_empty_notes tracker.ignored_filter&.file, options
|
130
130
|
|
131
131
|
unless fingerprints.empty?
|
132
132
|
ensure_ignore_notes_failed = true
|
data/lib/brakeman/file_parser.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'parallel'
|
2
|
+
|
1
3
|
module Brakeman
|
2
4
|
ASTFile = Struct.new(:path, :ast)
|
3
5
|
|
@@ -5,29 +7,62 @@ module Brakeman
|
|
5
7
|
class FileParser
|
6
8
|
attr_reader :file_list, :errors
|
7
9
|
|
8
|
-
def initialize app_tree, timeout
|
10
|
+
def initialize app_tree, timeout, parallel = true
|
9
11
|
@app_tree = app_tree
|
10
12
|
@timeout = timeout
|
11
13
|
@file_list = []
|
12
14
|
@errors = []
|
15
|
+
@parallel = parallel
|
13
16
|
end
|
14
17
|
|
15
18
|
def parse_files list
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
if @parallel
|
20
|
+
parallel_options = {}
|
21
|
+
else
|
22
|
+
# Disable parallelism
|
23
|
+
parallel_options = { in_threads: 0 }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Parse the files in parallel.
|
27
|
+
# By default, the parsing will be in separate processes.
|
28
|
+
# So we map the result to ASTFiles and/or Exceptions
|
29
|
+
# then partition them into ASTFiles and Exceptions
|
30
|
+
# and add the Exceptions to @errors
|
31
|
+
#
|
32
|
+
# Basically just a funky way to deal with two possible
|
33
|
+
# return types that are returned from isolated processes.
|
34
|
+
#
|
35
|
+
# Note this method no longer uses read_files
|
36
|
+
@file_list, new_errors = Parallel.map(list, parallel_options) do |file_name|
|
37
|
+
file_path = @app_tree.file_path(file_name)
|
38
|
+
contents = file_path.read
|
39
|
+
|
40
|
+
begin
|
41
|
+
if ast = parse_ruby(contents, file_path.relative)
|
42
|
+
ASTFile.new(file_name, ast)
|
43
|
+
end
|
44
|
+
rescue Exception => e
|
45
|
+
e
|
19
46
|
end
|
47
|
+
end.compact.partition do |result|
|
48
|
+
result.is_a? ASTFile
|
20
49
|
end
|
50
|
+
|
51
|
+
errors.concat new_errors
|
21
52
|
end
|
22
53
|
|
23
54
|
def read_files list
|
24
55
|
list.each do |path|
|
25
56
|
file = @app_tree.file_path(path)
|
26
57
|
|
27
|
-
|
58
|
+
begin
|
59
|
+
result = yield file, file.read
|
28
60
|
|
29
|
-
|
30
|
-
|
61
|
+
if result
|
62
|
+
@file_list << result
|
63
|
+
end
|
64
|
+
rescue Exception => e
|
65
|
+
@errors << e
|
31
66
|
end
|
32
67
|
end
|
33
68
|
end
|
@@ -42,17 +77,12 @@ module Brakeman
|
|
42
77
|
Brakeman.debug "Parsing #{path}"
|
43
78
|
RubyParser.new.parse input, path, @timeout
|
44
79
|
rescue Racc::ParseError => e
|
45
|
-
|
80
|
+
raise e.exception(e.message + "\nCould not parse #{path}")
|
46
81
|
rescue Timeout::Error => e
|
47
|
-
|
82
|
+
raise Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout")
|
48
83
|
rescue => e
|
49
|
-
|
84
|
+
raise e.exception(e.message + "\nWhile processing #{path}")
|
50
85
|
end
|
51
86
|
end
|
52
|
-
|
53
|
-
def error exception
|
54
|
-
@errors << exception
|
55
|
-
nil
|
56
|
-
end
|
57
87
|
end
|
58
88
|
end
|
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 }
|
@@ -233,7 +238,7 @@ module Brakeman::Options
|
|
233
238
|
|
234
239
|
opts.on "-f",
|
235
240
|
"--format TYPE",
|
236
|
-
[:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif, :sonar],
|
241
|
+
[:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif, :sonar, :github],
|
237
242
|
"Specify output formats. Default is text" do |type|
|
238
243
|
|
239
244
|
type = "s" if type == :text
|
@@ -208,6 +208,15 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
208
208
|
return Sexp.new(:false).line(exp.line)
|
209
209
|
end
|
210
210
|
|
211
|
+
# For the simplest case of `Foo.thing`
|
212
|
+
if node_type? target, :const and first_arg.nil?
|
213
|
+
if tracker and (klass = tracker.find_class(class_name(target.value)))
|
214
|
+
if return_value = klass.get_simple_method_return_value(:class, method)
|
215
|
+
return return_value.deep_clone(exp.line)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
211
220
|
#See if it is possible to simplify some basic cases
|
212
221
|
#of addition/concatenation.
|
213
222
|
case method
|
@@ -220,13 +229,28 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
220
229
|
exp = math_op(:+, target, first_arg, exp)
|
221
230
|
end
|
222
231
|
when :-, :*, :/
|
223
|
-
|
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
|
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
|
306
|
-
|
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
|
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
|
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
|
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
|