brakeman 2.0.0 → 2.1.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.
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