brakeman-lib 4.10.1 → 5.0.4
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 +44 -0
- data/README.md +11 -2
- data/lib/brakeman.rb +17 -4
- data/lib/brakeman/app_tree.rb +36 -3
- data/lib/brakeman/checks/base_check.rb +7 -1
- data/lib/brakeman/checks/check_execute.rb +1 -0
- data/lib/brakeman/checks/check_mass_assignment.rb +4 -6
- data/lib/brakeman/checks/check_sanitize_methods.rb +2 -1
- data/lib/brakeman/checks/check_sql.rb +1 -1
- data/lib/brakeman/checks/check_unsafe_reflection_methods.rb +68 -0
- data/lib/brakeman/checks/check_verb_confusion.rb +75 -0
- data/lib/brakeman/file_parser.rb +19 -18
- data/lib/brakeman/options.rb +5 -1
- data/lib/brakeman/parsers/template_parser.rb +26 -3
- data/lib/brakeman/processors/alias_processor.rb +39 -12
- data/lib/brakeman/processors/base_processor.rb +4 -4
- data/lib/brakeman/processors/lib/file_type_detector.rb +64 -0
- data/lib/brakeman/processors/lib/rails3_config_processor.rb +16 -16
- data/lib/brakeman/processors/lib/rails4_config_processor.rb +2 -1
- data/lib/brakeman/report.rb +8 -0
- data/lib/brakeman/report/report_base.rb +0 -2
- data/lib/brakeman/report/report_csv.rb +37 -60
- data/lib/brakeman/report/report_junit.rb +2 -2
- data/lib/brakeman/report/report_sarif.rb +1 -1
- data/lib/brakeman/report/report_sonar.rb +38 -0
- data/lib/brakeman/report/report_tabs.rb +1 -1
- data/lib/brakeman/report/report_text.rb +1 -1
- data/lib/brakeman/rescanner.rb +7 -5
- data/lib/brakeman/scanner.rb +44 -18
- data/lib/brakeman/tracker.rb +6 -0
- data/lib/brakeman/tracker/config.rb +73 -0
- data/lib/brakeman/util.rb +7 -2
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +10 -2
- data/lib/brakeman/warning_codes.rb +2 -0
- metadata +8 -4
data/lib/brakeman/file_parser.rb
CHANGED
@@ -3,32 +3,31 @@ module Brakeman
|
|
3
3
|
|
4
4
|
# This class handles reading and parsing files.
|
5
5
|
class FileParser
|
6
|
-
attr_reader :file_list
|
6
|
+
attr_reader :file_list, :errors
|
7
7
|
|
8
|
-
def initialize
|
9
|
-
@
|
10
|
-
@timeout =
|
11
|
-
@
|
12
|
-
@
|
8
|
+
def initialize app_tree, timeout
|
9
|
+
@app_tree = app_tree
|
10
|
+
@timeout = timeout
|
11
|
+
@file_list = []
|
12
|
+
@errors = []
|
13
13
|
end
|
14
14
|
|
15
|
-
def parse_files list
|
16
|
-
read_files list
|
15
|
+
def parse_files list
|
16
|
+
read_files list do |path, contents|
|
17
17
|
if ast = parse_ruby(contents, path.relative)
|
18
18
|
ASTFile.new(path, ast)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def read_files list
|
24
|
-
@file_list[type] ||= []
|
25
|
-
|
23
|
+
def read_files list
|
26
24
|
list.each do |path|
|
27
25
|
file = @app_tree.file_path(path)
|
28
26
|
|
29
27
|
result = yield file, file.read
|
28
|
+
|
30
29
|
if result
|
31
|
-
@file_list
|
30
|
+
@file_list << result
|
32
31
|
end
|
33
32
|
end
|
34
33
|
end
|
@@ -43,15 +42,17 @@ module Brakeman
|
|
43
42
|
Brakeman.debug "Parsing #{path}"
|
44
43
|
RubyParser.new.parse input, path, @timeout
|
45
44
|
rescue Racc::ParseError => e
|
46
|
-
|
47
|
-
nil
|
45
|
+
error e.exception(e.message + "\nCould not parse #{path}")
|
48
46
|
rescue Timeout::Error => e
|
49
|
-
|
50
|
-
nil
|
47
|
+
error Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout")
|
51
48
|
rescue => e
|
52
|
-
|
53
|
-
nil
|
49
|
+
error e.exception(e.message + "\nWhile processing #{path}")
|
54
50
|
end
|
55
51
|
end
|
52
|
+
|
53
|
+
def error exception
|
54
|
+
@errors << exception
|
55
|
+
nil
|
56
|
+
end
|
56
57
|
end
|
57
58
|
end
|
data/lib/brakeman/options.rb
CHANGED
@@ -166,6 +166,10 @@ module Brakeman::Options
|
|
166
166
|
options[:only_files].merge files
|
167
167
|
end
|
168
168
|
|
169
|
+
opts.on "--[no-]skip-vendor", "Skip processing vendor directory (Default)" do |skip|
|
170
|
+
options[:skip_vendor] = skip
|
171
|
+
end
|
172
|
+
|
169
173
|
opts.on "--skip-libs", "Skip processing lib directory" do
|
170
174
|
options[:skip_libs] = true
|
171
175
|
end
|
@@ -229,7 +233,7 @@ module Brakeman::Options
|
|
229
233
|
|
230
234
|
opts.on "-f",
|
231
235
|
"--format TYPE",
|
232
|
-
[:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif],
|
236
|
+
[:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif, :sonar],
|
233
237
|
"Specify output formats. Default is text" do |type|
|
234
238
|
|
235
239
|
type = "s" if type == :text
|
@@ -9,7 +9,7 @@ module Brakeman
|
|
9
9
|
def initialize tracker, file_parser
|
10
10
|
@tracker = tracker
|
11
11
|
@file_parser = file_parser
|
12
|
-
@
|
12
|
+
@slim_smart = nil # Load slim/smart ?
|
13
13
|
end
|
14
14
|
|
15
15
|
def parse_template path, text
|
@@ -33,7 +33,7 @@ module Brakeman
|
|
33
33
|
end
|
34
34
|
|
35
35
|
if src and ast = @file_parser.parse_ruby(src, path)
|
36
|
-
@file_parser.file_list
|
36
|
+
@file_parser.file_list << TemplateFile.new(path, ast, name, type)
|
37
37
|
end
|
38
38
|
rescue Racc::ParseError => e
|
39
39
|
tracker.error e, "Could not parse #{path}"
|
@@ -89,6 +89,14 @@ module Brakeman
|
|
89
89
|
|
90
90
|
def parse_slim path, text
|
91
91
|
Brakeman.load_brakeman_dependency 'slim'
|
92
|
+
|
93
|
+
if @slim_smart.nil? and load_slim_smart?
|
94
|
+
@slim_smart = true
|
95
|
+
Brakeman.load_brakeman_dependency 'slim/smart'
|
96
|
+
else
|
97
|
+
@slim_smart = false
|
98
|
+
end
|
99
|
+
|
92
100
|
require_relative 'slim_embedded'
|
93
101
|
|
94
102
|
Slim::Template.new(path,
|
@@ -96,8 +104,23 @@ module Brakeman
|
|
96
104
|
:generator => Temple::Generators::RailsOutputBuffer) { text }.precompiled_template
|
97
105
|
end
|
98
106
|
|
107
|
+
def load_slim_smart?
|
108
|
+
return !@slim_smart unless @slim_smart.nil?
|
109
|
+
|
110
|
+
# Terrible hack to find
|
111
|
+
# gem "slim", "~> 3.0.1", require: ["slim", "slim/smart"]
|
112
|
+
if tracker.app_tree.exists? 'Gemfile'
|
113
|
+
gemfile_contents = tracker.app_tree.file_path('Gemfile').read
|
114
|
+
if gemfile_contents.include? 'slim/smart'
|
115
|
+
return true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
false
|
120
|
+
end
|
121
|
+
|
99
122
|
def self.parse_inline_erb tracker, text
|
100
|
-
fp = Brakeman::FileParser.new(tracker)
|
123
|
+
fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
|
101
124
|
tp = self.new(tracker, fp)
|
102
125
|
src = tp.parse_erb '_inline_', text
|
103
126
|
type = tp.erubis? ? :erubis : :erb
|
@@ -161,6 +161,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
161
161
|
ARRAY_CONST = s(:const, :Array)
|
162
162
|
HASH_CONST = s(:const, :Hash)
|
163
163
|
RAILS_TEST = s(:call, s(:call, s(:const, :Rails), :env), :test?)
|
164
|
+
RAILS_DEV = s(:call, s(:call, s(:const, :Rails), :env), :development?)
|
164
165
|
|
165
166
|
#Process a method call.
|
166
167
|
def process_call exp
|
@@ -182,11 +183,17 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
182
183
|
return exp
|
183
184
|
end
|
184
185
|
|
186
|
+
# If x(*[1,2,3]) change to x(1,2,3)
|
187
|
+
# if that's the only argument
|
188
|
+
if splat_array? exp.first_arg and exp.second_arg.nil?
|
189
|
+
exp.arglist = exp.first_arg[1].sexp_body
|
190
|
+
end
|
191
|
+
|
185
192
|
target = exp.target
|
186
193
|
method = exp.method
|
187
194
|
first_arg = exp.first_arg
|
188
195
|
|
189
|
-
if method == :send or method == :try
|
196
|
+
if method == :send or method == :__send__ or method == :try
|
190
197
|
collapse_send_call exp, first_arg
|
191
198
|
end
|
192
199
|
|
@@ -194,11 +201,11 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
194
201
|
res = process_or_simple_operation(exp)
|
195
202
|
return res if res
|
196
203
|
elsif target == ARRAY_CONST and method == :new
|
197
|
-
return Sexp.new(:array, *exp.args)
|
204
|
+
return Sexp.new(:array, *exp.args).line(exp.line)
|
198
205
|
elsif target == HASH_CONST and method == :new and first_arg.nil? and !node_type?(@exp_context.last, :iter)
|
199
|
-
return Sexp.new(:hash)
|
200
|
-
elsif exp == RAILS_TEST
|
201
|
-
return Sexp.new(:false)
|
206
|
+
return Sexp.new(:hash).line(exp.line)
|
207
|
+
elsif exp == RAILS_TEST or exp == RAILS_DEV
|
208
|
+
return Sexp.new(:false).line(exp.line)
|
202
209
|
end
|
203
210
|
|
204
211
|
#See if it is possible to simplify some basic cases
|
@@ -236,7 +243,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
236
243
|
env[target_var] = target
|
237
244
|
return target
|
238
245
|
elsif string? target and string_interp? first_arg
|
239
|
-
exp = Sexp.new(:dstr, target.value + first_arg[1]).concat(first_arg.sexp_body(2))
|
246
|
+
exp = Sexp.new(:dstr, target.value + first_arg[1]).concat(first_arg.sexp_body(2)).line(exp.line)
|
240
247
|
env[target_var] = exp
|
241
248
|
elsif string? first_arg and string_interp? target
|
242
249
|
if string? target.last
|
@@ -287,7 +294,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
287
294
|
|
288
295
|
# Painful conversion of Array#join into string interpolation
|
289
296
|
def process_array_join array, join_str
|
290
|
-
result = s()
|
297
|
+
result = s().line(array.line)
|
291
298
|
|
292
299
|
join_value = if string? join_str
|
293
300
|
join_str.value
|
@@ -325,11 +332,11 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
325
332
|
result.unshift combined_first
|
326
333
|
|
327
334
|
# Have to fix up strings that follow interpolation
|
328
|
-
result.reduce(s(:dstr)) do |memo, e|
|
335
|
+
result.reduce(s(:dstr).line(array.line)) do |memo, e|
|
329
336
|
if string? e and node_type? memo.last, :evstr
|
330
337
|
e.value = "#{join_value}#{e.value}"
|
331
338
|
elsif join_value and node_type? memo.last, :evstr and node_type? e, :evstr
|
332
|
-
memo << s(:str, join_value)
|
339
|
+
memo << s(:str, join_value).line(e.line)
|
333
340
|
end
|
334
341
|
|
335
342
|
memo << e
|
@@ -340,12 +347,29 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
340
347
|
if item.is_a? String
|
341
348
|
"#{item}#{join_value}"
|
342
349
|
elsif string? item or symbol? item or number? item
|
343
|
-
s(:str, "#{item.value}#{join_value}")
|
350
|
+
s(:str, "#{item.value}#{join_value}").line(item.line)
|
344
351
|
else
|
345
|
-
s(:evstr, item)
|
352
|
+
s(:evstr, item).line(item.line)
|
346
353
|
end
|
347
354
|
end
|
348
355
|
|
356
|
+
TEMP_FILE_CLASS = s(:const, :Tempfile)
|
357
|
+
|
358
|
+
def temp_file_open? exp
|
359
|
+
call? exp and
|
360
|
+
exp.target == TEMP_FILE_CLASS and
|
361
|
+
exp.method == :open
|
362
|
+
end
|
363
|
+
|
364
|
+
def temp_file_new line
|
365
|
+
s(:call, TEMP_FILE_CLASS, :new).line(line)
|
366
|
+
end
|
367
|
+
|
368
|
+
def splat_array? exp
|
369
|
+
node_type? exp, :splat and
|
370
|
+
node_type? exp[1], :array
|
371
|
+
end
|
372
|
+
|
349
373
|
def process_iter exp
|
350
374
|
@exp_context.push exp
|
351
375
|
exp[1] = process exp.block_call
|
@@ -363,6 +387,9 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
363
387
|
# Iterating over an array of all literal values
|
364
388
|
local = Sexp.new(:lvar, block_args.last)
|
365
389
|
env.current[local] = safe_literal(exp.line)
|
390
|
+
elsif temp_file_open? call
|
391
|
+
local = Sexp.new(:lvar, block_args.last)
|
392
|
+
env.current[local] = temp_file_new(exp.line)
|
366
393
|
else
|
367
394
|
block_args.each do |e|
|
368
395
|
#Force block arg(s) to be local
|
@@ -663,7 +690,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
663
690
|
end
|
664
691
|
end
|
665
692
|
else
|
666
|
-
new_value = process s(:call, s(:call, target_var, :[], index), exp[3], value)
|
693
|
+
new_value = process s(:call, s(:call, target_var, :[], index), exp[3], value).line(exp.line)
|
667
694
|
|
668
695
|
env[match] = new_value
|
669
696
|
end
|
@@ -8,7 +8,7 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
|
|
8
8
|
include Brakeman::SafeCallHelper
|
9
9
|
include Brakeman::Util
|
10
10
|
|
11
|
-
IGNORE = Sexp.new
|
11
|
+
IGNORE = Sexp.new(:ignore).line(0)
|
12
12
|
|
13
13
|
#Return a new Processor.
|
14
14
|
def initialize tracker
|
@@ -216,7 +216,7 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
|
|
216
216
|
#
|
217
217
|
#And also :layout for inside templates
|
218
218
|
def find_render_type call, in_view = false
|
219
|
-
rest = Sexp.new(:hash)
|
219
|
+
rest = Sexp.new(:hash).line(call.line)
|
220
220
|
type = nil
|
221
221
|
value = nil
|
222
222
|
first_arg = call.first_arg
|
@@ -236,7 +236,7 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
|
|
236
236
|
end
|
237
237
|
elsif first_arg.is_a? Symbol or first_arg.is_a? String
|
238
238
|
type = :action
|
239
|
-
value = Sexp.new(:lit, first_arg.to_sym)
|
239
|
+
value = Sexp.new(:lit, first_arg.to_sym).line(call.line)
|
240
240
|
elsif first_arg.nil?
|
241
241
|
type = :default
|
242
242
|
elsif not hash? first_arg
|
@@ -293,6 +293,6 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
|
|
293
293
|
@tracker.processor.process_template(template_name, ast, type, nil, @current_file)
|
294
294
|
@tracker.processor.process_template_alias(@tracker.templates[template_name])
|
295
295
|
|
296
|
-
return s(:lit, template_name), options
|
296
|
+
return s(:lit, template_name).line(value.line), options
|
297
297
|
end
|
298
298
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Brakeman
|
2
|
+
class FileTypeDetector < BaseProcessor
|
3
|
+
def initialize
|
4
|
+
super(nil)
|
5
|
+
reset
|
6
|
+
end
|
7
|
+
|
8
|
+
def detect_type(file)
|
9
|
+
reset
|
10
|
+
process(file.ast)
|
11
|
+
|
12
|
+
if @file_type.nil?
|
13
|
+
@file_type = guess_from_path(file.path.relative)
|
14
|
+
end
|
15
|
+
|
16
|
+
@file_type || :libs
|
17
|
+
end
|
18
|
+
|
19
|
+
MODEL_CLASSES = [
|
20
|
+
:'ActiveRecord::Base',
|
21
|
+
:ApplicationRecord
|
22
|
+
]
|
23
|
+
|
24
|
+
def process_class exp
|
25
|
+
name = class_name(exp.class_name)
|
26
|
+
parent = class_name(exp.parent_name)
|
27
|
+
|
28
|
+
if name.match(/Controller$/)
|
29
|
+
@file_type = :controllers
|
30
|
+
return exp
|
31
|
+
elsif MODEL_CLASSES.include? parent
|
32
|
+
@file_type = :models
|
33
|
+
return exp
|
34
|
+
end
|
35
|
+
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
def guess_from_path path
|
40
|
+
case
|
41
|
+
when path.include?('app/models')
|
42
|
+
:models
|
43
|
+
when path.include?('app/controllers')
|
44
|
+
:controllers
|
45
|
+
when path.include?('config/initializers')
|
46
|
+
:initializers
|
47
|
+
when path.include?('lib/')
|
48
|
+
:libs
|
49
|
+
when path.match?(%r{config/environments/(?!production\.rb)$})
|
50
|
+
:skip
|
51
|
+
when path.match?(%r{environments/production\.rb$})
|
52
|
+
:skip
|
53
|
+
when path.match?(%r{application\.rb$})
|
54
|
+
:skip
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def reset
|
61
|
+
@file_type = nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -57,6 +57,20 @@ class Brakeman::Rails3ConfigProcessor < Brakeman::BasicProcessor
|
|
57
57
|
exp
|
58
58
|
end
|
59
59
|
|
60
|
+
#Look for configuration settings that
|
61
|
+
#are just a call like
|
62
|
+
#
|
63
|
+
# config.load_defaults 5.2
|
64
|
+
def process_call exp
|
65
|
+
return exp unless @inside_config
|
66
|
+
|
67
|
+
if exp.target == RAILS_CONFIG and exp.first_arg
|
68
|
+
@tracker.config.rails[exp.method] = exp.first_arg
|
69
|
+
end
|
70
|
+
|
71
|
+
exp
|
72
|
+
end
|
73
|
+
|
60
74
|
#Look for configuration settings
|
61
75
|
def process_attrasgn exp
|
62
76
|
return exp unless @inside_config
|
@@ -71,22 +85,8 @@ class Brakeman::Rails3ConfigProcessor < Brakeman::BasicProcessor
|
|
71
85
|
@tracker.config.rails[attribute] = exp.first_arg
|
72
86
|
end
|
73
87
|
elsif include_rails_config? exp
|
74
|
-
|
75
|
-
|
76
|
-
options[0..-2].each do |o|
|
77
|
-
level[o] ||= {}
|
78
|
-
|
79
|
-
option = level[o]
|
80
|
-
|
81
|
-
if not option.is_a? Hash
|
82
|
-
Brakeman.debug "[Notice] Skipping config setting: #{options.map(&:to_s).join(".")}"
|
83
|
-
return exp
|
84
|
-
end
|
85
|
-
|
86
|
-
level = level[o]
|
87
|
-
end
|
88
|
-
|
89
|
-
level[options.last] = exp.first_arg
|
88
|
+
options_path = get_rails_config exp
|
89
|
+
@tracker.config.set_rails_config(exp.first_arg, *options_path)
|
90
90
|
end
|
91
91
|
|
92
92
|
exp
|
@@ -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
|
data/lib/brakeman/report.rb
CHANGED
@@ -45,6 +45,9 @@ class Brakeman::Report
|
|
45
45
|
Brakeman::Report::JUnit
|
46
46
|
when :to_sarif
|
47
47
|
return self.to_sarif
|
48
|
+
when :to_sonar
|
49
|
+
require_report 'sonar'
|
50
|
+
Brakeman::Report::Sonar
|
48
51
|
else
|
49
52
|
raise "Invalid format: #{format}. Should be one of #{VALID_FORMATS.inspect}"
|
50
53
|
end
|
@@ -69,6 +72,11 @@ class Brakeman::Report
|
|
69
72
|
generate Brakeman::Report::JSON
|
70
73
|
end
|
71
74
|
|
75
|
+
def to_sonar
|
76
|
+
require_report 'sonar'
|
77
|
+
generate Brakeman::Report::Sonar
|
78
|
+
end
|
79
|
+
|
72
80
|
def to_table
|
73
81
|
require_report 'table'
|
74
82
|
generate Brakeman::Report::Table
|