brakeman-min 0.5.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/CHANGES +529 -0
  2. data/README.md +74 -28
  3. data/bin/brakeman +60 -266
  4. data/lib/brakeman.rb +422 -0
  5. data/lib/brakeman/app_tree.rb +101 -0
  6. data/lib/brakeman/brakeman.rake +10 -0
  7. data/lib/brakeman/call_index.rb +215 -0
  8. data/lib/brakeman/checks.rb +180 -0
  9. data/lib/brakeman/checks/base_check.rb +538 -0
  10. data/lib/brakeman/checks/check_basic_auth.rb +89 -0
  11. data/lib/brakeman/checks/check_content_tag.rb +162 -0
  12. data/lib/brakeman/checks/check_cross_site_scripting.rb +334 -0
  13. data/lib/{checks → brakeman/checks}/check_default_routes.rb +13 -6
  14. data/lib/brakeman/checks/check_deserialize.rb +57 -0
  15. data/lib/brakeman/checks/check_digest_dos.rb +38 -0
  16. data/lib/brakeman/checks/check_escape_function.rb +21 -0
  17. data/lib/brakeman/checks/check_evaluation.rb +33 -0
  18. data/lib/brakeman/checks/check_execute.rb +98 -0
  19. data/lib/brakeman/checks/check_file_access.rb +62 -0
  20. data/lib/brakeman/checks/check_filter_skipping.rb +31 -0
  21. data/lib/brakeman/checks/check_forgery_setting.rb +54 -0
  22. data/lib/brakeman/checks/check_jruby_xml.rb +38 -0
  23. data/lib/brakeman/checks/check_json_parsing.rb +102 -0
  24. data/lib/brakeman/checks/check_link_to.rb +132 -0
  25. data/lib/brakeman/checks/check_link_to_href.rb +92 -0
  26. data/lib/{checks → brakeman/checks}/check_mail_to.rb +14 -13
  27. data/lib/brakeman/checks/check_mass_assignment.rb +143 -0
  28. data/lib/brakeman/checks/check_model_attr_accessible.rb +48 -0
  29. data/lib/brakeman/checks/check_model_attributes.rb +118 -0
  30. data/lib/brakeman/checks/check_model_serialize.rb +66 -0
  31. data/lib/{checks → brakeman/checks}/check_nested_attributes.rb +10 -6
  32. data/lib/brakeman/checks/check_quote_table_name.rb +40 -0
  33. data/lib/brakeman/checks/check_redirect.rb +177 -0
  34. data/lib/brakeman/checks/check_render.rb +62 -0
  35. data/lib/brakeman/checks/check_response_splitting.rb +21 -0
  36. data/lib/brakeman/checks/check_safe_buffer_manipulation.rb +31 -0
  37. data/lib/brakeman/checks/check_sanitize_methods.rb +54 -0
  38. data/lib/brakeman/checks/check_select_tag.rb +60 -0
  39. data/lib/brakeman/checks/check_select_vulnerability.rb +58 -0
  40. data/lib/brakeman/checks/check_send.rb +35 -0
  41. data/lib/brakeman/checks/check_send_file.rb +19 -0
  42. data/lib/brakeman/checks/check_session_settings.rb +145 -0
  43. data/lib/brakeman/checks/check_single_quotes.rb +101 -0
  44. data/lib/brakeman/checks/check_skip_before_filter.rb +62 -0
  45. data/lib/brakeman/checks/check_sql.rb +577 -0
  46. data/lib/brakeman/checks/check_strip_tags.rb +64 -0
  47. data/lib/brakeman/checks/check_symbol_dos.rb +67 -0
  48. data/lib/brakeman/checks/check_translate_bug.rb +45 -0
  49. data/lib/brakeman/checks/check_unsafe_reflection.rb +51 -0
  50. data/lib/brakeman/checks/check_validation_regex.rb +88 -0
  51. data/lib/brakeman/checks/check_without_protection.rb +64 -0
  52. data/lib/brakeman/checks/check_yaml_parsing.rb +121 -0
  53. data/lib/brakeman/differ.rb +66 -0
  54. data/lib/{format → brakeman/format}/style.css +28 -0
  55. data/lib/brakeman/options.rb +256 -0
  56. data/lib/brakeman/parsers/rails2_erubis.rb +6 -0
  57. data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +48 -0
  58. data/lib/{scanner_erubis.rb → brakeman/parsers/rails3_erubis.rb} +8 -21
  59. data/lib/brakeman/processor.rb +102 -0
  60. data/lib/brakeman/processors/alias_processor.rb +780 -0
  61. data/lib/{processors → brakeman/processors}/base_processor.rb +90 -74
  62. data/lib/brakeman/processors/config_processor.rb +14 -0
  63. data/lib/brakeman/processors/controller_alias_processor.rb +334 -0
  64. data/lib/brakeman/processors/controller_processor.rb +265 -0
  65. data/lib/{processors → brakeman/processors}/erb_template_processor.rb +21 -19
  66. data/lib/brakeman/processors/erubis_template_processor.rb +96 -0
  67. data/lib/brakeman/processors/gem_processor.rb +59 -0
  68. data/lib/{processors → brakeman/processors}/haml_template_processor.rb +26 -21
  69. data/lib/brakeman/processors/lib/find_all_calls.rb +185 -0
  70. data/lib/{processors → brakeman/processors}/lib/find_call.rb +23 -28
  71. data/lib/brakeman/processors/lib/find_return_value.rb +134 -0
  72. data/lib/brakeman/processors/lib/processor_helper.rb +82 -0
  73. data/lib/{processors/config_processor.rb → brakeman/processors/lib/rails2_config_processor.rb} +32 -35
  74. data/lib/{processors → brakeman/processors}/lib/rails2_route_processor.rb +60 -52
  75. data/lib/brakeman/processors/lib/rails3_config_processor.rb +129 -0
  76. data/lib/brakeman/processors/lib/rails3_route_processor.rb +282 -0
  77. data/lib/{processors → brakeman/processors}/lib/render_helper.rb +54 -20
  78. data/lib/brakeman/processors/lib/route_helper.rb +62 -0
  79. data/lib/{processors → brakeman/processors}/library_processor.rb +24 -17
  80. data/lib/{processors → brakeman/processors}/model_processor.rb +46 -22
  81. data/lib/{processors → brakeman/processors}/output_processor.rb +34 -40
  82. data/lib/brakeman/processors/route_processor.rb +17 -0
  83. data/lib/brakeman/processors/slim_template_processor.rb +113 -0
  84. data/lib/brakeman/processors/template_alias_processor.rb +120 -0
  85. data/lib/{processors → brakeman/processors}/template_processor.rb +10 -7
  86. data/lib/brakeman/report.rb +68 -0
  87. data/lib/brakeman/report/ignore/config.rb +130 -0
  88. data/lib/brakeman/report/ignore/interactive.rb +311 -0
  89. data/lib/brakeman/report/initializers/faster_csv.rb +7 -0
  90. data/lib/brakeman/report/initializers/multi_json.rb +29 -0
  91. data/lib/brakeman/report/renderer.rb +24 -0
  92. data/lib/brakeman/report/report_base.rb +279 -0
  93. data/lib/brakeman/report/report_csv.rb +56 -0
  94. data/lib/brakeman/report/report_hash.rb +22 -0
  95. data/lib/brakeman/report/report_html.rb +203 -0
  96. data/lib/brakeman/report/report_json.rb +46 -0
  97. data/lib/brakeman/report/report_table.rb +109 -0
  98. data/lib/brakeman/report/report_tabs.rb +17 -0
  99. data/lib/brakeman/report/templates/controller_overview.html.erb +18 -0
  100. data/lib/brakeman/report/templates/controller_warnings.html.erb +17 -0
  101. data/lib/brakeman/report/templates/error_overview.html.erb +25 -0
  102. data/lib/brakeman/report/templates/header.html.erb +44 -0
  103. data/lib/brakeman/report/templates/ignored_warnings.html.erb +21 -0
  104. data/lib/brakeman/report/templates/model_warnings.html.erb +17 -0
  105. data/lib/brakeman/report/templates/overview.html.erb +34 -0
  106. data/lib/brakeman/report/templates/security_warnings.html.erb +19 -0
  107. data/lib/brakeman/report/templates/template_overview.html.erb +17 -0
  108. data/lib/brakeman/report/templates/view_warnings.html.erb +30 -0
  109. data/lib/brakeman/report/templates/warning_overview.html.erb +13 -0
  110. data/lib/brakeman/rescanner.rb +446 -0
  111. data/lib/brakeman/scanner.rb +362 -0
  112. data/lib/brakeman/tracker.rb +296 -0
  113. data/lib/brakeman/util.rb +413 -0
  114. data/lib/brakeman/version.rb +3 -0
  115. data/lib/brakeman/warning.rb +217 -0
  116. data/lib/brakeman/warning_codes.rb +68 -0
  117. data/lib/ruby_parser/bm_sexp.rb +562 -0
  118. data/lib/ruby_parser/bm_sexp_processor.rb +230 -0
  119. metadata +152 -66
  120. data/lib/checks.rb +0 -71
  121. data/lib/checks/base_check.rb +0 -357
  122. data/lib/checks/check_cross_site_scripting.rb +0 -336
  123. data/lib/checks/check_evaluation.rb +0 -27
  124. data/lib/checks/check_execute.rb +0 -110
  125. data/lib/checks/check_file_access.rb +0 -46
  126. data/lib/checks/check_forgery_setting.rb +0 -42
  127. data/lib/checks/check_mass_assignment.rb +0 -74
  128. data/lib/checks/check_model_attributes.rb +0 -36
  129. data/lib/checks/check_redirect.rb +0 -98
  130. data/lib/checks/check_render.rb +0 -65
  131. data/lib/checks/check_send_file.rb +0 -15
  132. data/lib/checks/check_session_settings.rb +0 -79
  133. data/lib/checks/check_sql.rb +0 -146
  134. data/lib/checks/check_validation_regex.rb +0 -60
  135. data/lib/processor.rb +0 -86
  136. data/lib/processors/alias_processor.rb +0 -384
  137. data/lib/processors/controller_alias_processor.rb +0 -237
  138. data/lib/processors/controller_processor.rb +0 -202
  139. data/lib/processors/erubis_template_processor.rb +0 -85
  140. data/lib/processors/lib/find_model_call.rb +0 -39
  141. data/lib/processors/lib/processor_helper.rb +0 -36
  142. data/lib/processors/lib/rails3_route_processor.rb +0 -184
  143. data/lib/processors/lib/route_helper.rb +0 -34
  144. data/lib/processors/params_processor.rb +0 -77
  145. data/lib/processors/route_processor.rb +0 -11
  146. data/lib/processors/template_alias_processor.rb +0 -86
  147. data/lib/report.rb +0 -680
  148. data/lib/scanner.rb +0 -227
  149. data/lib/tracker.rb +0 -144
  150. data/lib/util.rb +0 -141
  151. data/lib/version.rb +0 -1
  152. data/lib/warning.rb +0 -99
@@ -0,0 +1,362 @@
1
+ require 'rubygems'
2
+
3
+ begin
4
+ require 'ruby_parser'
5
+ require 'ruby_parser/bm_sexp.rb'
6
+ require 'ruby_parser/bm_sexp_processor.rb'
7
+ require 'brakeman/processor'
8
+ require 'brakeman/app_tree'
9
+ rescue LoadError => e
10
+ $stderr.puts e.message
11
+ $stderr.puts "Please install the appropriate dependency."
12
+ exit -1
13
+ end
14
+
15
+ #Scans the Rails application.
16
+ class Brakeman::Scanner
17
+ attr_reader :options
18
+
19
+ RUBY_1_9 = !!(RUBY_VERSION >= "1.9.0")
20
+ KNOWN_TEMPLATE_EXTENSIONS = /.*\.(erb|haml|rhtml|slim)$/
21
+
22
+ #Pass in path to the root of the Rails application
23
+ def initialize options, processor = nil
24
+ @options = options
25
+ @app_tree = Brakeman::AppTree.from_options(options)
26
+
27
+ if !@app_tree.root || !@app_tree.exists?("app")
28
+ raise Brakeman::NoApplication, "Please supply the path to a Rails application."
29
+ end
30
+
31
+ if @app_tree.exists?("script/rails")
32
+ options[:rails3] = true
33
+ Brakeman.notify "[Notice] Detected Rails 3 application"
34
+ elsif not @app_tree.exists?("script")
35
+ options[:rails3] = true # Probably need to do some refactoring
36
+ Brakeman.notify "[Notice] Detected Rails 4 application"
37
+ end
38
+
39
+ @ruby_parser = ::RubyParser
40
+ @processor = processor || Brakeman::Processor.new(@app_tree, options)
41
+ end
42
+
43
+ #Returns the Tracker generated from the scan
44
+ def tracker
45
+ @processor.tracked_events
46
+ end
47
+
48
+ #Process everything in the Rails application
49
+ def process
50
+ Brakeman.notify "Processing configuration..."
51
+ process_config
52
+ Brakeman.notify "Processing gems..."
53
+ process_gems
54
+ Brakeman.notify "Processing initializers..."
55
+ process_initializers
56
+ Brakeman.notify "Processing libs..."
57
+ process_libs
58
+ Brakeman.notify "Processing routes... "
59
+ process_routes
60
+ Brakeman.notify "Processing templates... "
61
+ process_templates
62
+ Brakeman.notify "Processing models... "
63
+ process_models
64
+ Brakeman.notify "Processing controllers... "
65
+ process_controllers
66
+ Brakeman.notify "Indexing call sites... "
67
+ index_call_sites
68
+ tracker
69
+ end
70
+
71
+ #Process config/environment.rb and config/gems.rb
72
+ #
73
+ #Stores parsed information in tracker.config
74
+ def process_config
75
+ if options[:rails3]
76
+ process_config_file "application.rb"
77
+ process_config_file "environments/production.rb"
78
+ else
79
+ process_config_file "environment.rb"
80
+ process_config_file "gems.rb"
81
+ end
82
+
83
+ if @app_tree.exists?("vendor/plugins/rails_xss") or
84
+ options[:rails3] or options[:escape_html]
85
+
86
+ tracker.config[:escape_html] = true
87
+ Brakeman.notify "[Notice] Escaping HTML by default"
88
+ end
89
+ end
90
+
91
+ def process_config_file file
92
+ path = "config/#{file}"
93
+
94
+ if @app_tree.exists?(path)
95
+ @processor.process_config(parse_ruby(@app_tree.read(path)))
96
+ end
97
+
98
+ rescue Exception => e
99
+ Brakeman.notify "[Notice] Error while processing #{path}"
100
+ tracker.error e.exception(e.message + "\nwhile processing #{path}"), e.backtrace
101
+ end
102
+
103
+ private :process_config_file
104
+
105
+ #Process Gemfile
106
+ def process_gems
107
+ if @app_tree.exists? "Gemfile"
108
+ if @app_tree.exists? "Gemfile.lock"
109
+ @processor.process_gems(parse_ruby(@app_tree.read("Gemfile")), @app_tree.read("Gemfile.lock"))
110
+ else
111
+ @processor.process_gems(parse_ruby(@app_tree.read("Gemfile")))
112
+ end
113
+ end
114
+ rescue Exception => e
115
+ Brakeman.notify "[Notice] Error while processing Gemfile."
116
+ tracker.error e.exception(e.message + "\nWhile processing Gemfile"), e.backtrace
117
+ end
118
+
119
+ #Process all the .rb files in config/initializers/
120
+ #
121
+ #Adds parsed information to tracker.initializers
122
+ def process_initializers
123
+ @app_tree.initializer_paths.each do |f|
124
+ process_initializer f
125
+ end
126
+ end
127
+
128
+ #Process an initializer
129
+ def process_initializer path
130
+ begin
131
+ @processor.process_initializer(path, parse_ruby(@app_tree.read_path(path)))
132
+ rescue Racc::ParseError => e
133
+ tracker.error e, "could not parse #{path}. There is probably a typo in the file. Test it with 'ruby_parse #{path}'"
134
+ rescue Exception => e
135
+ tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
136
+ end
137
+ end
138
+
139
+ #Process all .rb in lib/
140
+ #
141
+ #Adds parsed information to tracker.libs.
142
+ def process_libs
143
+ if options[:skip_libs]
144
+ Brakeman.notify '[Skipping]'
145
+ return
146
+ end
147
+
148
+ total = @app_tree.lib_paths.length
149
+ current = 0
150
+
151
+ @app_tree.lib_paths.each do |f|
152
+ Brakeman.debug "Processing #{f}"
153
+ report_progress(current, total)
154
+ current += 1
155
+ process_lib f
156
+ end
157
+ end
158
+
159
+ #Process a library
160
+ def process_lib path
161
+ begin
162
+ @processor.process_lib parse_ruby(@app_tree.read_path(path)), path
163
+ rescue Racc::ParseError => e
164
+ tracker.error e, "could not parse #{path}. There is probably a typo in the file. Test it with 'ruby_parse #{path}'"
165
+ rescue Exception => e
166
+ tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
167
+ end
168
+ end
169
+
170
+ #Process config/routes.rb
171
+ #
172
+ #Adds parsed information to tracker.routes
173
+ def process_routes
174
+ if @app_tree.exists?("config/routes.rb")
175
+ begin
176
+ @processor.process_routes parse_ruby(@app_tree.read("config/routes.rb"))
177
+ rescue Exception => e
178
+ tracker.error e.exception(e.message + "\nWhile processing routes.rb"), e.backtrace
179
+ Brakeman.notify "[Notice] Error while processing routes - assuming all public controller methods are actions."
180
+ options[:assume_all_routes] = true
181
+ end
182
+ else
183
+ Brakeman.notify "[Notice] No route information found"
184
+ end
185
+ end
186
+
187
+ #Process all .rb files in controllers/
188
+ #
189
+ #Adds processed controllers to tracker.controllers
190
+ def process_controllers
191
+ total = @app_tree.controller_paths.length
192
+ current = 0
193
+
194
+ @app_tree.controller_paths.each do |f|
195
+ Brakeman.debug "Processing #{f}"
196
+ report_progress(current, total)
197
+ current += 1
198
+ process_controller f
199
+ end
200
+
201
+ current = 0
202
+ total = tracker.controllers.length
203
+
204
+ Brakeman.notify "Processing data flow in controllers..."
205
+
206
+ tracker.controllers.sort_by{|name| name.to_s}.each do |name, controller|
207
+ Brakeman.debug "Processing #{name}"
208
+ report_progress(current, total, "controllers")
209
+ current += 1
210
+ @processor.process_controller_alias name, controller[:src]
211
+ end
212
+
213
+ #No longer need these processed filter methods
214
+ tracker.filter_cache.clear
215
+ end
216
+
217
+ def process_controller path
218
+ begin
219
+ @processor.process_controller(parse_ruby(@app_tree.read_path(path)), path)
220
+ rescue Racc::ParseError => e
221
+ tracker.error e, "could not parse #{path}. There is probably a typo in the file. Test it with 'ruby_parse #{path}'"
222
+ rescue Exception => e
223
+ tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
224
+ end
225
+ end
226
+
227
+ #Process all views and partials in views/
228
+ #
229
+ #Adds processed views to tracker.views
230
+ def process_templates
231
+ $stdout.sync = true
232
+
233
+ count = 0
234
+ total = @app_tree.template_paths.length
235
+
236
+ @app_tree.template_paths.each do |path|
237
+ Brakeman.debug "Processing #{path}"
238
+ report_progress(count, total)
239
+ count += 1
240
+ process_template path
241
+ end
242
+
243
+ total = tracker.templates.length
244
+ count = 0
245
+
246
+ Brakeman.notify "Processing data flow in templates..."
247
+
248
+ tracker.templates.keys.dup.sort_by{|name| name.to_s}.each do |name|
249
+ Brakeman.debug "Processing #{name}"
250
+ report_progress(count, total, "templates")
251
+ count += 1
252
+ @processor.process_template_alias tracker.templates[name]
253
+ end
254
+ end
255
+
256
+ def process_template path
257
+ type = path.match(KNOWN_TEMPLATE_EXTENSIONS)[1].to_sym
258
+ type = :erb if type == :rhtml
259
+ name = template_path_to_name path
260
+ text = @app_tree.read_path path
261
+
262
+ begin
263
+ if type == :erb
264
+ if tracker.config[:escape_html]
265
+ type = :erubis
266
+ if options[:rails3]
267
+ require 'brakeman/parsers/rails3_erubis'
268
+ src = Brakeman::Rails3Erubis.new(text).src
269
+ else
270
+ require 'brakeman/parsers/rails2_xss_plugin_erubis'
271
+ src = Brakeman::Rails2XSSPluginErubis.new(text).src
272
+ end
273
+ elsif tracker.config[:erubis]
274
+ require 'brakeman/parsers/rails2_erubis'
275
+ type = :erubis
276
+ src = Brakeman::ScannerErubis.new(text).src
277
+ else
278
+ require 'erb'
279
+ src = ERB.new(text, nil, "-").src
280
+ src.sub!(/^#.*\n/, '') if RUBY_1_9
281
+ end
282
+
283
+ parsed = parse_ruby src
284
+ elsif type == :haml
285
+ Brakeman.load_dependency 'haml'
286
+ Brakeman.load_dependency 'sass'
287
+
288
+ src = Haml::Engine.new(text,
289
+ :escape_html => !!tracker.config[:escape_html]).precompiled
290
+ parsed = parse_ruby src
291
+ elsif type == :slim
292
+ Brakeman.load_dependency 'slim'
293
+
294
+ src = Slim::Template.new(:disable_capture => true,
295
+ :generator => Temple::Generators::RailsOutputBuffer) { text }.precompiled_template
296
+
297
+ parsed = parse_ruby src
298
+ else
299
+ tracker.error "Unkown template type in #{path}"
300
+ end
301
+
302
+ @processor.process_template(name, parsed, type, nil, path)
303
+
304
+ rescue Racc::ParseError => e
305
+ tracker.error e, "could not parse #{path}"
306
+ rescue Haml::Error => e
307
+ tracker.error e, ["While compiling HAML in #{path}"] << e.backtrace
308
+ rescue Exception => e
309
+ tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
310
+ end
311
+ end
312
+
313
+ #Convert path/filename to view name
314
+ #
315
+ # views/test/something.html.erb -> test/something
316
+ def template_path_to_name path
317
+ names = path.split("/")
318
+ names.last.gsub!(/(\.(html|js)\..*|\.rhtml)$/, '')
319
+ names[(names.index("views") + 1)..-1].join("/").to_sym
320
+ end
321
+
322
+ #Process all the .rb files in models/
323
+ #
324
+ #Adds the processed models to tracker.models
325
+ def process_models
326
+ total = @app_tree.model_paths.length
327
+ current = 0
328
+
329
+ @app_tree.model_paths.each do |f|
330
+ Brakeman.debug "Processing #{f}"
331
+ report_progress(current, total)
332
+ current += 1
333
+ process_model f
334
+ end
335
+ end
336
+
337
+ def process_model path
338
+ begin
339
+ @processor.process_model(parse_ruby(@app_tree.read_path(path)), path)
340
+ rescue Racc::ParseError => e
341
+ tracker.error e, "could not parse #{path}"
342
+ rescue Exception => e
343
+ tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
344
+ end
345
+ end
346
+
347
+ def report_progress(current, total, type = "files")
348
+ return unless @options[:report_progress]
349
+ $stderr.print " #{current}/#{total} #{type} processed\r"
350
+ end
351
+
352
+ def index_call_sites
353
+ tracker.index_call_sites
354
+ end
355
+
356
+ def parse_ruby input
357
+ @ruby_parser.new.parse input
358
+ end
359
+ end
360
+
361
+ # This is to allow operation without loading the Haml library
362
+ module Haml; class Error < StandardError; end; end
@@ -0,0 +1,296 @@
1
+ require 'set'
2
+ require 'brakeman/call_index'
3
+ require 'brakeman/checks'
4
+ require 'brakeman/report'
5
+ require 'brakeman/processors/lib/find_call'
6
+ require 'brakeman/processors/lib/find_all_calls'
7
+
8
+ #The Tracker keeps track of all the processed information.
9
+ class Brakeman::Tracker
10
+ attr_accessor :controllers, :templates, :models, :errors,
11
+ :checks, :initializers, :config, :routes, :processor, :libs,
12
+ :template_cache, :options, :filter_cache, :start_time, :end_time,
13
+ :duration, :ignored_filter
14
+
15
+ #Place holder when there should be a model, but it is not
16
+ #clear what model it will be.
17
+ UNKNOWN_MODEL = :BrakemanUnresolvedModel
18
+
19
+ #Creates a new Tracker.
20
+ #
21
+ #The Processor argument is only used by other Processors
22
+ #that might need to access it.
23
+ def initialize(app_tree, processor = nil, options = {})
24
+ @app_tree = app_tree
25
+ @processor = processor
26
+ @options = options
27
+
28
+ @config = { :rails => {} }
29
+ @templates = {}
30
+ @controllers = {}
31
+ #Initialize models with the unknown model so
32
+ #we can match models later without knowing precisely what
33
+ #class they are.
34
+ @models = { UNKNOWN_MODEL => { :name => UNKNOWN_MODEL,
35
+ :parent => nil,
36
+ :includes => [],
37
+ :public => {},
38
+ :private => {},
39
+ :protected => {},
40
+ :options => {} } }
41
+ @routes = {}
42
+ @initializers = {}
43
+ @errors = []
44
+ @libs = {}
45
+ @checks = nil
46
+ @processed = nil
47
+ @template_cache = Set.new
48
+ @filter_cache = {}
49
+ @call_index = nil
50
+ @start_time = Time.now
51
+ @end_time = nil
52
+ @duration = nil
53
+ end
54
+
55
+ #Add an error to the list. If no backtrace is given,
56
+ #the one from the exception will be used.
57
+ def error exception, backtrace = nil
58
+ backtrace ||= exception.backtrace
59
+ unless backtrace.is_a? Array
60
+ backtrace = [ backtrace ]
61
+ end
62
+
63
+ Brakeman.debug exception
64
+ Brakeman.debug backtrace
65
+
66
+ @errors << { :error => exception.to_s.gsub("\n", " "), :backtrace => backtrace }
67
+ end
68
+
69
+ #Run a set of checks on the current information. Results will be stored
70
+ #in Tracker#checks.
71
+ def run_checks
72
+ @checks = Brakeman::Checks.run_checks(@app_tree, self)
73
+
74
+ @end_time = Time.now
75
+ @duration = @end_time - @start_time
76
+ @checks
77
+ end
78
+
79
+ #Iterate over all methods in controllers and models.
80
+ def each_method
81
+ [self.controllers, self.models].each do |set|
82
+ set.each do |set_name, info|
83
+ [:private, :public, :protected].each do |visibility|
84
+ info[visibility].each do |method_name, definition|
85
+ if definition.node_type == :selfdef
86
+ method_name = "#{definition[1]}.#{method_name}"
87
+ end
88
+
89
+ yield definition, set_name, method_name, info[:file]
90
+
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ #Iterates over each template, yielding the name and the template.
98
+ #Prioritizes templates which have been rendered.
99
+ def each_template
100
+ if @processed.nil?
101
+ @processed, @rest = templates.keys.sort_by{|template| template.to_s}.partition { |k| k.to_s.include? "." }
102
+ end
103
+
104
+ @processed.each do |k|
105
+ yield k, templates[k]
106
+ end
107
+
108
+ @rest.each do |k|
109
+ yield k, templates[k]
110
+ end
111
+ end
112
+
113
+ #Find a method call.
114
+ #
115
+ #Options:
116
+ # * :target => target name(s)
117
+ # * :method => method name(s)
118
+ # * :chained => search in method chains
119
+ #
120
+ #If :target => false or :target => nil, searches for methods without a target.
121
+ #Targets and methods can be specified as a symbol, an array of symbols,
122
+ #or a regular expression.
123
+ #
124
+ #If :chained => true, matches target at head of method chain and method at end.
125
+ #
126
+ #For example:
127
+ #
128
+ # find_call :target => User, :method => :all, :chained => true
129
+ #
130
+ #could match
131
+ #
132
+ # User.human.active.all(...)
133
+ #
134
+ def find_call options
135
+ index_call_sites unless @call_index
136
+ @call_index.find_calls options
137
+ end
138
+
139
+ #Searches the initializers for a method call
140
+ def check_initializers target, method
141
+ finder = Brakeman::FindCall.new target, method, self
142
+
143
+ initializers.sort.each do |name, initializer|
144
+ finder.process_source initializer
145
+ end
146
+
147
+ finder.matches
148
+ end
149
+
150
+ #Returns a Report with this Tracker's information
151
+ def report
152
+ Brakeman::Report.new(@app_tree, self)
153
+ end
154
+
155
+ def warnings
156
+ self.checks.all_warnings
157
+ end
158
+
159
+ def index_call_sites
160
+ finder = Brakeman::FindAllCalls.new self
161
+
162
+ self.each_method do |definition, set_name, method_name, file|
163
+ finder.process_source definition, :class => set_name, :method => method_name, :file => file
164
+ end
165
+
166
+ self.each_template do |name, template|
167
+ finder.process_source template[:src], :template => template, :file => template[:file]
168
+ end
169
+
170
+ @call_index = Brakeman::CallIndex.new finder.calls
171
+ end
172
+
173
+ #Reindex call sites
174
+ #
175
+ #Takes a set of symbols which can include :templates, :models,
176
+ #or :controllers
177
+ #
178
+ #This will limit reindexing to the given sets
179
+ def reindex_call_sites locations
180
+ #If reindexing templates, models, and controllers, just redo
181
+ #everything
182
+ if locations.length == 3
183
+ return index_call_sites
184
+ end
185
+
186
+ if locations.include? :templates
187
+ @call_index.remove_template_indexes
188
+ end
189
+
190
+ classes_to_reindex = Set.new
191
+ method_sets = []
192
+
193
+ if locations.include? :models
194
+ classes_to_reindex.merge self.models.keys
195
+ method_sets << self.models
196
+ end
197
+
198
+ if locations.include? :controllers
199
+ classes_to_reindex.merge self.controllers.keys
200
+ method_sets << self.controllers
201
+ end
202
+
203
+ @call_index.remove_indexes_by_class classes_to_reindex
204
+
205
+ finder = Brakeman::FindAllCalls.new self
206
+
207
+ method_sets.each do |set|
208
+ set.each do |set_name, info|
209
+ [:private, :public, :protected].each do |visibility|
210
+ info[visibility].each do |method_name, definition|
211
+ if definition.node_type == :selfdef
212
+ method_name = "#{definition[1]}.#{method_name}"
213
+ end
214
+
215
+ finder.process_source definition, :class => set_name, :method => method_name, :file => info[:file]
216
+
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ if locations.include? :templates
223
+ self.each_template do |name, template|
224
+ finder.process_source template[:src], :template => template, :file => template[:file]
225
+ end
226
+ end
227
+
228
+ @call_index.index_calls finder.calls
229
+ end
230
+
231
+ #Clear information related to templates.
232
+ #If :only_rendered => true, will delete templates rendered from
233
+ #controllers (but not those rendered from other templates)
234
+ def reset_templates options = { :only_rendered => false }
235
+ if options[:only_rendered]
236
+ @templates.delete_if do |name, template|
237
+ name.to_s.include? "Controller#"
238
+ end
239
+ else
240
+ @templates = {}
241
+ end
242
+ @processed = nil
243
+ @rest = nil
244
+ @template_cache.clear
245
+ end
246
+
247
+ #Clear information related to template
248
+ def reset_template name
249
+ name = name.to_sym
250
+ @templates.delete name
251
+ @processed = nil
252
+ @rest = nil
253
+ @template_cache.clear
254
+ end
255
+
256
+ #Clear information related to model
257
+ def reset_model path
258
+ model_name = nil
259
+
260
+ @models.each do |name, model|
261
+ if model[:file] == path
262
+ model_name = name
263
+ break
264
+ end
265
+ end
266
+
267
+ @models.delete model_name
268
+ end
269
+
270
+ def reset_controller path
271
+ #Remove from controller
272
+ @controllers.delete_if do |name, controller|
273
+ if controller[:file] == path
274
+ template_matcher = /^#{name}#/
275
+
276
+ #Remove templates rendered from this controller
277
+ @templates.each do |template_name, template|
278
+ if template[:caller] and not template[:caller].grep(template_matcher).empty?
279
+ reset_template template_name
280
+ @call_index.remove_template_indexes template_name
281
+ end
282
+ end
283
+
284
+ #Remove calls indexed from this controller
285
+ @call_index.remove_indexes_by_class [name]
286
+
287
+ true
288
+ end
289
+ end
290
+ end
291
+
292
+ #Clear information about routes
293
+ def reset_routes
294
+ @routes = {}
295
+ end
296
+ end