brakeman-lib 6.2.2 → 7.0.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.
@@ -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