brakeman 0.0.2

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 (47) hide show
  1. data/FEATURES +16 -0
  2. data/README.md +112 -0
  3. data/WARNING_TYPES +69 -0
  4. data/bin/brakeman +266 -0
  5. data/lib/checks.rb +67 -0
  6. data/lib/checks/base_check.rb +338 -0
  7. data/lib/checks/check_cross_site_scripting.rb +216 -0
  8. data/lib/checks/check_default_routes.rb +29 -0
  9. data/lib/checks/check_evaluation.rb +29 -0
  10. data/lib/checks/check_execute.rb +110 -0
  11. data/lib/checks/check_file_access.rb +46 -0
  12. data/lib/checks/check_forgery_setting.rb +25 -0
  13. data/lib/checks/check_mass_assignment.rb +72 -0
  14. data/lib/checks/check_model_attributes.rb +36 -0
  15. data/lib/checks/check_redirect.rb +98 -0
  16. data/lib/checks/check_render.rb +65 -0
  17. data/lib/checks/check_send_file.rb +15 -0
  18. data/lib/checks/check_session_settings.rb +36 -0
  19. data/lib/checks/check_sql.rb +124 -0
  20. data/lib/checks/check_validation_regex.rb +60 -0
  21. data/lib/format/style.css +105 -0
  22. data/lib/processor.rb +83 -0
  23. data/lib/processors/alias_processor.rb +384 -0
  24. data/lib/processors/base_processor.rb +235 -0
  25. data/lib/processors/config_processor.rb +146 -0
  26. data/lib/processors/controller_alias_processor.rb +222 -0
  27. data/lib/processors/controller_processor.rb +175 -0
  28. data/lib/processors/erb_template_processor.rb +84 -0
  29. data/lib/processors/erubis_template_processor.rb +62 -0
  30. data/lib/processors/haml_template_processor.rb +115 -0
  31. data/lib/processors/lib/find_call.rb +176 -0
  32. data/lib/processors/lib/find_model_call.rb +39 -0
  33. data/lib/processors/lib/processor_helper.rb +36 -0
  34. data/lib/processors/lib/render_helper.rb +118 -0
  35. data/lib/processors/library_processor.rb +117 -0
  36. data/lib/processors/model_processor.rb +125 -0
  37. data/lib/processors/output_processor.rb +204 -0
  38. data/lib/processors/params_processor.rb +77 -0
  39. data/lib/processors/route_processor.rb +338 -0
  40. data/lib/processors/template_alias_processor.rb +86 -0
  41. data/lib/processors/template_processor.rb +55 -0
  42. data/lib/report.rb +628 -0
  43. data/lib/scanner.rb +232 -0
  44. data/lib/tracker.rb +144 -0
  45. data/lib/util.rb +141 -0
  46. data/lib/warning.rb +97 -0
  47. metadata +191 -0
@@ -0,0 +1,77 @@
1
+ require 'rubygems'
2
+ require 'sexp_processor'
3
+ require 'set'
4
+
5
+ #Looks for request parameters. Not used currently.
6
+ class ParamsProcessor < SexpProcessor
7
+ attr_reader :result
8
+
9
+ def initialize
10
+ super()
11
+ self.strict = false
12
+ self.auto_shift_type = false
13
+ self.require_empty = false
14
+ self.default_method = :process_default
15
+ self.warn_on_default = false
16
+ @result = []
17
+ @matched = false
18
+ @mark = false
19
+ @watch_nodes = Set.new([:call, :iasgn, :lasgn, :gasgn, :cvasgn, :return, :attrasgn])
20
+ @params = Sexp.new(:call, nil, :params, Sexp.new(:arglist))
21
+ end
22
+
23
+ def process_default exp
24
+ if @watch_nodes.include?(exp.node_type) and not @mark
25
+ @mark = true
26
+ @matched = false
27
+ process_these exp[1..-1]
28
+ if @matched
29
+ @result << exp
30
+ @matched = false
31
+ end
32
+ @mark = false
33
+ else
34
+ process_these exp[1..-1]
35
+ end
36
+
37
+ exp
38
+ end
39
+
40
+ def process_these exp
41
+ exp.each do |e|
42
+ if sexp? e and not e.empty?
43
+ process e
44
+ end
45
+ end
46
+ end
47
+
48
+ def process_call exp
49
+ if @mark
50
+ actually_process_call exp
51
+ else
52
+ @mark = true
53
+ actually_process_call exp
54
+ if @matched
55
+ @result << exp
56
+ end
57
+ @mark = @matched = false
58
+ end
59
+
60
+ exp
61
+ end
62
+
63
+ def actually_process_call exp
64
+ process exp[1]
65
+ process exp[3]
66
+ if exp[1] == @params or exp == @params
67
+ @matched = true
68
+ end
69
+ end
70
+
71
+ #Don't really care about condition
72
+ def process_if exp
73
+ process_these exp[2..-1]
74
+ exp
75
+ end
76
+
77
+ end
@@ -0,0 +1,338 @@
1
+ require 'processors/base_processor'
2
+ require 'processors/alias_processor'
3
+ require 'util'
4
+ require 'set'
5
+
6
+ #Processes the Sexp from routes.rb. Stores results in tracker.routes.
7
+ #
8
+ #Note that it is only interested in determining what methods on which
9
+ #controllers are used as routes, not the generated URLs for routes.
10
+ class RoutesProcessor < BaseProcessor
11
+ attr_reader :map, :nested, :current_controller
12
+
13
+ def initialize tracker
14
+ super
15
+ @map = Sexp.new(:lvar, :map)
16
+ @nested = nil #used for identifying nested targets
17
+ @prefix = [] #Controller name prefix (a module name, usually)
18
+ @current_controller = nil
19
+ @with_options = nil #For use inside map.with_options
20
+ end
21
+
22
+ #Call this with parsed route file information.
23
+ #
24
+ #This method first calls RouteAliasProcessor#process_safely on the +exp+,
25
+ #so it does not modify the +exp+.
26
+ def process_routes exp
27
+ process RouteAliasProcessor.new.process_safely(exp)
28
+ end
29
+
30
+ #Looking for mapping of routes
31
+ def process_call exp
32
+ target = exp[1]
33
+
34
+ if target == map or target == nested
35
+ process_map exp
36
+
37
+ else
38
+ process_default exp
39
+ end
40
+
41
+ exp
42
+ end
43
+
44
+ #Process a map.something call
45
+ #based on the method used
46
+ def process_map exp
47
+ args = exp[3][1..-1]
48
+
49
+ case exp[2]
50
+ when :resource
51
+ process_resource args
52
+ when :resources
53
+ process_resources args
54
+ when :connect, :root
55
+ process_connect args
56
+ else
57
+ process_named_route args
58
+ end
59
+
60
+ exp
61
+ end
62
+
63
+ #Look for map calls that take a block.
64
+ #Otherwise, just do the default processing.
65
+ def process_iter exp
66
+ if exp[1][1] == map or exp[1][1] == nested
67
+ method = exp[1][2]
68
+ case method
69
+ when :namespace
70
+ process_namespace exp
71
+ when :resources, :resource
72
+ process_resources exp[1][3][1..-1]
73
+ process_default exp[3]
74
+ when :with_options
75
+ process_with_options exp
76
+ end
77
+ exp
78
+ else
79
+ super
80
+ end
81
+ end
82
+
83
+ #Process
84
+ # map.resources :x, :controller => :y, :member => ...
85
+ #etc.
86
+ def process_resources exp
87
+ controller = check_for_controller_name exp
88
+ if controller
89
+ self.current_controller = controller
90
+ process_resource_options exp[-1]
91
+ else
92
+ exp.each do |argument|
93
+ if sexp? argument and argument.node_type == :lit
94
+ self.current_controller = exp[0][1]
95
+ add_resources_routes
96
+ process_resource_options exp[-1]
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ #Add default routes
103
+ def add_resources_routes
104
+ @tracker.routes[@current_controller].merge [:index, :new, :create, :show, :edit, :update, :destroy]
105
+ end
106
+
107
+ #Process all the options that might be in the hash passed to
108
+ #map.resource, et al.
109
+ def process_resource_options exp
110
+ if exp.nil? and @with_options
111
+ exp = @with_options
112
+ elsif @with_options
113
+ exp = exp.concat @with_options[1..-1]
114
+ end
115
+ return unless exp.node_type == :hash
116
+
117
+ hash_iterate(exp) do |option, value|
118
+ case option[1]
119
+ when :controller, :requirements, :singular, :path_prefix, :as,
120
+ :path_names, :shallow, :name_prefix
121
+ #should be able to skip
122
+ when :collection, :member, :new
123
+ process_collection value
124
+ when :has_one
125
+ save_controller = current_controller
126
+ process_resource value[1..-1]
127
+ self.current_controller = save_controller
128
+ when :has_many
129
+ save_controller = current_controller
130
+ process_resources value[1..-1]
131
+ self.current_controller = save_controller
132
+ when :only
133
+ process_option_only value
134
+ when :except
135
+ process_option_except value
136
+ else
137
+ raise "Unhandled resource option: #{option}"
138
+ end
139
+ end
140
+ end
141
+
142
+ #Process route option :only => ...
143
+ def process_option_only exp
144
+ routes = @tracker.routes[@current_controller]
145
+ [:index, :new, :create, :show, :edit, :update, :destroy].each do |r|
146
+ routes.delete r
147
+ end
148
+
149
+ if exp.node_type == :array
150
+ exp[1..-1].each do |e|
151
+ routes << e[1]
152
+ end
153
+ end
154
+ end
155
+
156
+ #Process route option :except => ...
157
+ def process_option_except exp
158
+ return unless exp.node_type == :array
159
+ routes = @tracker.routes[@current_controller]
160
+
161
+ exp[1..-1].each do |e|
162
+ routes.delete e[1]
163
+ end
164
+ end
165
+
166
+ # map.resource :x, ..
167
+ def process_resource exp
168
+ controller = check_for_controller_name exp
169
+ if controller
170
+ self.current_controller = controller
171
+ process_resource_options exp[-1]
172
+ else
173
+ exp.each do |argument|
174
+ if argument.node_type == :lit
175
+ self.current_controller = pluralize(exp[0][1].to_s)
176
+ add_resource_routes
177
+ process_resource_options exp[-1]
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ #Add default routes minus :index
184
+ def add_resource_routes
185
+ @tracker.routes[@current_controller].merge [:new, :create, :show, :edit, :update, :destroy]
186
+ end
187
+
188
+ #Process
189
+ # map.connect '/something', :controller => 'blah', :action => 'whatever'
190
+ def process_connect exp
191
+ controller = check_for_controller_name exp
192
+ self.current_controller = controller if controller
193
+
194
+ #Check for default route
195
+ if string? exp[0]
196
+ if exp[0][1] == ":controller/:action/:id"
197
+ @tracker.routes[:allow_all_actions] = exp[0]
198
+ elsif exp[0][1].include? ":action"
199
+ @tracker.routes[@current_controller] = :allow_all_actions
200
+ return
201
+ end
202
+ end
203
+
204
+ #This -seems- redundant, but people might connect actions
205
+ #to a controller which already allows them all
206
+ return if @tracker.routes[@current_controller] == :allow_all_actions
207
+
208
+ exp[-1].each_with_index do |e,i|
209
+ if symbol? e and e[1] == :action
210
+ @tracker.routes[@current_controller] << exp[-1][i + 1][1].to_sym
211
+ return
212
+ end
213
+ end
214
+ end
215
+
216
+ # map.with_options :controller => 'something' do |something|
217
+ # something.resources :blah
218
+ # end
219
+ def process_with_options exp
220
+ @with_options = exp[1][3][-1]
221
+ @nested = Sexp.new(:lvar, exp[2][1])
222
+
223
+ self.current_controller = check_for_controller_name exp[1][3]
224
+
225
+ #process block
226
+ process exp[3]
227
+
228
+ @with_options = nil
229
+ @nested = nil
230
+ end
231
+
232
+ # map.namespace :something do |something|
233
+ # something.resources :blah
234
+ # end
235
+ def process_namespace exp
236
+ call = exp[1]
237
+ formal_args = exp[2]
238
+ block = exp[3]
239
+
240
+ @prefix << camelize(call[3][1][1])
241
+
242
+ @nested = Sexp.new(:lvar, formal_args[1])
243
+
244
+ process block
245
+
246
+ @prefix.pop
247
+ end
248
+
249
+ # map.something_abnormal '/blah', :controller => 'something', :action => 'wohoo'
250
+ def process_named_route exp
251
+ process_connect exp
252
+ end
253
+
254
+ #Process collection option
255
+ # :collection => { :some_action => :http_actions }
256
+ def process_collection exp
257
+ return unless exp.node_type == :hash
258
+ routes = @tracker.routes[@current_controller]
259
+
260
+ hash_iterate(exp) do |action, type|
261
+ routes << action[1]
262
+ end
263
+ end
264
+
265
+ #Manage Controller prefixes
266
+ #@prefix is an Array, but this method returns a string
267
+ #suitable for prefixing onto a controller name.
268
+ def prefix
269
+ if @prefix.length > 0
270
+ @prefix.join("::") << "::"
271
+ else
272
+ ''
273
+ end
274
+ end
275
+
276
+ #Sets the controller name to a proper class name.
277
+ #For example
278
+ # self.current_controller = :session
279
+ # @controller == :SessionController #true
280
+ #
281
+ #Also prepends the prefix if there is one set.
282
+ def current_controller= name
283
+ @current_controller = (prefix + camelize(name) + "Controller").to_sym
284
+ @tracker.routes[@current_controller] ||= Set.new
285
+ end
286
+
287
+ private
288
+
289
+ #Checks an argument list for a hash that has a key :controller.
290
+ #If it does, returns the value.
291
+ #
292
+ #Otherwise, returns nil.
293
+ def check_for_controller_name args
294
+ args.each do |a|
295
+ if hash? a
296
+ hash_iterate(a) do |k, v|
297
+ if k[1] == :controller
298
+ return v[1]
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ nil
305
+ end
306
+ end
307
+
308
+ #This is for a really specific case where a hash is used as arguments
309
+ #to one of the map methods.
310
+ class RouteAliasProcessor < AliasProcessor
311
+
312
+ #This replaces
313
+ # { :some => :hash }.keys
314
+ #with
315
+ # [:some]
316
+ def process_call exp
317
+ process_default exp
318
+
319
+ if hash? exp[1] and exp[2] == :keys
320
+ keys = get_keys exp[1]
321
+ exp.clear
322
+ keys.each_with_index do |e,i|
323
+ exp[i] = e
324
+ end
325
+ end
326
+ exp
327
+ end
328
+
329
+ #Returns an array Sexp containing the keys from the hash
330
+ def get_keys hash
331
+ keys = Sexp.new(:array)
332
+ hash_iterate(hash) do |key, value|
333
+ keys << key
334
+ end
335
+
336
+ keys
337
+ end
338
+ end
@@ -0,0 +1,86 @@
1
+ require 'set'
2
+ require 'processors/alias_processor'
3
+ require 'processors/lib/render_helper'
4
+
5
+ #Processes aliasing in templates.
6
+ #Handles calls to +render+.
7
+ class TemplateAliasProcessor < AliasProcessor
8
+ include RenderHelper
9
+
10
+ FORM_METHODS = Set.new([:form_for, :remote_form_for, :form_remote_for])
11
+
12
+ def initialize tracker, template
13
+ super()
14
+ @tracker = tracker
15
+ @template = template
16
+ end
17
+
18
+ #Process template
19
+ def process_template name, args
20
+ super name, args, "Template:#{@template[:name]}"
21
+ end
22
+
23
+ #Determine template name
24
+ def template_name name
25
+ unless name.to_s.include? "/"
26
+ name = "#{@template[:name].to_s.match(/^(.*\/).*$/)[1]}#{name}"
27
+ end
28
+ name
29
+ end
30
+
31
+ #Looks for form methods and iterating over collections of Models
32
+ def process_call_with_block exp
33
+ process_default exp
34
+
35
+ call = exp[1]
36
+ target = call[1]
37
+ method = call[2]
38
+ args = exp[2]
39
+ block = exp[3]
40
+
41
+ #Check for e.g. Model.find.each do ... end
42
+ if method == :each and args and block and model = get_model_target(target)
43
+ if sexp? args and args.node_type == :lasgn
44
+ if model == target[1]
45
+ env[Sexp.new(:lvar, args[1])] = Sexp.new(:call, model, :new, Sexp.new(:arglist))
46
+ else
47
+ env[Sexp.new(:lvar, args[1])] = Sexp.new(:call, Sexp.new(:const, Tracker::UNKNOWN_MODEL), :new, Sexp.new(:arglist))
48
+ end
49
+
50
+ process block if sexp? block
51
+ end
52
+ elsif FORM_METHODS.include? method
53
+ if sexp? args and args.node_type == :lasgn
54
+ env[Sexp.new(:lvar, args[1])] = Sexp.new(:call, Sexp.new(:const, :FormBuilder), :new, Sexp.new(:arglist))
55
+
56
+ process block if sexp? block
57
+ end
58
+ end
59
+
60
+ exp
61
+ end
62
+
63
+ alias process_iter process_call_with_block
64
+
65
+ #Checks if +exp+ is a call to Model.all or Model.find*
66
+ def get_model_target exp
67
+ if call? exp
68
+ target = exp[1]
69
+
70
+ if exp[2] == :all or exp[2].to_s[0,4] == "find"
71
+ models = Set.new @tracker.models.keys
72
+
73
+ begin
74
+ name = class_name target
75
+ return target if models.include?(name)
76
+ rescue StandardError
77
+ end
78
+
79
+ end
80
+
81
+ return get_model_target(target)
82
+ end
83
+
84
+ false
85
+ end
86
+ end