brakeman 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGES +20 -0
  2. data/README.md +6 -1
  3. data/bin/brakeman +13 -3
  4. data/lib/brakeman.rb +64 -7
  5. data/lib/brakeman/call_index.rb +6 -4
  6. data/lib/brakeman/checks/check_basic_auth.rb +47 -2
  7. data/lib/brakeman/checks/check_cross_site_scripting.rb +50 -12
  8. data/lib/brakeman/checks/check_execute.rb +4 -1
  9. data/lib/brakeman/checks/check_model_attr_accessible.rb +48 -0
  10. data/lib/brakeman/checks/check_sql.rb +101 -154
  11. data/lib/brakeman/options.rb +16 -0
  12. data/lib/brakeman/parsers/rails2_erubis.rb +2 -0
  13. data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +2 -0
  14. data/lib/brakeman/parsers/rails3_erubis.rb +2 -0
  15. data/lib/brakeman/processors/alias_processor.rb +19 -4
  16. data/lib/brakeman/processors/controller_alias_processor.rb +2 -3
  17. data/lib/brakeman/processors/gem_processor.rb +5 -4
  18. data/lib/brakeman/processors/lib/find_all_calls.rb +43 -16
  19. data/lib/brakeman/report.rb +39 -640
  20. data/lib/brakeman/report/ignore/config.rb +130 -0
  21. data/lib/brakeman/report/ignore/interactive.rb +311 -0
  22. data/lib/brakeman/report/renderer.rb +2 -0
  23. data/lib/brakeman/report/report_base.rb +279 -0
  24. data/lib/brakeman/report/report_csv.rb +56 -0
  25. data/lib/brakeman/report/report_hash.rb +22 -0
  26. data/lib/brakeman/report/report_html.rb +203 -0
  27. data/lib/brakeman/report/report_json.rb +46 -0
  28. data/lib/brakeman/report/report_table.rb +109 -0
  29. data/lib/brakeman/report/report_tabs.rb +17 -0
  30. data/lib/brakeman/report/templates/ignored_warnings.html.erb +21 -0
  31. data/lib/brakeman/report/templates/overview.html.erb +6 -0
  32. data/lib/brakeman/report/templates/security_warnings.html.erb +1 -1
  33. data/lib/brakeman/scanner.rb +14 -12
  34. data/lib/brakeman/tracker.rb +5 -1
  35. data/lib/brakeman/util.rb +2 -0
  36. data/lib/brakeman/version.rb +1 -1
  37. data/lib/ruby_parser/bm_sexp.rb +12 -1
  38. metadata +179 -90
  39. checksums.yaml +0 -7
@@ -1,3 +1,5 @@
1
+ Brakeman.load_dependency 'erubis'
2
+
1
3
  #Erubis processor which ignores any output which is plain text.
2
4
  class Brakeman::ScannerErubis < Erubis::Eruby
3
5
  include Erubis::NoTextEnhancer
@@ -1,3 +1,5 @@
1
+ Brakeman.load_dependency 'erubis'
2
+
1
3
  #This is from the rails_xss plugin for Rails 2
2
4
  class Brakeman::Rails2XSSPluginErubis < ::Erubis::Eruby
3
5
  def add_preamble(src)
@@ -1,3 +1,5 @@
1
+ Brakeman.load_dependency 'erubis'
2
+
1
3
  #This is from Rails 3 version of the Erubis handler
2
4
  class Brakeman::Rails3Erubis < ::Erubis::Eruby
3
5
 
@@ -26,6 +26,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
26
26
  @tracker = tracker #set in subclass as necessary
27
27
  @helper_method_cache = {}
28
28
  @helper_method_info = Hash.new({})
29
+ @or_depth_limit = (tracker && tracker.options[:branch_limit]) || 5 #arbitrary default
29
30
  set_env_defaults
30
31
  end
31
32
 
@@ -354,7 +355,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
354
355
 
355
356
  unless env[match]
356
357
  if request_value? target
357
- env[match] = Sexp.new(:or, match, value)
358
+ env[match] = match.combine(value)
358
359
  else
359
360
  env[match] = value
360
361
  end
@@ -464,11 +465,18 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
464
465
 
465
466
  def merge_if_branch branch_env
466
467
  branch_env.each do |k, v|
468
+ next if v.nil?
469
+
467
470
  current_val = env[k]
468
471
 
469
472
  if current_val
470
- unless same_value? current_val, v
471
- env[k] = Sexp.new(:or, current_val, v).line(k.line || -2)
473
+ unless same_value?(current_val, v)
474
+ if too_deep? current_val
475
+ # Give up branching, start over with latest value
476
+ env[k] = v
477
+ else
478
+ env[k] = current_val.combine(v, k.line)
479
+ end
472
480
  end
473
481
  else
474
482
  env[k] = v
@@ -476,6 +484,13 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
476
484
  end
477
485
  end
478
486
 
487
+ def too_deep? exp
488
+ @or_depth_limit >= 0 and
489
+ node_type? exp, :or and
490
+ exp.or_depth and
491
+ exp.or_depth >= @or_depth_limit
492
+ end
493
+
479
494
  #Process single integer access to an array.
480
495
  #
481
496
  #Returns the value inside the array, if possible.
@@ -701,7 +716,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
701
716
  elsif false? condition
702
717
  exp.else_clause
703
718
  else
704
- Sexp.new(:or, exp.then_clause, exp.else_clause).line(exp.line)
719
+ exp.then_clause.combine(exp.else_clause, exp.line)
705
720
  end
706
721
  end
707
722
  end
@@ -11,10 +11,9 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
11
11
  #other methods will be skipped.
12
12
  #This is for rescanning just a single action.
13
13
  def initialize app_tree, tracker, only_method = nil
14
- super()
14
+ super tracker
15
15
  @app_tree = app_tree
16
16
  @only_method = only_method
17
- @tracker = tracker
18
17
  @rendered = false
19
18
  @current_class = @current_module = @current_method = nil
20
19
  @method_cache = {} #Cache method lookups
@@ -49,7 +48,7 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
49
48
  #Need to process the method like it was in a controller in order
50
49
  #to get the renders set
51
50
  processor = Brakeman::ControllerProcessor.new(@app_tree, @tracker)
52
- method = mixin[:public][name]
51
+ method = mixin[:public][name].deep_clone
53
52
 
54
53
  if node_type? method, :methdef
55
54
  method = processor.process_defn method
@@ -40,11 +40,12 @@ class Brakeman::GemProcessor < Brakeman::BaseProcessor
40
40
 
41
41
  exp
42
42
  end
43
-
44
- #Need to implement generic gem version check
43
+
44
+ # Supports .rc2 but not ~>, >=, or <=
45
45
  def get_version name, gem_lock
46
- match = gem_lock.match(/\s#{name} \((\d+.\d+.\d+.*)\)$/)
47
- match[1] if match
46
+ if gem_lock =~ /\s#{name} \((\w(\.\w+)*)\)(?:\n|\r\n)/
47
+ $1
48
+ end
48
49
  end
49
50
 
50
51
  def get_rails_version gem_lock
@@ -37,28 +37,33 @@ class Brakeman::FindAllCalls < Brakeman::BaseProcessor
37
37
  end
38
38
 
39
39
  def process_call exp
40
- target = get_target exp.target
41
-
42
- if call? target
43
- already_in_target = @in_target
44
- @in_target = true
45
- process target
46
- @in_target = already_in_target
47
- end
40
+ @calls << create_call_hash(exp)
41
+ exp
42
+ end
48
43
 
49
- method = exp.method
50
- process_call_args exp
44
+ def process_call_with_block exp
45
+ call = exp.block_call
51
46
 
52
- @calls << { :target => target,
53
- :method => method,
54
- :call => exp,
55
- :nested => @in_target,
56
- :chain => get_chain(exp),
57
- :location => make_location }
47
+ if call.node_type == :call
48
+ call_hash = create_call_hash(call)
49
+
50
+ call_hash[:block] = exp.block
51
+ call_hash[:block_args] = exp.block_args
52
+
53
+ @calls << call_hash
54
+
55
+ process exp.block
56
+ else
57
+ #Probably a :render call with block
58
+ process call
59
+ process exp.block
60
+ end
58
61
 
59
62
  exp
60
63
  end
61
64
 
65
+ alias process_iter process_call_with_block
66
+
62
67
  #Calls to render() are converted to s(:render, ...) but we would
63
68
  #like them in the call cache still for speed
64
69
  def process_render exp
@@ -155,4 +160,26 @@ class Brakeman::FindAllCalls < Brakeman::BaseProcessor
155
160
  end
156
161
 
157
162
  end
163
+
164
+ #Return info hash for a call Sexp
165
+ def create_call_hash exp
166
+ target = get_target exp.target
167
+
168
+ if call? target
169
+ already_in_target = @in_target
170
+ @in_target = true
171
+ process target
172
+ @in_target = already_in_target
173
+ end
174
+
175
+ method = exp.method
176
+ process_call_args exp
177
+
178
+ { :target => target,
179
+ :method => method,
180
+ :call => exp,
181
+ :nested => @in_target,
182
+ :chain => get_chain(exp),
183
+ :location => make_location }
184
+ end
158
185
  end
@@ -1,669 +1,68 @@
1
- require 'cgi'
2
- require 'set'
3
- require 'brakeman/processors/output_processor'
4
- require 'brakeman/util'
5
- require 'terminal-table'
6
- require 'highline'
7
- require "csv"
8
- require 'multi_json'
9
- require 'brakeman/version'
10
- require 'brakeman/report/renderer'
11
- Dir.glob(File.dirname(__FILE__) + '/report/initializers/*.rb').each {|file| require file }
1
+ require 'brakeman/report/report_base'
12
2
 
13
3
  #Generates a report based on the Tracker and the results of
14
4
  #Tracker#run_checks. Be sure to +run_checks+ before generating
15
5
  #a report.
16
6
  class Brakeman::Report
17
- include Brakeman::Util
7
+ attr_reader :tracker
18
8
 
19
- attr_reader :tracker, :checks
9
+ VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s]
20
10
 
21
- TEXT_CONFIDENCE = [ "High", "Medium", "Weak" ]
22
- HTML_CONFIDENCE = [ "<span class='high-confidence'>High</span>",
23
- "<span class='med-confidence'>Medium</span>",
24
- "<span class='weak-confidence'>Weak</span>" ]
25
-
26
- def initialize(app_tree, tracker)
11
+ def initialize app_tree, tracker
27
12
  @app_tree = app_tree
28
13
  @tracker = tracker
29
- @checks = tracker.checks
30
- @element_id = 0 #Used for HTML ids
31
- @warnings_summary = nil
32
- @highlight_user_input = tracker.options[:highlight_user_input]
33
- end
34
-
35
- #Generate summary table of what was parsed
36
- def generate_overview html = false
37
- warnings = all_warnings.length
38
-
39
- if html
40
- locals = {
41
- :tracker => tracker,
42
- :warnings => warnings,
43
- :warnings_summary => warnings_summary,
44
- :number_of_templates => number_of_templates(@tracker),
45
- }
46
-
47
- Brakeman::Report::Renderer.new('overview', :locals => locals).render
48
- else
49
- Terminal::Table.new(:headings => ['Scanned/Reported', 'Total']) do |t|
50
- t.add_row ['Controllers', tracker.controllers.length]
51
- t.add_row ['Models', tracker.models.length - 1]
52
- t.add_row ['Templates', number_of_templates(@tracker)]
53
- t.add_row ['Errors', tracker.errors.length]
54
- t.add_row ['Security Warnings', "#{warnings} (#{warnings_summary[:high_confidence]})"]
55
- end
56
- end
57
- end
58
-
59
- #Generate table of how many warnings of each warning type were reported
60
- def generate_warning_overview html = false
61
- types = warnings_summary.keys
62
- types.delete :high_confidence
63
- values = types.sort.collect{|warning_type| [warning_type, warnings_summary[warning_type]] }
64
- locals = {:types => types, :warnings_summary => warnings_summary}
65
-
66
- render_array('warning_overview', ['Warning Type', 'Total'], values, locals, html)
67
- end
68
-
69
- #Generate table of errors or return nil if no errors
70
- def generate_errors html = false
71
- values = tracker.errors.collect{|error| [error[:error], error[:backtrace][0]]}
72
- render_array('error_overview', ['Error', 'Location'], values, {:tracker => tracker}, html)
73
- end
74
-
75
- def render_array(template, headings, value_array, locals, html = false)
76
- return if value_array.empty?
77
- if html
78
- Brakeman::Report::Renderer.new(template, :locals => locals).render
79
- else
80
- Terminal::Table.new(:headings => headings) do |t|
81
- value_array.each { |value_row| t.add_row value_row }
82
- end
83
- end
84
- end
85
-
86
- #Generate table of general security warnings
87
- def generate_warnings html = false
88
- warning_messages = []
89
- checks.warnings.each do |warning|
90
- w = warning.to_row
91
-
92
- if html
93
- w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
94
- w["Message"] = with_context warning, w["Message"]
95
- w["Warning Type"] = with_link warning, w["Warning Type"]
96
- else
97
- w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
98
- w["Message"] = text_message warning, w["Message"]
99
- end
100
-
101
- warning_messages << w
102
- end
103
-
104
- stabilizer = 0
105
- warning_messages = warning_messages.sort_by{|row| stabilizer += 1; [row['Confidence'], row['Warning Type'], row['Class'], stabilizer]}
106
-
107
- locals = {:warning_messages => warning_messages}
108
- values = warning_messages.collect{|row| [row["Confidence"], row["Class"], row["Method"], row["Warning Type"], row["Message"]] }
109
- render_array('security_warnings', ["Confidence", "Class", "Method", "Warning Type", "Message"], values, locals, html)
110
- end
111
-
112
- #Generate table of template warnings or return nil if no warnings
113
- def generate_template_warnings html = false
114
- if checks.template_warnings.any?
115
- warnings = []
116
- checks.template_warnings.each do |warning|
117
- w = warning.to_row :template
118
-
119
- if html
120
- w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
121
- w["Message"] = with_context warning, w["Message"]
122
- w["Warning Type"] = with_link warning, w["Warning Type"]
123
- w["Called From"] = warning.called_from
124
- w["Template Name"] = warning.template[:name]
125
- else
126
- w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
127
- w["Message"] = text_message warning, w["Message"]
128
- end
129
-
130
- warnings << w
131
- end
132
-
133
- return nil if warnings.empty?
134
-
135
- stabilizer = 0
136
- warnings = warnings.sort_by{|row| stabilizer += 1; [row["Confidence"], row["Warning Type"], row["Template"], stabilizer]}
137
-
138
- locals = {:warnings => warnings}
139
- values = warnings.collect{|warning| [warning["Confidence"], warning["Template"], warning["Warning Type"], warning["Message"]] }
140
- render_array('view_warnings', ["Confidence", "Template", "Warning Type", "Message"], values, locals, html)
141
- else
142
- nil
143
- end
144
- end
145
-
146
- #Generate table of model warnings or return nil if no warnings
147
- def generate_model_warnings html = false
148
- if checks.model_warnings.any?
149
- warnings = []
150
- checks.model_warnings.each do |warning|
151
- w = warning.to_row :model
152
-
153
- if html
154
- w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
155
- w["Message"] = with_context warning, w["Message"]
156
- w["Warning Type"] = with_link warning, w["Warning Type"]
157
- else
158
- w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
159
- w["Message"] = text_message warning, w["Message"]
160
- end
161
-
162
- warnings << w
163
- end
164
-
165
- return nil if warnings.empty?
166
- stabilizer = 0
167
- warnings = warnings.sort_by{|row| stabilizer +=1; [row["Confidence"],row["Warning Type"], row["Model"], stabilizer]}
168
-
169
- locals = {:warnings => warnings}
170
- values = warnings.collect{|warning| [warning["Confidence"], warning["Model"], warning["Warning Type"], warning["Message"]] }
171
- render_array('model_warnings', ["Confidence", "Model", "Warning Type", "Message"], values, locals, html)
172
- else
173
- nil
174
- end
175
14
  end
176
15
 
177
- #Generate table of controller warnings or nil if no warnings
178
- def generate_controller_warnings html = false
179
- unless checks.controller_warnings.empty?
180
- warnings = []
181
- checks.controller_warnings.each do |warning|
182
- w = warning.to_row :controller
183
-
184
- if html
185
- w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
186
- w["Message"] = with_context warning, w["Message"]
187
- w["Warning Type"] = with_link warning, w["Warning Type"]
188
- else
189
- w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
190
- w["Message"] = text_message warning, w["Message"]
191
- end
192
-
193
- warnings << w
194
- end
195
-
196
- return nil if warnings.empty?
197
-
198
- stabilizer = 0
199
- warnings = warnings.sort_by{|row| stabilizer +=1; [row["Confidence"], row["Warning Type"], row["Controller"], stabilizer]}
200
-
201
- locals = {:warnings => warnings}
202
- values = warnings.collect{|warning| [warning["Confidence"], warning["Controller"], warning["Warning Type"], warning["Message"]] }
203
- render_array('controller_warnings', ["Confidence", "Controller", "Warning Type", "Message"], values, locals, html)
16
+ def format format
17
+ reporter = case format
18
+ when :to_csv
19
+ require_report 'csv'
20
+ Brakeman::Report::CSV
21
+ when :to_html
22
+ require_report 'html'
23
+ Brakeman::Report::HTML
24
+ when :to_json
25
+ return self.to_json
26
+ when :to_tabs
27
+ require_report 'tabs'
28
+ Brakeman::Report::Tabs
29
+ when :to_hash
30
+ require_report 'hash'
31
+ Brakeman::Report::Hash
32
+ when :to_s
33
+ return self.to_s
34
+ when :to_pdf
35
+ raise "PDF output is not yet supported."
204
36
  else
205
- nil
206
- end
207
- end
208
-
209
- #Generate table of controllers and routes found for those controllers
210
- def generate_controllers html=false
211
- controller_rows = []
212
- tracker.controllers.keys.map{|k| k.to_s}.sort.each do |name|
213
- name = name.to_sym
214
- c = tracker.controllers[name]
215
-
216
- if tracker.routes[:allow_all_actions] or tracker.routes[name] == :allow_all_actions
217
- routes = c[:public].keys.map{|e| e.to_s}.sort.join(", ")
218
- elsif tracker.routes[name].nil?
219
- #No routes defined for this controller.
220
- #This can happen when it is only a parent class
221
- #for other controllers, for example.
222
- routes = "[None]"
223
-
224
- else
225
- routes = (Set.new(c[:public].keys) & tracker.routes[name.to_sym]).
226
- to_a.
227
- map {|e| e.to_s}.
228
- sort.
229
- join(", ")
230
- end
231
-
232
- if routes == ""
233
- routes = "[None]"
234
- end
235
-
236
- controller_rows << { "Name" => name.to_s,
237
- "Parent" => c[:parent].to_s,
238
- "Includes" => c[:includes].join(", "),
239
- "Routes" => routes
240
- }
241
- end
242
- controller_rows = controller_rows.sort_by{|row| row['Name']}
243
-
244
- locals = {:controller_rows => controller_rows}
245
- values = controller_rows.collect{|row| [row['Name'], row['Parent'], row['Includes'], row['Routes']] }
246
- render_array('controller_overview', ['Name', 'Parent', 'Includes', 'Routes'], values, locals, html)
247
- end
248
-
249
- #Generate listings of templates and their output
250
- def generate_templates html = false
251
- out_processor = Brakeman::OutputProcessor.new
252
- template_rows = {}
253
- tracker.templates.each do |name, template|
254
- unless template[:outputs].empty?
255
- template[:outputs].each do |out|
256
- out = out_processor.format out
257
- out = CGI.escapeHTML(out) if html
258
- template_rows[name] ||= []
259
- template_rows[name] << out.gsub("\n", ";").gsub(/\s+/, " ")
260
- end
261
- end
37
+ raise "Invalid format: #{format}. Should be one of #{VALID_FORMATS.inspect}"
262
38
  end
263
39
 
264
- template_rows = template_rows.sort_by{|name, value| name.to_s}
265
-
266
- if html
267
- Brakeman::Report::Renderer.new('template_overview', :locals => {:template_rows => template_rows}).render
268
- else
269
- output = ''
270
- template_rows.each do |template|
271
- output << template.first.to_s << "\n\n"
272
- table = Terminal::Table.new(:headings => ['Output']) do |t|
273
- # template[1] is an array of calls
274
- template[1].each do |v|
275
- t.add_row [v]
276
- end
277
- end
278
-
279
- output << table.to_s << "\n\n"
280
- end
281
-
282
- output
283
- end
40
+ generate(reporter)
284
41
  end
285
42
 
286
- # format output from filename or format
287
- def format(filename_or_format)
288
- case filename_or_format
289
- when /\.html/, :to_html
290
- to_html
291
- when /\.pdf/, :to_pdf
292
- to_pdf
293
- when /\.csv/, :to_csv
294
- to_csv
295
- when /\.json/, :to_json
296
- to_json
297
- when /\.tabs/, :to_tabs
298
- to_tabs
299
- when :to_test
300
- to_test
43
+ def method_missing method, *args
44
+ if VALID_FORMATS.include? method
45
+ format method
301
46
  else
302
- to_s
47
+ super
303
48
  end
304
49
  end
305
-
306
- #Generate HTML output
307
- def to_html
308
- out = html_header <<
309
- generate_overview(true) <<
310
- generate_warning_overview(true).to_s
311
-
312
- # Return early if only summarizing
313
- return out if tracker.options[:summary_only]
314
-
315
- out << generate_controllers(true).to_s if tracker.options[:report_routes] or tracker.options[:debug]
316
- out << generate_templates(true).to_s if tracker.options[:debug]
317
- out << generate_errors(true).to_s
318
- out << generate_warnings(true).to_s
319
- out << generate_controller_warnings(true).to_s
320
- out << generate_model_warnings(true).to_s
321
- out << generate_template_warnings(true).to_s
322
- out << "</body></html>"
323
- end
324
-
325
- #Output text version of the report
326
- def to_s
327
- out = text_header <<
328
- "\n\n+SUMMARY+\n\n" <<
329
- truncate_table(generate_overview.to_s) << "\n\n" <<
330
- truncate_table(generate_warning_overview.to_s) << "\n"
331
-
332
- #Return output early if only summarizing
333
- return out if tracker.options[:summary_only]
334
-
335
- if tracker.options[:report_routes] or tracker.options[:debug]
336
- out << "\n+CONTROLLERS+\n" <<
337
- truncate_table(generate_controllers.to_s) << "\n"
338
- end
339
-
340
- if tracker.options[:debug]
341
- out << "\n+TEMPLATES+\n\n" <<
342
- truncate_table(generate_templates.to_s) << "\n"
343
- end
344
-
345
- res = generate_errors
346
- out << "+Errors+\n" << truncate_table(res.to_s) if res
347
-
348
- res = generate_warnings
349
- out << "\n\n+SECURITY WARNINGS+\n\n" << truncate_table(res.to_s) if res
350
-
351
- res = generate_controller_warnings
352
- out << "\n\n\nController Warnings:\n\n" << truncate_table(res.to_s) if res
353
-
354
- res = generate_model_warnings
355
- out << "\n\n\nModel Warnings:\n\n" << truncate_table(res.to_s) if res
356
50
 
357
- res = generate_template_warnings
358
- out << "\n\nView Warnings:\n\n" << truncate_table(res.to_s) if res
359
-
360
- out << "\n"
361
- out
362
- end
363
-
364
- #Generate CSV output
365
- def to_csv
366
- output = csv_header
367
- output << "\nSUMMARY\n"
368
-
369
- output << table_to_csv(generate_overview) << "\n"
370
-
371
- output << table_to_csv(generate_warning_overview) << "\n"
372
-
373
- #Return output early if only summarizing
374
- if tracker.options[:summary_only]
375
- return output
376
- end
377
-
378
- if tracker.options[:report_routes] or tracker.options[:debug]
379
- output << "CONTROLLERS\n"
380
- output << table_to_csv(generate_controllers) << "\n"
381
- end
382
-
383
- if tracker.options[:debug]
384
- output << "TEMPLATES\n\n"
385
- output << table_to_csv(generate_templates) << "\n"
386
- end
387
-
388
- res = generate_errors
389
- output << "ERRORS\n" << table_to_csv(res) << "\n" if res
390
-
391
- res = generate_warnings
392
- output << "SECURITY WARNINGS\n" << table_to_csv(res) << "\n" if res
393
-
394
- output << "Controller Warnings\n"
395
- res = generate_controller_warnings
396
- output << table_to_csv(res) << "\n" if res
397
-
398
- output << "Model Warnings\n"
399
- res = generate_model_warnings
400
- output << table_to_csv(res) << "\n" if res
401
-
402
- res = generate_template_warnings
403
- output << "Template Warnings\n"
404
- output << table_to_csv(res) << "\n" if res
405
-
406
- output
407
- end
408
-
409
- #Not yet implemented
410
- def to_pdf
411
- raise "PDF output is not yet supported."
412
- end
413
-
414
- def rails_version
415
- return tracker.config[:rails_version] if tracker.config[:rails_version]
416
- return "3.x" if tracker.options[:rails3]
417
- "Unknown"
418
- end
419
-
420
- #Return header for HTML output. Uses CSS from tracker.options[:html_style]
421
- def html_header
422
- if File.exist? tracker.options[:html_style]
423
- css = File.read tracker.options[:html_style]
424
- else
425
- raise "Cannot find CSS stylesheet for HTML: #{tracker.options[:html_style]}"
426
- end
427
-
428
- locals = {
429
- :css => css,
430
- :tracker => tracker,
431
- :checks => checks,
432
- :rails_version => rails_version,
433
- :brakeman_version => Brakeman::Version
434
- }
435
-
436
- Brakeman::Report::Renderer.new('header', :locals => locals).render
437
- end
438
-
439
- #Generate header for text output
440
- def text_header
441
- <<-HEADER
442
-
443
- +BRAKEMAN REPORT+
444
-
445
- Application path: #{File.expand_path tracker.options[:app_path]}
446
- Rails version: #{rails_version}
447
- Brakeman version: #{Brakeman::Version}
448
- Started at #{tracker.start_time}
449
- Duration: #{tracker.duration} seconds
450
- Checks run: #{checks.checks_run.sort.join(", ")}
451
- HEADER
452
- end
453
-
454
- #Generate header for CSV output
455
- def csv_header
456
- header = CSV.generate_line(["Application Path", "Report Generation Time", "Checks Performed", "Rails Version"])
457
- header << CSV.generate_line([File.expand_path(tracker.options[:app_path]), Time.now.to_s, checks.checks_run.sort.join(", "), rails_version])
458
- "BRAKEMAN REPORT\n\n" + header
459
- end
460
-
461
- #Return summary of warnings in hash and store in @warnings_summary
462
- def warnings_summary
463
- return @warnings_summary if @warnings_summary
464
-
465
- summary = Hash.new(0)
466
- high_confidence_warnings = 0
467
-
468
- [all_warnings].each do |warnings|
469
- warnings.each do |warning|
470
- summary[warning.warning_type.to_s] += 1
471
- high_confidence_warnings += 1 if warning.confidence == 0
472
- end
473
- end
474
-
475
- summary[:high_confidence] = high_confidence_warnings
476
- @warnings_summary = summary
477
- end
478
-
479
- #Escape warning message and highlight user input in text output
480
- def text_message warning, message
481
- if @highlight_user_input and warning.user_input
482
- user_input = warning.format_user_input
483
- message.gsub(user_input, "+#{user_input}+")
484
- else
485
- message
486
- end
487
- end
488
-
489
- #Escape warning message and highlight user input in HTML output
490
- def html_message warning, message
491
- message = CGI.escapeHTML(message)
492
-
493
- if @highlight_user_input and warning.user_input
494
- user_input = CGI.escapeHTML(warning.format_user_input)
495
- message.gsub!(user_input, "<span class=\"user_input\">#{user_input}</span>")
496
- end
497
-
498
- message
499
- end
500
-
501
- #Generate HTML for warnings, including context show/hidden via Javascript
502
- def with_context warning, message
503
- context = context_for(@app_tree, warning)
504
- full_message = nil
505
-
506
- if tracker.options[:message_limit] and tracker.options[:message_limit] > 0 and message.length > tracker.options[:message_limit]
507
- full_message = html_message(warning, message)
508
- message = message[0..tracker.options[:message_limit]] << "..."
509
- end
510
-
511
- message = html_message(warning, message)
512
- return message if context.empty? and not full_message
513
-
514
- @element_id += 1
515
- code_id = "context#@element_id"
516
- message_id = "message#@element_id"
517
- full_message_id = "full_message#@element_id"
518
- alt = false
519
- output = "<div class='warning_message' onClick=\"toggle('#{code_id}');toggle('#{message_id}');toggle('#{full_message_id}')\" >" <<
520
- if full_message
521
- "<span id='#{message_id}' style='display:block' >#{message}</span>" <<
522
- "<span id='#{full_message_id}' style='display:none'>#{full_message}</span>"
523
- else
524
- message
525
- end <<
526
- "<table id='#{code_id}' class='context' style='display:none'>" <<
527
- "<caption>#{warning_file(warning) || ''}</caption>"
528
-
529
- unless context.empty?
530
- if warning.line - 1 == 1 or warning.line + 1 == 1
531
- error = " near_error"
532
- elsif 1 == warning.line
533
- error = " error"
534
- else
535
- error = ""
536
- end
537
-
538
- output << <<-HTML
539
- <tr class='context first#{error}'>
540
- <td class='context_line'>
541
- <pre class='context'>#{context.first[0]}</pre>
542
- </td>
543
- <td class='context'>
544
- <pre class='context'>#{CGI.escapeHTML context.first[1].chomp}</pre>
545
- </td>
546
- </tr>
547
- HTML
548
-
549
- if context.length > 1
550
- output << context[1..-1].map do |code|
551
- alt = !alt
552
- if code[0] == warning.line - 1 or code[0] == warning.line + 1
553
- error = " near_error"
554
- elsif code[0] == warning.line
555
- error = " error"
556
- else
557
- error = ""
558
- end
559
-
560
- <<-HTML
561
- <tr class='context#{alt ? ' alt' : ''}#{error}'>
562
- <td class='context_line'>
563
- <pre class='context'>#{code[0]}</pre>
564
- </td>
565
- <td class='context'>
566
- <pre class='context'>#{CGI.escapeHTML code[1].chomp}</pre>
567
- </td>
568
- </tr>
569
- HTML
570
- end.join
571
- end
572
- end
573
-
574
- output << "</table></div>"
575
- end
576
-
577
- def with_link warning, message
578
- "<a rel=\"no-referrer\" href=\"#{warning.link}\">#{message}</a>"
579
- end
580
-
581
- #Generated tab-separated output suitable for the Jenkins Brakeman Plugin:
582
- #https://github.com/presidentbeef/brakeman-jenkins-plugin
583
- def to_tabs
584
- [[:warnings, "General"], [:controller_warnings, "Controller"],
585
- [:model_warnings, "Model"], [:template_warnings, "Template"]].map do |meth, category|
586
-
587
- checks.send(meth).map do |w|
588
- line = w.line || 0
589
- w.warning_type.gsub!(/[^\w\s]/, ' ')
590
- "#{warning_file(w, :absolute)}\t#{line}\t#{w.warning_type}\t#{category}\t#{w.format_message}\t#{TEXT_CONFIDENCE[w.confidence]}"
591
- end.join "\n"
592
-
593
- end.join "\n"
594
- end
595
-
596
- def to_test
597
- report = { :errors => tracker.errors,
598
- :controllers => tracker.controllers,
599
- :models => tracker.models,
600
- :templates => tracker.templates
601
- }
602
-
603
- [:warnings, :controller_warnings, :model_warnings, :template_warnings].each do |meth|
604
- report[meth] = @checks.send(meth)
605
- report[meth].each do |w|
606
- w.message = w.format_message
607
- w.context = context_for(@app_tree, w).join("\n")
608
- end
609
- end
610
-
611
- report[:config] = tracker.config
612
-
613
- report
51
+ def require_report type
52
+ require "brakeman/report/report_#{type}"
614
53
  end
615
54
 
616
55
  def to_json
617
- errors = tracker.errors.map{|e| { :error => e[:error], :location => e[:backtrace][0] }}
618
- app_path = tracker.options[:app_path]
619
-
620
- warnings = all_warnings.map do |w|
621
- hash = w.to_hash
622
- hash[:file] = warning_file w
623
- hash
624
- end.sort_by { |w| w[:file] }
625
-
626
- scan_info = {
627
- :app_path => File.expand_path(tracker.options[:app_path]),
628
- :rails_version => rails_version,
629
- :security_warnings => all_warnings.length,
630
- :start_time => tracker.start_time.to_s,
631
- :end_time => tracker.end_time.to_s,
632
- :duration => tracker.duration,
633
- :checks_performed => checks.checks_run.sort,
634
- :number_of_controllers => tracker.controllers.length,
635
- # ignore the "fake" model
636
- :number_of_models => tracker.models.length - 1,
637
- :number_of_templates => number_of_templates(@tracker),
638
- :ruby_version => RUBY_VERSION,
639
- :brakeman_version => Brakeman::Version
640
- }
641
-
642
- report_info = {
643
- :scan_info => scan_info,
644
- :warnings => warnings,
645
- :errors => errors
646
- }
647
-
648
- MultiJson.dump(report_info, :pretty => true)
56
+ require_report 'json'
57
+ generate Brakeman::Report::JSON
649
58
  end
650
59
 
651
- def all_warnings
652
- @all_warnings ||= @checks.all_warnings
60
+ def to_s
61
+ require_report 'table'
62
+ generate Brakeman::Report::Table
653
63
  end
654
64
 
655
- def number_of_templates tracker
656
- Set.new(tracker.templates.map {|k,v| v[:name].to_s[/[^.]+/]}).length
65
+ def generate reporter
66
+ reporter.new(@app_tree, @tracker).generate_report
657
67
  end
658
-
659
- def warning_file warning, absolute = @tracker.options[:absolute_paths]
660
- return nil if warning.file.nil?
661
-
662
- if absolute
663
- warning.file
664
- else
665
- relative_path warning.file
666
- end
667
- end
668
-
669
68
  end