brakeman-lib 5.0.0 → 5.1.0
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 -0
- data/README.md +10 -1
- data/lib/brakeman.rb +23 -8
- 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_mass_assignment.rb +4 -6
- data/lib/brakeman/checks/check_render.rb +15 -1
- data/lib/brakeman/checks/check_sanitize_methods.rb +2 -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/parsers/template_parser.rb +24 -0
- data/lib/brakeman/processors/alias_processor.rb +105 -18
- data/lib/brakeman/processors/base_processor.rb +4 -4
- 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/lib/rails4_config_processor.rb +2 -1
- 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
@@ -1,11 +1,5 @@
|
|
1
1
|
module Brakeman
|
2
2
|
module CallConversionHelper
|
3
|
-
def all_literals? exp, expected_type = :array
|
4
|
-
node_type? exp, expected_type and
|
5
|
-
exp.length > 1 and
|
6
|
-
exp.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
|
7
|
-
end
|
8
|
-
|
9
3
|
# Join two array literals into one.
|
10
4
|
def join_arrays lhs, rhs, original_exp = nil
|
11
5
|
if array? lhs and array? rhs
|
@@ -76,6 +70,8 @@ module Brakeman
|
|
76
70
|
|
77
71
|
#Have to do this because first element is :array and we have to skip it
|
78
72
|
array[1..-1][index] or original_exp
|
73
|
+
elsif all_literals? array
|
74
|
+
safe_literal(array.line)
|
79
75
|
else
|
80
76
|
original_exp
|
81
77
|
end
|
@@ -92,5 +88,13 @@ module Brakeman
|
|
92
88
|
original_exp
|
93
89
|
end
|
94
90
|
end
|
91
|
+
|
92
|
+
def hash_values_at hash, keys
|
93
|
+
values = keys.map do |key|
|
94
|
+
process_hash_access hash, key
|
95
|
+
end
|
96
|
+
|
97
|
+
Sexp.new(:array).concat(values).line(hash.line)
|
98
|
+
end
|
95
99
|
end
|
96
100
|
end
|
@@ -2,10 +2,11 @@ require 'brakeman/processors/lib/rails3_config_processor'
|
|
2
2
|
|
3
3
|
class Brakeman::Rails4ConfigProcessor < Brakeman::Rails3ConfigProcessor
|
4
4
|
APPLICATION_CONFIG = s(:call, s(:call, s(:const, :Rails), :application), :configure)
|
5
|
+
ALT_APPLICATION_CONFIG = s(:call, s(:call, s(:colon3, :Rails), :application), :configure)
|
5
6
|
|
6
7
|
# Look for Rails.application.configure do ... end
|
7
8
|
def process_iter exp
|
8
|
-
if exp.block_call == APPLICATION_CONFIG
|
9
|
+
if exp.block_call == APPLICATION_CONFIG or exp.block_call == ALT_APPLICATION_CONFIG
|
9
10
|
@inside_config = true
|
10
11
|
process exp.block if sexp? exp.block
|
11
12
|
@inside_config = false
|
@@ -54,6 +54,15 @@ class Brakeman::LibraryProcessor < Brakeman::BaseProcessor
|
|
54
54
|
|
55
55
|
def process_call exp
|
56
56
|
if process_call_defn? exp
|
57
|
+
exp
|
58
|
+
elsif @current_method.nil? and exp.target.nil? and (@current_class or @current_module)
|
59
|
+
# Methods called inside class / module
|
60
|
+
case exp.method
|
61
|
+
when :include
|
62
|
+
module_name = class_name(exp.first_arg)
|
63
|
+
(@current_class || @current_module).add_include module_name
|
64
|
+
end
|
65
|
+
|
57
66
|
exp
|
58
67
|
else
|
59
68
|
process_default exp
|
@@ -73,6 +73,8 @@ class Brakeman::ModelProcessor < Brakeman::BaseProcessor
|
|
73
73
|
@current_class.set_attr_accessible exp
|
74
74
|
when :attr_protected
|
75
75
|
@current_class.set_attr_protected exp
|
76
|
+
when :enum
|
77
|
+
add_enum_method exp
|
76
78
|
else
|
77
79
|
if @current_class
|
78
80
|
@current_class.add_option method, exp
|
@@ -87,4 +89,33 @@ class Brakeman::ModelProcessor < Brakeman::BaseProcessor
|
|
87
89
|
call
|
88
90
|
end
|
89
91
|
end
|
92
|
+
|
93
|
+
def add_enum_method call
|
94
|
+
arg = call.first_arg
|
95
|
+
return unless hash? arg
|
96
|
+
|
97
|
+
enum_name = arg[1].value # first key
|
98
|
+
enums = arg[2] # first value
|
99
|
+
enums_name = pluralize(enum_name.to_s).to_sym
|
100
|
+
|
101
|
+
call_line = call.line
|
102
|
+
|
103
|
+
if hash? enums
|
104
|
+
enum_values = enums
|
105
|
+
elsif array? enums
|
106
|
+
# Build hash for enum values like Rails does
|
107
|
+
enum_values = s(:hash).line(call_line)
|
108
|
+
|
109
|
+
enums.each_sexp.with_index do |v, index|
|
110
|
+
enum_values << v
|
111
|
+
enum_values << s(:lit, index).line(call_line)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
enum_method = s(:defn, enum_name, s(:args), safe_literal(call_line))
|
116
|
+
enums_method = s(:defs, s(:self), enums_name, s(:args), enum_values)
|
117
|
+
|
118
|
+
@current_class.add_method :public, enum_name, enum_method, @current_file
|
119
|
+
@current_class.add_method :public, enums_name, enums_method, @current_file
|
120
|
+
end
|
90
121
|
end
|
data/lib/brakeman/report.rb
CHANGED
@@ -6,7 +6,7 @@ require 'brakeman/report/report_base'
|
|
6
6
|
class Brakeman::Report
|
7
7
|
attr_reader :tracker
|
8
8
|
|
9
|
-
VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s, :to_markdown, :to_codeclimate, :to_plain, :to_text, :to_junit]
|
9
|
+
VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s, :to_markdown, :to_codeclimate, :to_plain, :to_text, :to_junit, :to_github]
|
10
10
|
|
11
11
|
def initialize tracker
|
12
12
|
@app_tree = tracker.app_tree
|
@@ -48,6 +48,9 @@ class Brakeman::Report
|
|
48
48
|
when :to_sonar
|
49
49
|
require_report 'sonar'
|
50
50
|
Brakeman::Report::Sonar
|
51
|
+
when :to_github
|
52
|
+
require_report 'github'
|
53
|
+
Brakeman::Report::Github
|
51
54
|
else
|
52
55
|
raise "Invalid format: #{format}. Should be one of #{VALID_FORMATS.inspect}"
|
53
56
|
end
|
@@ -100,14 +100,14 @@ module Brakeman
|
|
100
100
|
|
101
101
|
# Read configuration to file
|
102
102
|
def read_from_file file = @file
|
103
|
-
if File.exist? file
|
103
|
+
if File.exist? file.absolute
|
104
104
|
begin
|
105
105
|
@already_ignored = JSON.parse(File.read(file), :symbolize_names => true)[:ignored_warnings]
|
106
106
|
rescue => e
|
107
|
-
raise e, "\nError[#{e.class}] while reading brakeman ignore file: #{file}\n"
|
107
|
+
raise e, "\nError[#{e.class}] while reading brakeman ignore file: #{file.relative}\n"
|
108
108
|
end
|
109
109
|
else
|
110
|
-
Brakeman.notify "[Notice] Could not find ignore configuration in #{file}"
|
110
|
+
Brakeman.notify "[Notice] Could not find ignore configuration in #{file.relative}"
|
111
111
|
@already_ignored = []
|
112
112
|
end
|
113
113
|
|
@@ -134,7 +134,7 @@ module Brakeman
|
|
134
134
|
:brakeman_version => Brakeman::Version
|
135
135
|
}
|
136
136
|
|
137
|
-
File.open file, "w" do |f|
|
137
|
+
File.open file.absolute, "w" do |f|
|
138
138
|
f.puts JSON.pretty_generate(output)
|
139
139
|
end
|
140
140
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Github Actions Formatter
|
2
|
+
# Formats warnings as workflow commands to create annotations in GitHub UI
|
3
|
+
class Brakeman::Report::Github < Brakeman::Report::Base
|
4
|
+
def generate_report
|
5
|
+
# @see https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message
|
6
|
+
errors.concat(warnings).join("\n")
|
7
|
+
end
|
8
|
+
|
9
|
+
def warnings
|
10
|
+
all_warnings
|
11
|
+
.map { |warning| "::warning file=#{warning_file(warning)},line=#{warning.line}::#{warning.message}" }
|
12
|
+
end
|
13
|
+
|
14
|
+
def errors
|
15
|
+
tracker.errors.map do |error|
|
16
|
+
if error[:exception].is_a?(Racc::ParseError)
|
17
|
+
# app/services/balance.rb:4 :: parse error on value "..." (tDOT3)
|
18
|
+
file, line = error[:exception].message.split(':').map(&:strip)[0,2]
|
19
|
+
"::error file=#{file},line=#{line}::#{clean_message(error[:error])}"
|
20
|
+
else
|
21
|
+
"::error ::#{clean_message(error[:error])}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def clean_message(msg)
|
29
|
+
msg.gsub('::','').squeeze(' ')
|
30
|
+
end
|
31
|
+
end
|
@@ -48,7 +48,7 @@ class Brakeman::Report::SARIF < Brakeman::Report::Base
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def results
|
51
|
-
@results ||= all_warnings.map do |warning|
|
51
|
+
@results ||= tracker.checks.all_warnings.map do |warning|
|
52
52
|
rule_id = render_id warning
|
53
53
|
result_level = infer_level warning
|
54
54
|
message_text = render_message warning.message.to_s
|
@@ -72,6 +72,23 @@ class Brakeman::Report::SARIF < Brakeman::Report::Base
|
|
72
72
|
],
|
73
73
|
}
|
74
74
|
|
75
|
+
if @ignore_filter && @ignore_filter.ignored?(warning)
|
76
|
+
result[:suppressions] = [
|
77
|
+
{
|
78
|
+
:kind => 'external',
|
79
|
+
:justification => @ignore_filter.note_for(warning),
|
80
|
+
:location => {
|
81
|
+
:physicalLocation => {
|
82
|
+
:artifactLocation => {
|
83
|
+
:uri => @ignore_filter.file.relative,
|
84
|
+
:uriBaseId => '%SRCROOT%',
|
85
|
+
},
|
86
|
+
},
|
87
|
+
},
|
88
|
+
}
|
89
|
+
]
|
90
|
+
end
|
91
|
+
|
75
92
|
result
|
76
93
|
end
|
77
94
|
end
|
@@ -85,7 +102,7 @@ class Brakeman::Report::SARIF < Brakeman::Report::Base
|
|
85
102
|
|
86
103
|
# Returns a de-duplicated set of warnings, used to generate rules
|
87
104
|
def unique_warnings_by_warning_code
|
88
|
-
@unique_warnings_by_warning_code ||= all_warnings.uniq { |w| w.warning_code }
|
105
|
+
@unique_warnings_by_warning_code ||= tracker.checks.all_warnings.uniq { |w| w.warning_code }
|
89
106
|
end
|
90
107
|
|
91
108
|
def render_id warning
|
@@ -94,6 +111,8 @@ class Brakeman::Report::SARIF < Brakeman::Report::Base
|
|
94
111
|
end
|
95
112
|
|
96
113
|
def render_message message
|
114
|
+
return message if message.nil?
|
115
|
+
|
97
116
|
# Ensure message ends with a period
|
98
117
|
if message.end_with? "."
|
99
118
|
message
|
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
@@ -71,7 +71,7 @@ class Brakeman::Scanner
|
|
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
|
|
@@ -353,6 +353,9 @@ class Brakeman::Scanner
|
|
353
353
|
def parse_ruby_file file
|
354
354
|
fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
|
355
355
|
fp.parse_ruby(file.read, file)
|
356
|
+
rescue Exception => e
|
357
|
+
tracker.error(e)
|
358
|
+
nil
|
356
359
|
end
|
357
360
|
end
|
358
361
|
|
data/lib/brakeman/tracker.rb
CHANGED
@@ -35,6 +35,7 @@ class Brakeman::Tracker
|
|
35
35
|
#class they are.
|
36
36
|
@models = {}
|
37
37
|
@models[UNKNOWN_MODEL] = Brakeman::Model.new(UNKNOWN_MODEL, nil, @app_tree.file_path("NOT_REAL.rb"), nil, self)
|
38
|
+
@method_cache = {}
|
38
39
|
@routes = {}
|
39
40
|
@initializers = {}
|
40
41
|
@errors = []
|
@@ -99,8 +100,8 @@ class Brakeman::Tracker
|
|
99
100
|
classes.each do |set|
|
100
101
|
set.each do |set_name, collection|
|
101
102
|
collection.each_method do |method_name, definition|
|
102
|
-
src = definition
|
103
|
-
yield src, set_name, method_name, definition
|
103
|
+
src = definition.src
|
104
|
+
yield src, set_name, method_name, definition.file
|
104
105
|
end
|
105
106
|
end
|
106
107
|
end
|
@@ -220,6 +221,34 @@ class Brakeman::Tracker
|
|
220
221
|
nil
|
221
222
|
end
|
222
223
|
|
224
|
+
def find_method method_name, class_name, method_type = :instance
|
225
|
+
return nil unless method_name.is_a? Symbol
|
226
|
+
|
227
|
+
klass = find_class(class_name)
|
228
|
+
return nil unless klass
|
229
|
+
|
230
|
+
cache_key = [klass, method_name, method_type]
|
231
|
+
|
232
|
+
if method = @method_cache[cache_key]
|
233
|
+
return method
|
234
|
+
end
|
235
|
+
|
236
|
+
if method = klass.get_method(method_name, method_type)
|
237
|
+
return method
|
238
|
+
else
|
239
|
+
# Check modules included for method definition
|
240
|
+
# TODO: only for instance methods, otherwise check extends!
|
241
|
+
klass.includes.each do |included_name|
|
242
|
+
if method = find_method(method_name, included_name, method_type)
|
243
|
+
return (@method_cache[cache_key] = method)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Not in any included modules, check the parent
|
248
|
+
@method_cache[cache_key] = find_method(method_name, klass.parent)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
223
252
|
def index_call_sites
|
224
253
|
finder = Brakeman::FindAllCalls.new self
|
225
254
|
|
@@ -285,8 +314,8 @@ class Brakeman::Tracker
|
|
285
314
|
method_sets.each do |set|
|
286
315
|
set.each do |set_name, info|
|
287
316
|
info.each_method do |method_name, definition|
|
288
|
-
src = definition
|
289
|
-
finder.process_source src, :class => set_name, :method => method_name, :file => definition
|
317
|
+
src = definition.src
|
318
|
+
finder.process_source src, :class => set_name, :method => method_name, :file => definition.file
|
290
319
|
end
|
291
320
|
end
|
292
321
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'brakeman/util'
|
2
|
+
require 'brakeman/tracker/method_info'
|
2
3
|
|
3
4
|
module Brakeman
|
4
5
|
class Collection
|
@@ -13,6 +14,8 @@ module Brakeman
|
|
13
14
|
@src = {}
|
14
15
|
@includes = []
|
15
16
|
@methods = { :public => {}, :private => {}, :protected => {} }
|
17
|
+
@class_methods = {}
|
18
|
+
@simple_methods = { :class => {}, instance: {} }
|
16
19
|
@options = {}
|
17
20
|
@tracker = tracker
|
18
21
|
|
@@ -22,7 +25,7 @@ module Brakeman
|
|
22
25
|
def ancestor? parent, seen={}
|
23
26
|
seen[self.name] = true
|
24
27
|
|
25
|
-
if self.parent == parent or seen[self.parent]
|
28
|
+
if self.parent == parent or self.name == parent or seen[self.parent]
|
26
29
|
true
|
27
30
|
elsif parent_model = collection[self.parent]
|
28
31
|
parent_model.ancestor? parent, seen
|
@@ -37,7 +40,7 @@ module Brakeman
|
|
37
40
|
end
|
38
41
|
|
39
42
|
def add_include class_name
|
40
|
-
@includes << class_name
|
43
|
+
@includes << class_name unless ancestor?(class_name)
|
41
44
|
end
|
42
45
|
|
43
46
|
def add_option name, exp
|
@@ -46,11 +49,17 @@ module Brakeman
|
|
46
49
|
end
|
47
50
|
|
48
51
|
def add_method visibility, name, src, file_name
|
52
|
+
meth_info = Brakeman::MethodInfo.new(name, src, self, file_name)
|
53
|
+
add_simple_method_maybe meth_info
|
54
|
+
|
49
55
|
if src.node_type == :defs
|
56
|
+
@class_methods[name] = meth_info
|
57
|
+
|
58
|
+
# TODO fix this weirdness
|
50
59
|
name = :"#{src[1]}.#{name}"
|
51
60
|
end
|
52
61
|
|
53
|
-
@methods[visibility][name] =
|
62
|
+
@methods[visibility][name] = meth_info
|
54
63
|
end
|
55
64
|
|
56
65
|
def each_method
|
@@ -61,16 +70,31 @@ module Brakeman
|
|
61
70
|
end
|
62
71
|
end
|
63
72
|
|
64
|
-
def get_method name
|
65
|
-
|
66
|
-
|
67
|
-
|
73
|
+
def get_method name, type = :instance
|
74
|
+
case type
|
75
|
+
when :class
|
76
|
+
get_class_method name
|
77
|
+
when :instance
|
78
|
+
get_instance_method name
|
79
|
+
else
|
80
|
+
raise "Unexpected method type: #{type.inspect}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_instance_method name
|
85
|
+
@methods.each do |_vis, meths|
|
86
|
+
if meths[name]
|
87
|
+
return meths[name]
|
68
88
|
end
|
69
89
|
end
|
70
90
|
|
71
91
|
nil
|
72
92
|
end
|
73
93
|
|
94
|
+
def get_class_method name
|
95
|
+
@class_methods[name]
|
96
|
+
end
|
97
|
+
|
74
98
|
def file
|
75
99
|
@files.first
|
76
100
|
end
|
@@ -90,5 +114,31 @@ module Brakeman
|
|
90
114
|
def methods_public
|
91
115
|
@methods[:public]
|
92
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
|
93
143
|
end
|
94
144
|
end
|