brakeman 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -65,6 +65,42 @@ class Brakeman::FindAllCalls < Brakeman::BaseProcessor
65
65
  exp
66
66
  end
67
67
 
68
+ #Calls to render() are converted to s(:render, ...) but we would
69
+ #like them in the call cache still for speed
70
+ def process_render exp
71
+ process exp[-1] if sexp? exp[-1]
72
+
73
+ call = { :target => nil, :method => :render, :call => exp, :nested => false }
74
+
75
+ if @current_template
76
+ call[:location] = [:template, @current_template]
77
+ else
78
+ call[:location] = [:class, @current_class, @current_method]
79
+ end
80
+
81
+ @calls << call
82
+
83
+ exp
84
+ end
85
+
86
+ #Technically, `` is call to Kernel#`
87
+ #But we just need them in the call cache for speed
88
+ def process_dxstr exp
89
+ process exp[-1] if sexp? exp[-1]
90
+
91
+ call = { :target => nil, :method => :`, :call => exp, :nested => false }
92
+
93
+ if @current_template
94
+ call[:location] = [:template, @current_template]
95
+ else
96
+ call[:location] = [:class, @current_class, @current_method]
97
+ end
98
+
99
+ @calls << call
100
+
101
+ exp
102
+ end
103
+
68
104
  #Process an assignment like a call
69
105
  def process_attrasgn exp
70
106
  process_call exp
@@ -153,6 +153,7 @@ class Brakeman::Rails3RoutesProcessor < Brakeman::BaseProcessor
153
153
 
154
154
  self.current_controller = controller
155
155
  @tracker.routes[@current_controller] << action.to_sym
156
+ break
156
157
  end
157
158
  end
158
159
  end
@@ -73,7 +73,7 @@ class Brakeman::ModelProcessor < Brakeman::BaseProcessor
73
73
  else
74
74
  if @model
75
75
  @model[:options][method] ||= []
76
- @model[:options][method] << process(args)
76
+ @model[:options][method] << args
77
77
  end
78
78
  end
79
79
  end
@@ -36,7 +36,7 @@ class Brakeman::Report
36
36
  end
37
37
 
38
38
  #Generate summary table of what was parsed
39
- def generate_overview
39
+ def generate_overview html = false
40
40
  templates = Set.new(@tracker.templates.map {|k,v| v[:name].to_s[/[^.]+/]}).length
41
41
  warnings = checks.warnings.length +
42
42
  checks.controller_warnings.length +
@@ -49,7 +49,7 @@ class Brakeman::Report
49
49
  unless tracker.options[:output_format] == :to_csv
50
50
  summary = warnings_summary
51
51
 
52
- if tracker.options[:output_format] == :to_html
52
+ if html
53
53
  warnings = "#{warnings} <span class='high-confidence'>(#{summary[:high_confidence]})</span>"
54
54
  else
55
55
  warnings = "#{warnings} (#{summary[:high_confidence]})"
@@ -77,7 +77,7 @@ class Brakeman::Report
77
77
  end
78
78
 
79
79
  #Generate table of errors or return nil if no errors
80
- def generate_errors
80
+ def generate_errors html = false
81
81
  unless tracker.errors.empty?
82
82
  table = Ruport::Data::Table(["Error", "Location"])
83
83
 
@@ -85,7 +85,7 @@ class Brakeman::Report
85
85
  tracker.errors.each do |w|
86
86
  p w if tracker.options[:debug]
87
87
 
88
- if tracker.options[:output_format] == :to_html
88
+ if html
89
89
  w[:error] = CGI.escapeHTML w[:error]
90
90
  end
91
91
 
@@ -99,13 +99,13 @@ class Brakeman::Report
99
99
  end
100
100
 
101
101
  #Generate table of general security warnings
102
- def generate_warnings
102
+ def generate_warnings html = false
103
103
  table = Ruport::Data::Table(["Confidence", "Class", "Method", "Warning Type", "Message"])
104
104
  checks.warnings.each do |warning|
105
105
  next if warning.confidence > tracker.options[:min_confidence]
106
106
  w = warning.to_row
107
107
 
108
- if tracker.options[:output_format] == :to_html
108
+ if html
109
109
  w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
110
110
  w["Message"] = with_context warning, w["Message"]
111
111
  else
@@ -128,14 +128,14 @@ class Brakeman::Report
128
128
  end
129
129
 
130
130
  #Generate table of template warnings or return nil if no warnings
131
- def generate_template_warnings
131
+ def generate_template_warnings html = false
132
132
  unless checks.template_warnings.empty?
133
133
  table = Ruport::Data::Table(["Confidence", "Template", "Warning Type", "Message"])
134
134
  checks.template_warnings.each do |warning|
135
135
  next if warning.confidence > tracker.options[:min_confidence]
136
136
  w = warning.to_row :template
137
137
 
138
- if tracker.options[:output_format] == :to_html
138
+ if html
139
139
  w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
140
140
  w["Message"] = with_context warning, w["Message"]
141
141
  else
@@ -159,14 +159,14 @@ class Brakeman::Report
159
159
  end
160
160
 
161
161
  #Generate table of model warnings or return nil if no warnings
162
- def generate_model_warnings
162
+ def generate_model_warnings html = false
163
163
  unless checks.model_warnings.empty?
164
164
  table = Ruport::Data::Table(["Confidence", "Model", "Warning Type", "Message"])
165
165
  checks.model_warnings.each do |warning|
166
166
  next if warning.confidence > tracker.options[:min_confidence]
167
167
  w = warning.to_row :model
168
168
 
169
- if tracker.options[:output_format] == :to_html
169
+ if html
170
170
  w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
171
171
  w["Message"] = with_context warning, w["Message"]
172
172
  else
@@ -190,14 +190,14 @@ class Brakeman::Report
190
190
  end
191
191
 
192
192
  #Generate table of controller warnings or nil if no warnings
193
- def generate_controller_warnings
193
+ def generate_controller_warnings html = false
194
194
  unless checks.controller_warnings.empty?
195
195
  table = Ruport::Data::Table(["Confidence", "Controller", "Warning Type", "Message"])
196
196
  checks.controller_warnings.each do |warning|
197
197
  next if warning.confidence > tracker.options[:min_confidence]
198
198
  w = warning.to_row :controller
199
199
 
200
- if tracker.options[:output_format] == :to_html
200
+ if html
201
201
  w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
202
202
  w["Message"] = with_context warning, w["Message"]
203
203
  else
@@ -257,14 +257,14 @@ class Brakeman::Report
257
257
  end
258
258
 
259
259
  #Generate listings of templates and their output
260
- def generate_templates
260
+ def generate_templates html = false
261
261
  out_processor = Brakeman::OutputProcessor.new
262
262
  table = Ruport::Data::Table(["Name", "Output"])
263
263
  tracker.templates.each do |name, template|
264
264
  unless template[:outputs].empty?
265
265
  template[:outputs].each do |out|
266
266
  out = out_processor.format out
267
- out = CGI.escapeHTML(out) if tracker.options[:output_format] == :to_html
267
+ out = CGI.escapeHTML(out) if html
268
268
  table << { "Name" => name,
269
269
  "Output" => out.gsub("\n", ";").gsub(/\s+/, " ") }
270
270
  end
@@ -277,9 +277,14 @@ class Brakeman::Report
277
277
  def to_html
278
278
  out = html_header <<
279
279
  "<h2 id='summary'>Summary</h2>" <<
280
- generate_overview.to_html << "<br/>" <<
280
+ generate_overview(true).to_html << "<br/>" <<
281
281
  generate_warning_overview.to_html
282
282
 
283
+ #Return early if only summarizing
284
+ if tracker.options[:summary_only]
285
+ return out
286
+ end
287
+
283
288
  if tracker.options[:report_routes] or tracker.options[:debug]
284
289
  out << "<h2>Controllers</h2>" <<
285
290
  generate_controllers.to_html
@@ -287,24 +292,24 @@ class Brakeman::Report
287
292
 
288
293
  if tracker.options[:debug]
289
294
  out << "<h2>Templates</h2>" <<
290
- generate_templates.to_html
295
+ generate_templates(true).to_html
291
296
  end
292
297
 
293
- res = generate_errors
298
+ res = generate_errors(true)
294
299
  if res
295
300
  out << "<div onClick=\"toggle('errors_table');\"> <h2>Exceptions raised during the analysis (click to see them)</h2 ></div> <div id='errors_table' style='display:none'>" << res.to_html << '</div>'
296
301
  end
297
302
 
298
- res = generate_warnings
303
+ res = generate_warnings(true)
299
304
  out << "<h2>Security Warnings</h2>" << res.to_html if res
300
305
 
301
- res = generate_controller_warnings
306
+ res = generate_controller_warnings(true)
302
307
  out << res.to_html if res
303
308
 
304
- res = generate_model_warnings
309
+ res = generate_model_warnings(true)
305
310
  out << res.to_html if res
306
311
 
307
- res = generate_template_warnings
312
+ res = generate_template_warnings(true)
308
313
  out << res.to_html if res
309
314
 
310
315
  out << "</body></html>"
@@ -317,6 +322,11 @@ class Brakeman::Report
317
322
  generate_overview.to_s << "\n" <<
318
323
  generate_warning_overview.to_s << "\n"
319
324
 
325
+ #Return output early if only summarizing
326
+ if tracker.options[:summary_only]
327
+ return out
328
+ end
329
+
320
330
  if tracker.options[:report_routes] or tracker.options[:debug]
321
331
  out << "+CONTROLLERS+\n" <<
322
332
  generate_controllers.to_s << "\n"
@@ -352,6 +362,11 @@ class Brakeman::Report
352
362
  generate_overview.to_csv << "\n" <<
353
363
  generate_warning_overview.to_csv << "\n"
354
364
 
365
+ #Return output early if only summarizing
366
+ if tracker.options[:summary_only]
367
+ return out
368
+ end
369
+
355
370
  if tracker.options[:report_routes] or tracker.options[:debug]
356
371
  out << "CONTROLLERS\n" <<
357
372
  generate_controllers.to_csv << "\n"
@@ -483,107 +498,6 @@ class Brakeman::Report
483
498
  @warnings_summary = summary
484
499
  end
485
500
 
486
- #Return file name related to given warning. Uses +warning.file+ if it exists
487
- def file_for warning
488
- if warning.file
489
- File.expand_path warning.file, tracker.options[:app_path]
490
- else
491
- case warning.warning_set
492
- when :controller
493
- file_by_name warning.controller, :controller
494
- when :template
495
- file_by_name warning.template[:name], :template
496
- when :model
497
- file_by_name warning.model, :model
498
- when :warning
499
- file_by_name warning.class
500
- else
501
- nil
502
- end
503
- end
504
- end
505
-
506
- #Attempt to determine path to context file based on the reported name
507
- #in the warning.
508
- #
509
- #For example,
510
- #
511
- # file_by_name FileController #=> "/rails/root/app/controllers/file_controller.rb
512
- def file_by_name name, type = nil
513
- return nil unless name
514
- string_name = name.to_s
515
- name = name.to_sym
516
-
517
- unless type
518
- if string_name =~ /Controller$/
519
- type = :controller
520
- elsif camelize(string_name) == string_name
521
- type = :model
522
- else
523
- type = :template
524
- end
525
- end
526
-
527
- path = tracker.options[:app_path]
528
-
529
- case type
530
- when :controller
531
- if tracker.controllers[name] and tracker.controllers[name][:file]
532
- path = tracker.controllers[name][:file]
533
- else
534
- path += "/app/controllers/#{underscore(string_name)}.rb"
535
- end
536
- when :model
537
- if tracker.models[name] and tracker.models[name][:file]
538
- path = tracker.models[name][:file]
539
- else
540
- path += "/app/controllers/#{underscore(string_name)}.rb"
541
- end
542
- when :template
543
- if tracker.templates[name] and tracker.templates[name][:file]
544
- path = tracker.templates[name][:file]
545
- elsif string_name.include? " "
546
- name = string_name.split[0].to_sym
547
- path = file_for name, :template
548
- else
549
- path = nil
550
- end
551
- end
552
-
553
- path
554
- end
555
-
556
- #Return array of lines surrounding the warning location from the original
557
- #file.
558
- def context_for warning
559
- file = file_for warning
560
- context = []
561
- return context unless warning.line and file and File.exist? file
562
-
563
- current_line = 0
564
- start_line = warning.line - 5
565
- end_line = warning.line + 5
566
-
567
- start_line = 1 if start_line < 0
568
-
569
- File.open file do |f|
570
- f.each_line do |line|
571
- current_line += 1
572
-
573
- next if line.strip == ""
574
-
575
- if current_line > end_line
576
- break
577
- end
578
-
579
- if current_line >= start_line
580
- context << [current_line, line]
581
- end
582
- end
583
- end
584
-
585
- context
586
- end
587
501
 
588
502
  #Generate HTML for warnings, including context show/hidden via Javascript
589
503
  def with_context warning, message
@@ -0,0 +1,247 @@
1
+ require 'brakeman/scanner'
2
+
3
+ #Class for rescanning changed files after an initial scan
4
+ class Brakeman::Rescanner < Brakeman::Scanner
5
+
6
+ SCAN_ORDER = [:config, :gemfile, :initializer, :lib, :routes, :template,
7
+ :model, :controller]
8
+
9
+ #Create new Rescanner to scan changed files
10
+ def initialize options, processor, changed_files
11
+ super(options, processor)
12
+
13
+ @paths = changed_files.map {|f| File.expand_path f, tracker.options[:app_path] }
14
+ @old_results = tracker.checks #Old warnings from previous scan
15
+ @changes = nil #True if files had to be rescanned
16
+ @reindex = Set.new
17
+ end
18
+
19
+ #Runs checks.
20
+ #Will rescan files if they have not already been scanned
21
+ def recheck
22
+ rescan if @changes.nil?
23
+
24
+ tracker.run_checks if @changes
25
+
26
+ Brakeman::RescanReport.new @old_results, tracker.checks
27
+ end
28
+
29
+ #Rescans changed files
30
+ def rescan
31
+ tracker.template_cache.clear
32
+
33
+ paths_by_type = {}
34
+
35
+ SCAN_ORDER.each do |type|
36
+ paths_by_type[type] = []
37
+ end
38
+
39
+ @paths.each do |path|
40
+ type = file_type(path)
41
+ paths_by_type[type] << path unless type == :unknown
42
+ end
43
+
44
+ @changes = false
45
+
46
+ SCAN_ORDER.each do |type|
47
+ paths_by_type[type].each do |path|
48
+ warn "Rescanning #{path} as #{type}" if tracker.options[:debug]
49
+
50
+ if rescan_file path, type
51
+ @changes = true
52
+ end
53
+ end
54
+ end
55
+
56
+ if @changes and not @reindex.empty?
57
+ tracker.reindex_call_sites @reindex
58
+ end
59
+
60
+ self
61
+ end
62
+
63
+ #Rescans a single file
64
+ def rescan_file path, type = nil
65
+ type ||= file_type path
66
+
67
+ case type
68
+ when :controller
69
+ rescan_controller path
70
+ @reindex << :controllers << :templates
71
+ when :template
72
+ rescan_template path
73
+ @reindex << :templates
74
+ when :model
75
+ rescan_model path
76
+ when :lib
77
+ process_library path
78
+ when :config
79
+ process_config
80
+ when :initializer
81
+ process_initializer path
82
+ when :routes
83
+ # Routes affect which controller methods are treated as actions
84
+ # which affects which templates are rendered, so routes, controllers,
85
+ # and templates rendered from controllers must be rescanned
86
+ tracker.reset_routes
87
+ tracker.reset_templates :only_rendered => true
88
+ process_routes
89
+ process_controllers
90
+ @reindex << :controllers << :templates
91
+ when :gemfile
92
+ process_gems
93
+ else
94
+ return false #Nothing to do, file hopefully does not need to be rescanned
95
+ end
96
+
97
+ true
98
+ end
99
+
100
+ def rescan_controller path
101
+ #Process source
102
+ process_controller path
103
+
104
+ #Process data flow and template rendering
105
+ #from the controller
106
+ tracker.controllers.each do |name, controller|
107
+ if controller[:file] == path
108
+ tracker.templates.keys.each do |template_name|
109
+ if template_name.to_s.match /(.+)\.#{name}#/
110
+ tracker.templates.delete template_name
111
+ end
112
+ end
113
+
114
+ @processor.process_controller_alias controller[:src]
115
+ end
116
+ end
117
+ end
118
+
119
+ def rescan_template path
120
+ template_name = template_path_to_name(path)
121
+
122
+ tracker.reset_template template_name
123
+ process_template path
124
+
125
+ @processor.process_template_alias tracker.templates[template_name]
126
+
127
+ rescan = Set.new
128
+
129
+ rendered_from_controller = /^#{template_name}\.(.+Controller)#(.+)/
130
+ rendered_from_view = /^#{template_name}\.Template:(.+)/
131
+
132
+ #Search for processed template and process it.
133
+ #Search for rendered versions of template and re-render (if necessary)
134
+ tracker.templates.each do |name, template|
135
+ if template[:file] == path or template[:file].nil?
136
+ name = name.to_s
137
+
138
+ if name.match(rendered_from_controller)
139
+ #Rendered from controller, so reprocess controller
140
+
141
+ rescan << [:controller, $1.to_sym, $2.to_sym]
142
+ elsif name.match(rendered_from_view)
143
+ #Rendered from another template, so reprocess that template
144
+
145
+ rescan << [:template, $1.to_sym]
146
+ end
147
+ end
148
+ end
149
+
150
+ rescan.each do |r|
151
+ if r[0] == :controller
152
+ controller = tracker.controllers[r[1]]
153
+
154
+ unless @paths.include? controller[:file]
155
+ @processor.process_controller_alias controller[:src], r[2]
156
+ end
157
+ elsif r[0] == :template
158
+ template = tracker.templates[r[1]]
159
+
160
+ rescan_template template[:file]
161
+ end
162
+ end
163
+ end
164
+
165
+ def rescan_model path
166
+ num_models = tracker.models.length
167
+ tracker.reset_model path
168
+ process_model path if File.exists? path
169
+
170
+ #Only need to rescan other things if a model is added or removed
171
+ if num_models != tracker.models.length
172
+ process_templates
173
+ process_controllers
174
+ @reindex << :templates << :controllers
175
+ end
176
+
177
+ @reindex << :models
178
+ end
179
+
180
+ #Guess at what kind of file the path contains
181
+ def file_type path
182
+ case path
183
+ when /\/app\/controllers/
184
+ :controller
185
+ when /\/app\/views/
186
+ :template
187
+ when /\/app\/models/
188
+ :model
189
+ when /\/lib/
190
+ :lib
191
+ when /\/config\/initializers/
192
+ :initializer
193
+ when /config\/routes\.rb/
194
+ :routes
195
+ when /\/config/
196
+ :config
197
+ when /Gemfile/
198
+ :gemfile
199
+ else
200
+ :unknown
201
+ end
202
+ end
203
+ end
204
+
205
+ #Class to make reporting of rescan results simpler to deal with
206
+ class Brakeman::RescanReport
207
+ attr_reader :old_results, :new_results
208
+
209
+ def initialize old_results, new_results
210
+ @old_results = old_results
211
+ @new_results = new_results
212
+ @all_warnings = nil
213
+ @diff = nil
214
+ end
215
+
216
+ #Returns true if any warnings were found (new or old)
217
+ def any_warnings?
218
+ not all_warnings.empty?
219
+ end
220
+
221
+ #Returns an array of all warnings found
222
+ def all_warnings
223
+ @all_warnings ||= new_results.all_warnings
224
+ end
225
+
226
+ #Returns an array of warnings which were in the old report but are not in the
227
+ #new report after rescanning
228
+ def fixed_warnings
229
+ diff[:fixed]
230
+ end
231
+
232
+ #Returns an array of warnings which were in the new report but were not in
233
+ #the old report
234
+ def new_warnings
235
+ diff[:new]
236
+ end
237
+
238
+ #Returns true if there are any new or fixed warnings
239
+ def warnings_changed?
240
+ not (diff[:new].empty? and diff[:fixed].empty?)
241
+ end
242
+
243
+ #Returns a hash of arrays for :new and :fixed warnings
244
+ def diff
245
+ @diff ||= @new_results.diff(@old_results)
246
+ end
247
+ end