brakeman-lib 6.2.2 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,15 +6,15 @@ require 'brakeman/differ'
6
6
  class Brakeman::Rescanner < Brakeman::Scanner
7
7
  include Brakeman::Util
8
8
  KNOWN_TEMPLATE_EXTENSIONS = Brakeman::TemplateParser::KNOWN_TEMPLATE_EXTENSIONS
9
- SCAN_ORDER = [:gemfile, :config, :initializer, :lib, :routes, :template,
10
- :model, :controller]
11
9
 
12
10
  #Create new Rescanner to scan changed files
13
11
  def initialize options, processor, changed_files
14
- super(options, processor)
12
+ super(options)
13
+
14
+ @old_tracker = processor.tracked_events
15
15
 
16
16
  @paths = changed_files.map {|f| tracker.app_tree.file_path(f) }
17
- @old_results = tracker.filtered_warnings #Old warnings from previous scan
17
+ @old_results = @old_tracker.filtered_warnings.dup #Old warnings from previous scan
18
18
  @changes = nil #True if files had to be rescanned
19
19
  @reindex = Set.new
20
20
  end
@@ -24,379 +24,55 @@ class Brakeman::Rescanner < Brakeman::Scanner
24
24
  def recheck
25
25
  rescan if @changes.nil?
26
26
 
27
- tracker.run_checks if @changes
28
-
29
- Brakeman::RescanReport.new @old_results, tracker
27
+ if @changes
28
+ tracker.run_checks
29
+ Brakeman.filter_warnings(tracker, options) # Actually sets ignored_filter
30
+ Brakeman::RescanReport.new @old_results, tracker
31
+ else
32
+ # No changes, fake no new results
33
+ Brakeman::RescanReport.new @old_results, @old_tracker
34
+ end
30
35
  end
31
36
 
32
37
  #Rescans changed files
33
38
  def rescan
34
- tracker.template_cache.clear
39
+ raise "Cannot rescan: set `support_rescanning: true`" unless @old_tracker.options[:support_rescanning]
35
40
 
36
- paths_by_type = {}
41
+ tracker.file_cache = @old_tracker.pristine_file_cache
37
42
 
38
- SCAN_ORDER.each do |type|
39
- paths_by_type[type] = []
40
- end
43
+ template_paths = []
44
+ ruby_paths = []
41
45
 
46
+ # Remove changed files from the cache.
47
+ # Collect files to re-parse.
42
48
  @paths.each do |path|
43
- type = file_type(path)
44
- paths_by_type[type] << path unless type == :unknown
45
- end
46
-
47
- @changes = false
48
-
49
- SCAN_ORDER.each do |type|
50
- paths_by_type[type].each do |path|
51
- Brakeman.debug "Rescanning #{path} as #{type}"
52
-
53
- if rescan_file path, type
54
- @changes = true
55
- end
56
- end
57
- end
58
-
59
- if @changes and not @reindex.empty?
60
- tracker.reindex_call_sites @reindex
61
- end
62
-
63
- self
64
- end
65
-
66
- #Rescans a single file
67
- def rescan_file path, type = nil
68
- type ||= file_type path
69
-
70
- unless path.exists?
71
- return rescan_deleted_file path, type
72
- end
73
-
74
- case type
75
- when :controller
76
- rescan_controller path
77
- when :template
78
- rescan_template path
79
- when :model
80
- rescan_model path
81
- when :lib
82
- rescan_lib path
83
- when :config
84
- process_config
85
- when :initializer
86
- rescan_initializer path
87
- when :routes
88
- rescan_routes
89
- when :gemfile
90
- if tracker.config.has_gem? :rails_xss and tracker.config.escape_html?
91
- tracker.config.escape_html = false
92
- end
93
-
94
- process_gems
95
- else
96
- return false #Nothing to do, file hopefully does not need to be rescanned
97
- end
98
-
99
- true
100
- end
101
-
102
- def rescan_controller path
103
- controller = tracker.reset_controller path
104
- paths = controller.nil? ? [path] : controller.files
105
- parse_ruby_files(paths).each do |astfile|
106
- process_controller astfile
107
- end
108
-
109
- #Process data flow and template rendering
110
- #from the controller
111
- tracker.controllers.each do |name, controller|
112
- if controller.files.include?(path)
113
- tracker.templates.each do |template_name, template|
114
- next unless template.render_path
115
- if template.render_path.include_controller? name
116
- tracker.reset_template template_name
117
- end
118
- end
119
-
120
- controller.src.each do |file, src|
121
- @processor.process_controller_alias controller.name, src, nil, file
122
- end
123
- end
124
- end
125
-
126
- @reindex << :templates << :controllers
127
- end
128
-
129
- def rescan_template path
130
- return unless path.relative.match KNOWN_TEMPLATE_EXTENSIONS and path.exists?
131
-
132
- template_name = template_path_to_name(path)
133
-
134
- tracker.reset_template template_name
135
- fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
136
- template_parser = Brakeman::TemplateParser.new(tracker, fp)
137
- template_parser.parse_template path, path.read
138
- tracker.add_errors(fp.errors)
139
- process_template fp.file_list.first
140
-
141
- @processor.process_template_alias tracker.templates[template_name]
142
-
143
- rescan = Set.new
49
+ file_cache.delete path
144
50
 
145
- #Search for processed template and process it.
146
- #Search for rendered versions of template and re-render (if necessary)
147
- tracker.templates.each do |_name, template|
148
- if template.file == path or template.file.nil?
149
- next unless template.render_path and template.name.to_sym == template_name.to_sym
150
-
151
- template.render_path.each do |from|
152
- case from[:type]
153
- when :template
154
- rescan << [:template, from[:name]]
155
- when :controller
156
- rescan << [:controller, from[:class], from[:method]]
157
- end
158
- end
159
- end
160
- end
161
-
162
- rescan.each do |r|
163
- if r[0] == :controller
164
- controller = tracker.controllers[r[1]]
165
-
166
- controller.src.each do |file, src|
167
- unless @paths.include? file
168
- @processor.process_controller_alias controller.name, src, r[2], file
169
- end
51
+ if path.exists?
52
+ if path.relative.match? KNOWN_TEMPLATE_EXTENSIONS
53
+ template_paths << path
54
+ elsif path.relative.end_with? '.rb'
55
+ ruby_paths << path
170
56
  end
171
- elsif r[0] == :template
172
- template = tracker.templates[r[1]]
173
-
174
- rescan_template template.file
175
57
  end
176
58
  end
177
59
 
178
- @reindex << :templates
179
- end
180
-
181
- def rescan_model path
182
- num_models = tracker.models.length
183
- model = tracker.reset_model path
184
- paths = model.nil? ? [path] : model.files
185
- parse_ruby_files(paths).each do |astfile|
186
- process_model astfile.path, astfile.ast
187
- end
188
-
189
- #Only need to rescan other things if a model is added or removed
190
- if num_models != tracker.models.length
191
- process_template_data_flows
192
- process_controller_data_flows
193
- @reindex << :templates << :controllers
194
- end
195
-
196
- @reindex << :models
197
- end
198
-
199
- def rescan_lib path
200
- lib = tracker.reset_lib path
201
- paths = lib.nil? ? [path] : lib.files
202
- parse_ruby_files(paths).each do |astfile|
203
- process_lib astfile
204
- end
205
-
206
- lib = nil
207
-
208
- tracker.libs.each do |_name, library|
209
- if library.files.include?(path)
210
- lib = library
211
- break
212
- end
213
- end
214
-
215
- rescan_mixin lib if lib
216
- end
217
-
218
- def rescan_routes
219
- # Routes affect which controller methods are treated as actions
220
- # which affects which templates are rendered, so routes, controllers,
221
- # and templates rendered from controllers must be rescanned
222
- tracker.reset_routes
223
- tracker.reset_templates :only_rendered => true
224
- process_routes
225
- process_controller_data_flows
226
- @reindex << :controllers << :templates
227
- end
228
-
229
- def rescan_initializer path
230
- tracker.reset_initializer path
231
-
232
- parse_ruby_files([path]).each do |astfile|
233
- process_initializer astfile
234
- end
235
-
236
- @reindex << :initializers
237
- end
238
-
239
- #Handle rescanning when a file is deleted
240
- def rescan_deleted_file path, type
241
- case type
242
- when :controller
243
- rescan_controller path
244
- when :template
245
- rescan_deleted_template path
246
- when :model
247
- rescan_model path
248
- when :lib
249
- rescan_deleted_lib path
250
- when :initializer
251
- rescan_deleted_initializer path
60
+ # Try to skip rescanning files that do not impact
61
+ # Brakeman results
62
+ if @paths.all? { |path| ignorable? path }
63
+ @changes = false
252
64
  else
253
- if remove_deleted_file path
254
- return true
255
- else
256
- Brakeman.notify "Ignoring deleted file: #{path}"
257
- end
65
+ @changes = true
66
+ process(ruby_paths:, template_paths:)
258
67
  end
259
68
 
260
- true
261
- end
262
-
263
- def rescan_deleted_template path
264
- return unless path.relative.match KNOWN_TEMPLATE_EXTENSIONS
265
-
266
- template_name = template_path_to_name(path)
267
-
268
- #Remove template
269
- tracker.reset_template template_name
270
-
271
- #Remove any rendered versions, or partials rendered from it
272
- tracker.templates.delete_if do |_name, template|
273
- template.file == path or template.name.to_sym == template_name.to_sym
274
- end
275
- end
276
-
277
- def rescan_deleted_lib path
278
- deleted_lib = nil
279
-
280
- tracker.libs.delete_if do |_name, lib|
281
- if lib.files.include?(path)
282
- deleted_lib = lib
283
- true
284
- end
285
- end
286
-
287
- rescan_mixin deleted_lib if deleted_lib
288
- end
289
-
290
- def rescan_deleted_initializer path
291
- tracker.initializers.delete Pathname.new(path).basename.to_s
292
- end
293
-
294
- #Check controllers, templates, models and libs for data from file
295
- #and delete it.
296
- def remove_deleted_file path
297
- deleted = false
298
-
299
- [:controllers, :models, :libs].each do |collection|
300
- tracker.send(collection).delete_if do |_name, data|
301
- if data.files.include?(path)
302
- deleted = true
303
- true
304
- end
305
- end
306
- end
307
-
308
- tracker.templates.delete_if do |_name, data|
309
- if data.file == path
310
- deleted = true
311
- true
312
- end
313
- end
314
-
315
- deleted
316
- end
317
-
318
- #Guess at what kind of file the path contains
319
- def file_type path
320
- case path
321
- when /\/app\/controllers/
322
- :controller
323
- when /\/app\/views/
324
- :template
325
- when /\/app\/models/
326
- :model
327
- when /\/lib/
328
- :lib
329
- when /\/config\/initializers/
330
- :initializer
331
- when /config\/routes\.rb/
332
- :routes
333
- when /\/config\/.+\.(rb|yml)/
334
- :config
335
- when /\.ruby-version/
336
- :config
337
- when /Gemfile|gems\./
338
- :gemfile
339
- else
340
- :unknown
341
- end
69
+ self
342
70
  end
343
71
 
344
- def rescan_mixin lib
345
- method_names = []
346
-
347
- lib.each_method do |name, _meth|
348
- method_names << name
349
- end
72
+ IGNORE_PATTERN = /\.(md|txt|js|ts|tsx|json|scss|css|xml|ru|png|jpg|pdf|gif|svg|webm|ttf|sql)$/
350
73
 
351
- to_rescan = []
352
-
353
- #Rescan controllers that mixed in library
354
- tracker.controllers.each do |_name, controller|
355
- if controller.includes.include? lib.name
356
- controller.files.each do |path|
357
- unless @paths.include? path
358
- to_rescan << path
359
- end
360
- end
361
- end
362
- end
363
-
364
- to_rescan.each do |controller|
365
- tracker.reset_controller controller
366
- rescan_file controller
367
- end
368
-
369
- to_rescan = []
370
-
371
- #Check if a method from this mixin was used to render a template.
372
- #This is not precise, because a different controller might have the
373
- #same method...
374
- tracker.templates.each do |name, template|
375
- next unless template.render_path
376
-
377
- if template.render_path.include_any_method? method_names
378
- name.to_s.match(/^([^.]+)/)
379
-
380
- original = tracker.templates[$1.to_sym]
381
-
382
- if original
383
- to_rescan << [name, original.file]
384
- end
385
- end
386
- end
387
-
388
- to_rescan.each do |template|
389
- tracker.reset_template template[0]
390
- rescan_file template[1]
391
- end
392
- end
393
-
394
- def parse_ruby_files list
395
- paths = list.select(&:exists?)
396
- file_parser = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout], tracker.options[:parallel_checks])
397
- file_parser.parse_files paths
398
- tracker.add_errors(file_parser.errors)
399
- file_parser.file_list
74
+ def ignorable? path
75
+ path.relative.match? IGNORE_PATTERN
400
76
  end
401
77
  end
402
78
 
@@ -452,37 +128,11 @@ class Brakeman::RescanReport
452
128
  end
453
129
 
454
130
  #Output total, fixed, and new warnings
455
- def to_s(verbose = false)
456
- Brakeman.load_brakeman_dependency 'terminal-table'
457
-
458
- if !verbose
459
- <<-OUTPUT
460
- Total warnings: #{all_warnings.length}
461
- Fixed warnings: #{fixed_warnings.length}
462
- New warnings: #{new_warnings.length}
463
- OUTPUT
464
- else
465
- #Eventually move this to different method, or make default to_s
466
- out = ""
467
-
468
- {:fixed => fixed_warnings, :new => new_warnings, :existing => existing_warnings}.each do |warning_type, warnings|
469
- if warnings.length > 0
470
- out << "#{warning_type.to_s.titleize} warnings: #{warnings.length}\n"
471
-
472
- table = Terminal::Table.new(:headings => ["Confidence", "Class", "Method", "Warning Type", "Message"]) do |t|
473
- warnings.sort_by { |w| w.confidence}.each do |warning|
474
- w = warning.to_row
475
-
476
- w["Confidence"] = Brakeman::Report::TEXT_CONFIDENCE[w["Confidence"]]
477
-
478
- t << [w["Confidence"], w["Class"], w["Method"], w["Warning Type"], w["Message"]]
479
- end
480
- end
481
- out << truncate_table(table.to_s)
482
- end
483
- end
484
-
485
- out
486
- end
131
+ def to_s
132
+ <<~OUTPUT
133
+ Total warnings: #{all_warnings.length}
134
+ Fixed warnings: #{fixed_warnings.length}
135
+ New warnings: #{new_warnings.length}
136
+ OUTPUT
487
137
  end
488
138
  end
@@ -7,6 +7,7 @@ begin
7
7
  require 'brakeman/file_parser'
8
8
  require 'brakeman/parsers/template_parser'
9
9
  require 'brakeman/processors/lib/file_type_detector'
10
+ require 'brakeman/tracker/file_cache'
10
11
  rescue LoadError => e
11
12
  $stderr.puts e.message
12
13
  $stderr.puts "Please install the appropriate dependency."
@@ -38,6 +39,10 @@ class Brakeman::Scanner
38
39
  @processor.tracked_events
39
40
  end
40
41
 
42
+ def file_cache
43
+ tracker.file_cache
44
+ end
45
+
41
46
  def process_step description
42
47
  Brakeman.notify "#{description}...".ljust(40)
43
48
 
@@ -67,7 +72,7 @@ class Brakeman::Scanner
67
72
  end
68
73
 
69
74
  #Process everything in the Rails application
70
- def process
75
+ def process(ruby_paths: nil, template_paths: nil)
71
76
  process_step 'Processing gems' do
72
77
  process_gems
73
78
  end
@@ -77,14 +82,30 @@ class Brakeman::Scanner
77
82
  process_config
78
83
  end
79
84
 
85
+ # -
86
+ # If ruby_paths or template_paths are set,
87
+ # only parse those files. The rest will be fetched
88
+ # from the file cache.
89
+ #
90
+ # Otherwise, parse everything normally.
91
+ #
92
+ astfiles = nil
93
+ process_step 'Finding files' do
94
+ ruby_paths ||= tracker.app_tree.ruby_file_paths
95
+ template_paths ||= tracker.app_tree.template_paths
96
+ end
97
+
80
98
  process_step 'Parsing files' do
81
- parse_files
99
+ astfiles = parse_files(ruby_paths: ruby_paths, template_paths: template_paths)
82
100
  end
83
101
 
84
102
  process_step 'Detecting file types' do
85
- detect_file_types
103
+ detect_file_types(astfiles)
86
104
  end
87
105
 
106
+ tracker.save_file_cache! if support_rescanning?
107
+ # -
108
+
88
109
  process_step 'Processing initializers' do
89
110
  process_initializers
90
111
  end
@@ -124,44 +145,37 @@ class Brakeman::Scanner
124
145
  tracker
125
146
  end
126
147
 
127
- def parse_files
148
+ def parse_files(ruby_paths:, template_paths:)
128
149
  fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout], tracker.options[:parallel_checks], tracker.options[:use_prism])
129
150
 
130
- fp.parse_files tracker.app_tree.ruby_file_paths
151
+ fp.parse_files ruby_paths
131
152
 
132
153
  template_parser = Brakeman::TemplateParser.new(tracker, fp)
133
154
 
134
- fp.read_files(@app_tree.template_paths) do |path, contents|
135
- template_parser.parse_template path, contents
155
+ fp.read_files(template_paths) do |path, contents|
156
+ template_parser.parse_template(path, contents)
136
157
  end
137
158
 
138
159
  # Collect errors raised during parsing
139
160
  tracker.add_errors(fp.errors)
140
161
 
141
- @parsed_files = fp.file_list
162
+ fp.file_list
142
163
  end
143
164
 
144
- def detect_file_types
145
- @file_list = {
146
- controllers: [],
147
- initializers: [],
148
- libs: [],
149
- models: [],
150
- templates: [],
151
- }
152
-
165
+ def detect_file_types(astfiles)
153
166
  detector = Brakeman::FileTypeDetector.new
154
167
 
155
- @parsed_files.each do |file|
168
+ astfiles.each do |file|
156
169
  if file.is_a? Brakeman::TemplateParser::TemplateFile
157
- @file_list[:templates] << file
170
+ file_cache.add_file file, :template
158
171
  else
159
172
  type = detector.detect_type(file)
173
+
160
174
  unless type == :skip
161
- if @file_list[type].nil?
162
- raise type.to_s
175
+ if file_cache.valid_type? type
176
+ file_cache.add_file(file, type)
163
177
  else
164
- @file_list[type] << file
178
+ raise "Unexpected file type: #{type.inspect}"
165
179
  end
166
180
  end
167
181
  end
@@ -268,8 +282,8 @@ class Brakeman::Scanner
268
282
  #
269
283
  #Adds parsed information to tracker.initializers
270
284
  def process_initializers
271
- track_progress @file_list[:initializers] do |init|
272
- process_step_file init[:path] do
285
+ track_progress file_cache.initializers do |path, init|
286
+ process_step_file path do
273
287
  process_initializer init
274
288
  end
275
289
  end
@@ -289,8 +303,10 @@ class Brakeman::Scanner
289
303
  return
290
304
  end
291
305
 
292
- track_progress @file_list[:libs] do |lib|
293
- process_step_file lib.path do
306
+ libs = file_cache.libs.sort_by { |path, _| path }
307
+
308
+ track_progress libs do |path, lib|
309
+ process_step_file path do
294
310
  process_lib lib
295
311
  end
296
312
  end
@@ -322,15 +338,17 @@ class Brakeman::Scanner
322
338
  #
323
339
  #Adds processed controllers to tracker.controllers
324
340
  def process_controllers
325
- track_progress @file_list[:controllers] do |controller|
326
- process_step_file controller.path do
341
+ controllers = file_cache.controllers.sort_by { |path, _| path }
342
+
343
+ track_progress controllers do |path, controller|
344
+ process_step_file path do
327
345
  process_controller controller
328
346
  end
329
347
  end
330
348
  end
331
349
 
332
350
  def process_controller_data_flows
333
- controllers = tracker.controllers.sort_by { |name, _| name.to_s }
351
+ controllers = tracker.controllers.sort_by { |name, _| name }
334
352
 
335
353
  track_progress controllers, "controllers" do |name, controller|
336
354
  process_step_file name do
@@ -356,10 +374,10 @@ class Brakeman::Scanner
356
374
  #
357
375
  #Adds processed views to tracker.views
358
376
  def process_templates
359
- templates = @file_list[:templates].sort_by { |t| t[:path] }
377
+ templates = file_cache.templates.sort_by { |path, _| path }
360
378
 
361
- track_progress templates, "templates" do |template|
362
- process_step_file template[:path] do
379
+ track_progress templates, "templates" do |path, template|
380
+ process_step_file path do
363
381
  process_template template
364
382
  end
365
383
  end
@@ -370,7 +388,7 @@ class Brakeman::Scanner
370
388
  end
371
389
 
372
390
  def process_template_data_flows
373
- templates = tracker.templates.sort_by { |name, _| name.to_s }
391
+ templates = tracker.templates.sort_by { |name, _| name }
374
392
 
375
393
  track_progress templates, "templates" do |name, template|
376
394
  process_step_file name do
@@ -383,15 +401,17 @@ class Brakeman::Scanner
383
401
  #
384
402
  #Adds the processed models to tracker.models
385
403
  def process_models
386
- track_progress @file_list[:models] do |model|
387
- process_step_file model[:path] do
388
- process_model model[:path], model[:ast]
404
+ models = file_cache.models.sort_by { |path, _| path }
405
+
406
+ track_progress models do |path, model|
407
+ process_step_file path do
408
+ process_model model
389
409
  end
390
410
  end
391
411
  end
392
412
 
393
- def process_model path, ast
394
- @processor.process_model(ast, path)
413
+ def process_model astfile
414
+ @processor.process_model(astfile.ast, astfile.path)
395
415
  end
396
416
 
397
417
  def track_progress list, type = "files"
@@ -420,6 +440,10 @@ class Brakeman::Scanner
420
440
  tracker.error(e)
421
441
  nil
422
442
  end
443
+
444
+ def support_rescanning?
445
+ tracker.options[:support_rescanning]
446
+ end
423
447
  end
424
448
 
425
449
  # This is to allow operation without loading the Haml library