brakeman 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/README.md +2 -2
  2. data/lib/brakeman.rb +3 -1
  3. data/lib/brakeman/checks/base_check.rb +10 -2
  4. data/lib/brakeman/checks/check_basic_auth.rb +2 -0
  5. data/lib/brakeman/checks/check_cross_site_scripting.rb +4 -0
  6. data/lib/brakeman/checks/check_default_routes.rb +2 -0
  7. data/lib/brakeman/checks/check_escape_function.rb +2 -0
  8. data/lib/brakeman/checks/check_evaluation.rb +2 -0
  9. data/lib/brakeman/checks/check_execute.rb +2 -0
  10. data/lib/brakeman/checks/check_file_access.rb +2 -0
  11. data/lib/brakeman/checks/check_filter_skipping.rb +2 -0
  12. data/lib/brakeman/checks/check_forgery_setting.rb +2 -0
  13. data/lib/brakeman/checks/check_link_to.rb +2 -0
  14. data/lib/brakeman/checks/check_mail_to.rb +2 -0
  15. data/lib/brakeman/checks/check_mass_assignment.rb +10 -2
  16. data/lib/brakeman/checks/check_model_attributes.rb +47 -13
  17. data/lib/brakeman/checks/check_nested_attributes.rb +2 -0
  18. data/lib/brakeman/checks/check_quote_table_name.rb +2 -0
  19. data/lib/brakeman/checks/check_redirect.rb +2 -0
  20. data/lib/brakeman/checks/check_render.rb +2 -0
  21. data/lib/brakeman/checks/check_response_splitting.rb +2 -0
  22. data/lib/brakeman/checks/check_send_file.rb +2 -0
  23. data/lib/brakeman/checks/check_session_settings.rb +2 -0
  24. data/lib/brakeman/checks/check_sql.rb +42 -3
  25. data/lib/brakeman/checks/check_strip_tags.rb +2 -0
  26. data/lib/brakeman/checks/check_translate_bug.rb +2 -0
  27. data/lib/brakeman/checks/check_validation_regex.rb +2 -0
  28. data/lib/brakeman/checks/check_without_protection.rb +3 -1
  29. data/lib/brakeman/format/style.css +5 -0
  30. data/lib/brakeman/options.rb +13 -4
  31. data/lib/brakeman/processor.rb +2 -2
  32. data/lib/brakeman/processors/alias_processor.rb +65 -21
  33. data/lib/brakeman/processors/controller_alias_processor.rb +26 -8
  34. data/lib/brakeman/processors/lib/find_all_calls.rb +7 -1
  35. data/lib/brakeman/processors/lib/processor_helper.rb +2 -0
  36. data/lib/brakeman/processors/lib/rails3_config_processor.rb +1 -1
  37. data/lib/brakeman/processors/library_processor.rb +1 -1
  38. data/lib/brakeman/processors/output_processor.rb +3 -1
  39. data/lib/brakeman/report.rb +2 -1
  40. data/lib/brakeman/rescanner.rb +27 -4
  41. data/lib/brakeman/scanner.rb +21 -1
  42. data/lib/brakeman/tracker.rb +2 -1
  43. data/lib/brakeman/util.rb +5 -0
  44. data/lib/brakeman/version.rb +1 -1
  45. metadata +16 -8
data/README.md CHANGED
@@ -12,7 +12,7 @@ For even more continuous testing, try the [Guard plugin](https://github.com/oreo
12
12
 
13
13
  Website: http://brakemanscanner.org/
14
14
 
15
- Twitter: http://twitter.com/brakemanscanner
15
+ Twitter: http://twitter.com/brakeman
16
16
 
17
17
  Mailing list: brakeman@librelist.com
18
18
 
@@ -89,7 +89,7 @@ If Brakeman is running a bit slow, try
89
89
 
90
90
  This will disable some features, but will probably be much faster (currently it is the same as `--skip-libs --no-branching`). *WARNING*: This may cause Brakeman to miss some vulnerabilities.
91
91
 
92
- By default, Brakeman will return 0 as an exit code unless something when very wrong. To return an error code when warnings were found:
92
+ By default, Brakeman will return 0 as an exit code unless something went very wrong. To return an error code when warnings were found:
93
93
 
94
94
  brakeman -z
95
95
 
@@ -162,7 +162,9 @@ module Brakeman
162
162
  require 'brakeman/scanner'
163
163
  $stderr.puts "Available Checks:"
164
164
  $stderr.puts "-" * 30
165
- $stderr.puts Checks.checks.map { |c| c.to_s.match(/^Brakeman::(.*)$/)[1] }.sort.join "\n"
165
+ $stderr.puts Checks.checks.map { |c|
166
+ c.to_s.match(/^Brakeman::(.*)$/)[1].ljust(27) << c.description
167
+ }.sort.join "\n"
166
168
  end
167
169
 
168
170
  #Installs Rake task for running Brakeman,
@@ -68,6 +68,8 @@ class Brakeman::BaseCheck < SexpProcessor
68
68
  @has_user_input = :params
69
69
  elsif cookies? exp[1]
70
70
  @has_user_input = :cookies
71
+ elsif request_env? exp[1]
72
+ @has_user_input = :request
71
73
  elsif sexp? exp[1] and model_name? exp[1][1]
72
74
  @has_user_input = :model
73
75
  end
@@ -103,13 +105,13 @@ class Brakeman::BaseCheck < SexpProcessor
103
105
  end
104
106
 
105
107
  #Checks if the model inherits from parent,
106
- def parent? tracker, model, parent
108
+ def parent? model, parent
107
109
  if model == nil
108
110
  false
109
111
  elsif model[:parent] == parent
110
112
  true
111
113
  elsif model[:parent]
112
- parent? tracker, tracker.models[model[:parent]], parent
114
+ parent? tracker.models[model[:parent]], parent
113
115
  else
114
116
  false
115
117
  end
@@ -209,6 +211,8 @@ class Brakeman::BaseCheck < SexpProcessor
209
211
  return :params, exp
210
212
  elsif cookies? exp[1]
211
213
  return :cookies, exp
214
+ elsif request_env? exp[1]
215
+ return :request, exp
212
216
  else
213
217
  false
214
218
  end
@@ -396,4 +400,8 @@ class Brakeman::BaseCheck < SexpProcessor
396
400
  "config/environment.rb"
397
401
  end
398
402
  end
403
+
404
+ def self.description
405
+ @description
406
+ end
399
407
  end
@@ -7,6 +7,8 @@ require 'brakeman/checks/base_check'
7
7
  class Brakeman::CheckBasicAuth < Brakeman::BaseCheck
8
8
  Brakeman::Checks.add self
9
9
 
10
+ @description = "Checks for the use of http_basic_authenticate_with"
11
+
10
12
  def run_check
11
13
  return if version_between? "0.0.0", "3.0.99"
12
14
 
@@ -14,6 +14,8 @@ require 'set'
14
14
  class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
15
15
  Brakeman::Checks.add self
16
16
 
17
+ @description = "Checks for unescaped output in views"
18
+
17
19
  #Model methods which are known to be harmless
18
20
  IGNORE_MODEL_METHODS = Set.new([:average, :count, :maximum, :minimum, :sum])
19
21
 
@@ -84,6 +86,8 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
84
86
  message = "Unescaped parameter value"
85
87
  when :cookies
86
88
  message = "Unescaped cookie value"
89
+ when :request
90
+ message = "Unescaped request value"
87
91
  else
88
92
  message = "Unescaped user input value"
89
93
  end
@@ -4,6 +4,8 @@ require 'brakeman/checks/base_check'
4
4
  class Brakeman::CheckDefaultRoutes < Brakeman::BaseCheck
5
5
  Brakeman::Checks.add self
6
6
 
7
+ @description = "Checks for default routes"
8
+
7
9
  #Checks for :allow_all_actions globally and for individual routes
8
10
  #if it is not enabled globally.
9
11
  def run_check
@@ -5,6 +5,8 @@ require 'brakeman/checks/base_check'
5
5
  class Brakeman::CheckEscapeFunction < Brakeman::BaseCheck
6
6
  Brakeman::Checks.add self
7
7
 
8
+ @description = "Checks for versions before 2.3.14 which have a vulnerable escape method"
9
+
8
10
  def run_check
9
11
  if version_between?('2.0.0', '2.3.13') and RUBY_VERSION < '1.9.0'
10
12
 
@@ -5,6 +5,8 @@ require 'brakeman/checks/base_check'
5
5
  class Brakeman::CheckEvaluation < Brakeman::BaseCheck
6
6
  Brakeman::Checks.add self
7
7
 
8
+ @description = "Searches for evaluation of user input"
9
+
8
10
  #Process calls
9
11
  def run_check
10
12
  Brakeman.debug "Finding eval-like calls"
@@ -11,6 +11,8 @@ require 'brakeman/checks/base_check'
11
11
  class Brakeman::CheckExecute < Brakeman::BaseCheck
12
12
  Brakeman::Checks.add self
13
13
 
14
+ @description = "Finds instances of possible command injection"
15
+
14
16
  #Check models, controllers, and views for command injection.
15
17
  def run_check
16
18
  Brakeman.debug "Finding system calls using ``"
@@ -5,6 +5,8 @@ require 'brakeman/processors/lib/processor_helper'
5
5
  class Brakeman::CheckFileAccess < Brakeman::BaseCheck
6
6
  Brakeman::Checks.add self
7
7
 
8
+ @description = "Finds possible file access using user input"
9
+
8
10
  def run_check
9
11
  Brakeman.debug "Finding possible file access"
10
12
  methods = tracker.find_call :targets => [:Dir, :File, :IO, :Kernel, :"Net::FTP", :"Net::HTTP", :PStore, :Pathname, :Shell, :YAML], :methods => [:[], :chdir, :chroot, :delete, :entries, :foreach, :glob, :install, :lchmod, :lchown, :link, :load, :load_file, :makedirs, :move, :new, :open, :read, :read_lines, :rename, :rmdir, :safe_unlink, :symlink, :syscopy, :sysopen, :truncate, :unlink]
@@ -5,6 +5,8 @@ require 'brakeman/checks/base_check'
5
5
  class Brakeman::CheckFilterSkipping < Brakeman::BaseCheck
6
6
  Brakeman::Checks.add self
7
7
 
8
+ @description = "Checks for versions 3.0-3.0.9 which had a vulnerability in filters"
9
+
8
10
  def run_check
9
11
  if version_between?('3.0.0', '3.0.9') and uses_arbitrary_actions?
10
12
 
@@ -7,6 +7,8 @@ require 'brakeman/checks/base_check'
7
7
  class Brakeman::CheckForgerySetting < Brakeman::BaseCheck
8
8
  Brakeman::Checks.add self
9
9
 
10
+ @description = "Verifies that protect_from_forgery is enabled in ApplicationController"
11
+
10
12
  def run_check
11
13
  app_controller = tracker.controllers[:ApplicationController]
12
14
  if tracker.config[:rails][:action_controller] and
@@ -7,6 +7,8 @@ require 'brakeman/checks/check_cross_site_scripting'
7
7
  class Brakeman::CheckLinkTo < Brakeman::CheckCrossSiteScripting
8
8
  Brakeman::Checks.add self
9
9
 
10
+ @description = "Checks for XSS in link_to in versions before 3.0"
11
+
10
12
  def run_check
11
13
  return unless version_between?("2.0.0", "2.9.9") and not tracker.config[:escape_html]
12
14
 
@@ -7,6 +7,8 @@ require 'brakeman/checks/base_check'
7
7
  class Brakeman::CheckMailTo < Brakeman::BaseCheck
8
8
  Brakeman::Checks.add self
9
9
 
10
+ @description = "Checks for mail_to XSS vulnerability in certain versions"
11
+
10
12
  def run_check
11
13
  if (version_between? "2.3.0", "2.3.10" or version_between? "3.0.0", "3.0.3") and result = mail_to_javascript?
12
14
  message = "Vulnerability in mail_to using javascript encoding (CVE-2011-0446). Upgrade to Rails version "
@@ -7,12 +7,14 @@ require 'set'
7
7
  class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
8
8
  Brakeman::Checks.add self
9
9
 
10
+ @description = "Finds instances of mass assignment"
11
+
10
12
  def run_check
11
13
  return if mass_assign_disabled?
12
14
 
13
15
  models = []
14
16
  tracker.models.each do |name, m|
15
- if parent?(tracker, m, :"ActiveRecord::Base") and m[:attr_accessible].nil?
17
+ if parent?(m, :"ActiveRecord::Base") and m[:attr_accessible].nil?
16
18
  models << name
17
19
  end
18
20
  end
@@ -45,7 +47,13 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
45
47
  if check and not @results.include? call
46
48
  @results << call
47
49
 
48
- if include_user_input? call[3] and not hash? call[3][1]
50
+ model = tracker.models[res[:chain].first]
51
+
52
+ attr_protected = (model and model[:options][:attr_protected])
53
+
54
+ if attr_protected and tracker.options[:ignore_attr_protected]
55
+ return
56
+ elsif include_user_input? call[3] and not hash? call[3][1] and not attr_protected
49
57
  confidence = CONFIDENCE[:high]
50
58
  else
51
59
  confidence = CONFIDENCE[:low]
@@ -8,29 +8,63 @@ require 'brakeman/checks/base_check'
8
8
  class Brakeman::CheckModelAttributes < Brakeman::BaseCheck
9
9
  Brakeman::Checks.add self
10
10
 
11
+ @description = "Reports models which do not use attr_restricted and warns on models that use attr_protected"
12
+
11
13
  def run_check
12
14
  return if mass_assign_disabled?
13
15
 
14
- names = []
16
+ #Roll warnings into one warning for all models
17
+ if tracker.options[:collapse_mass_assignment]
18
+ no_accessible_names = []
19
+ protected_names = []
15
20
 
16
- tracker.models.each do |name, model|
17
- if model[:attr_accessible].nil? and parent? tracker, model, :"ActiveRecord::Base"
18
- if tracker.options[:collapse_mass_assignment]
19
- names << name.to_s
20
- else
21
- warn :model => name,
21
+ check_models do |name, model|
22
+ if model[:options][:attr_protected].nil?
23
+ no_accessible_names << name.to_s
24
+ elsif not tracker.options[:ignore_attr_protected]
25
+ protected_names << name.to_s
26
+ end
27
+ end
28
+
29
+ unless no_accessible_names.empty?
30
+ warn :model => no_accessible_names.sort.join(", "),
31
+ :warning_type => "Attribute Restriction",
32
+ :message => "Mass assignment is not restricted using attr_accessible",
33
+ :confidence => CONFIDENCE[:high]
34
+ end
35
+
36
+ unless protected_names.empty?
37
+ warn :model => protected_names.sort.join(", "),
38
+ :warning_type => "Attribute Restriction",
39
+ :message => "attr_accessible is recommended over attr_protected",
40
+ :confidence => CONFIDENCE[:low]
41
+ end
42
+ else #Output one warning per model
43
+
44
+ check_models do |name, model|
45
+ if model[:options][:attr_protected].nil?
46
+ warn :model => name,
47
+ :file => model[:file],
22
48
  :warning_type => "Attribute Restriction",
23
- :message => "Mass assignment is not restricted using attr_accessible",
49
+ :message => "Mass assignment is not restricted using attr_accessible",
24
50
  :confidence => CONFIDENCE[:high]
51
+ elsif not tracker.options[:ignore_attr_protected]
52
+ warn :model => name,
53
+ :file => model[:file],
54
+ :line => model[:options][:attr_protected].first.line,
55
+ :warning_type => "Attribute Restriction",
56
+ :message => "attr_accessible is recommended over attr_protected",
57
+ :confidence => CONFIDENCE[:low]
25
58
  end
26
59
  end
27
60
  end
61
+ end
28
62
 
29
- if tracker.options[:collapse_mass_assignment] and not names.empty?
30
- warn :model => names.sort.join(", "),
31
- :warning_type => "Attribute Restriction",
32
- :message => "Mass assignment is not restricted using attr_accessible",
33
- :confidence => CONFIDENCE[:high]
63
+ def check_models
64
+ tracker.models.each do |name, model|
65
+ if model[:attr_accessible].nil? and parent? model, :"ActiveRecord::Base"
66
+ yield name, model
67
+ end
34
68
  end
35
69
  end
36
70
  end
@@ -5,6 +5,8 @@ require 'brakeman/checks/base_check'
5
5
  class Brakeman::CheckNestedAttributes < Brakeman::BaseCheck
6
6
  Brakeman::Checks.add self
7
7
 
8
+ @description = "Checks for nested attributes vulnerability in Rails 2.3.9 and 3.0.0"
9
+
8
10
  def run_check
9
11
  version = tracker.config[:rails_version]
10
12
 
@@ -5,6 +5,8 @@ require 'brakeman/checks/base_check'
5
5
  class Brakeman::CheckQuoteTableName < Brakeman::BaseCheck
6
6
  Brakeman::Checks.add self
7
7
 
8
+ @description = "Checks for quote_table_name vulnerability in versions before 2.3.14 and 3.0.10"
9
+
8
10
  def run_check
9
11
  if (version_between?('2.0.0', '2.3.13') or
10
12
  version_between?('3.0.0', '3.0.9'))
@@ -8,6 +8,8 @@ require 'brakeman/checks/base_check'
8
8
  class Brakeman::CheckRedirect < Brakeman::BaseCheck
9
9
  Brakeman::Checks.add self
10
10
 
11
+ @description = "Looks for calls to redirect_to with user input as arguments"
12
+
11
13
  def run_check
12
14
  Brakeman.debug "Finding calls to redirect_to()"
13
15
 
@@ -4,6 +4,8 @@ require 'brakeman/checks/base_check'
4
4
  class Brakeman::CheckRender < Brakeman::BaseCheck
5
5
  Brakeman::Checks.add self
6
6
 
7
+ @description = "Finds calls to render that might allow file access"
8
+
7
9
  def run_check
8
10
  tracker.find_call(:target => nil, :method => :render).each do |result|
9
11
  process_render result
@@ -5,6 +5,8 @@ require 'brakeman/checks/base_check'
5
5
  class Brakeman::CheckResponseSplitting < Brakeman::BaseCheck
6
6
  Brakeman::Checks.add self
7
7
 
8
+ @description = "Report response splitting in Rails 2.3.0 - 2.3.13"
9
+
8
10
  def run_check
9
11
  if version_between?('2.3.0', '2.3.13')
10
12
 
@@ -5,6 +5,8 @@ require 'brakeman/processors/lib/processor_helper'
5
5
  class Brakeman::CheckSendFile < Brakeman::CheckFileAccess
6
6
  Brakeman::Checks.add self
7
7
 
8
+ @description = "Check for user input in uses of send_file"
9
+
8
10
  def run_check
9
11
  Brakeman.debug "Finding all calls to send_file()"
10
12
 
@@ -4,6 +4,8 @@ require 'brakeman/checks/base_check'
4
4
  class Brakeman::CheckSessionSettings < Brakeman::BaseCheck
5
5
  Brakeman::Checks.add self
6
6
 
7
+ @description = "Checks for session key length and http_only settings"
8
+
7
9
  def initialize *args
8
10
  super
9
11
 
@@ -11,6 +11,8 @@ require 'brakeman/checks/base_check'
11
11
  class Brakeman::CheckSQL < Brakeman::BaseCheck
12
12
  Brakeman::Checks.add self
13
13
 
14
+ @description = "Check for SQL injection"
15
+
14
16
  def run_check
15
17
  @rails_version = tracker.config[:rails_version]
16
18
 
@@ -49,7 +51,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
49
51
  if model[:options][:named_scope]
50
52
  model[:options][:named_scope].each do |args|
51
53
  call = Sexp.new(:call, nil, :named_scope, args).line(args.line)
52
- scope_calls << { :call => call, :location => [:class, name ] }
54
+ scope_calls << { :call => call, :location => [:class, name ], :method => :named_scope }
53
55
  end
54
56
  end
55
57
  end
@@ -57,8 +59,18 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
57
59
  tracker.models.each do |name, model|
58
60
  if model[:options][:scope]
59
61
  model[:options][:scope].each do |args|
60
- call = Sexp.new(:call, nil, :scope, args).line(args.line)
61
- scope_calls << { :call => call, :location => [:class, name ] }
62
+ second_arg = args[2]
63
+
64
+ if second_arg.node_type == :iter and
65
+ (second_arg[-1].node_type == :block or second_arg[-1].node_type == :call)
66
+ process_scope_with_block name, args
67
+ elsif second_arg.node_type == :call
68
+ call = second_arg
69
+ scope_calls << { :call => call, :location => [:class, name ], :method => call[2] }
70
+ else
71
+ call = Sexp.new(:call, nil, :scope, args).line(args.line)
72
+ scope_calls << { :call => call, :location => [:class, name ], :method => :scope }
73
+ end
62
74
  end
63
75
  end
64
76
  end
@@ -67,8 +79,32 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
67
79
  scope_calls
68
80
  end
69
81
 
82
+ def process_scope_with_block model_name, args
83
+ scope_name = args[1][1]
84
+ block = args[-1][-1]
85
+
86
+ #Search lambda for calls to query methods
87
+ if block.node_type == :block
88
+ find_calls = Brakeman::FindAllCalls.new tracker
89
+
90
+ find_calls.process_source block, model_name, scope_name
91
+
92
+ find_calls.calls.each do |call|
93
+ if call[:method].to_s =~ /^(find.*|first|last|all|where|order|group|having)$/
94
+ puts "Looks like #{call.inspect}"
95
+ process_result call
96
+ end
97
+ end
98
+ elsif block.node_type == :call
99
+ process_result :target => block[1], :method => block[2], :call => block, :location => [:class, model_name, scope_name]
100
+ end
101
+ end
102
+
70
103
  #Process result from Tracker#find_call.
71
104
  def process_result result
105
+ #TODO: I don't like this method at all. It's a pain to figure out what
106
+ #it is actually doing...
107
+
72
108
  call = result[:call]
73
109
 
74
110
  args = call[3]
@@ -77,6 +113,9 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
77
113
  failed = check_arguments args[1]
78
114
  elsif call[2].to_s =~ /^find/
79
115
  failed = (args.length > 2 and check_arguments args[-1])
116
+ elsif tracker.options[:rails3] and result[:method] != :scope
117
+ #This is for things like where("query = ?")
118
+ failed = check_arguments args[1]
80
119
  else
81
120
  failed = (args.length > 1 and check_arguments args[-1])
82
121
  end
@@ -5,6 +5,8 @@ require 'brakeman/checks/base_check'
5
5
  class Brakeman::CheckStripTags < Brakeman::BaseCheck
6
6
  Brakeman::Checks.add self
7
7
 
8
+ @description = "Report strip_tags vulnerability in versions before 2.3.13 and 3.0.10"
9
+
8
10
  def run_check
9
11
  if (version_between?('2.0.0', '2.3.12') or
10
12
  version_between?('3.0.0', '3.0.9')) and uses_strip_tags?
@@ -5,6 +5,8 @@ require 'brakeman/checks/base_check'
5
5
  class Brakeman::CheckTranslateBug < Brakeman::BaseCheck
6
6
  Brakeman::Checks.add self
7
7
 
8
+ @description = "Report XSS vulnerability in translate helper"
9
+
8
10
  def run_check
9
11
  if (version_between?('2.3.0', '2.3.99') and tracker.config[:escape_html]) or
10
12
  version_between?('3.0.0', '3.0.10') or
@@ -10,6 +10,8 @@ require 'brakeman/checks/base_check'
10
10
  class Brakeman::CheckValidationRegex < Brakeman::BaseCheck
11
11
  Brakeman::Checks.add self
12
12
 
13
+ @description = "Report uses of validates_format_of with improper anchors"
14
+
13
15
  WITH = Sexp.new(:lit, :with)
14
16
 
15
17
  def run_check
@@ -7,6 +7,8 @@ require 'brakeman/checks/base_check'
7
7
  class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
8
8
  Brakeman::Checks.add self
9
9
 
10
+ @description = "Check for mass assignment using without_protection"
11
+
10
12
  def run_check
11
13
  if version_between? "0.0.0", "3.0.99"
12
14
  return
@@ -14,7 +16,7 @@ class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
14
16
 
15
17
  models = []
16
18
  tracker.models.each do |name, m|
17
- if parent?(tracker, m, :"ActiveRecord::Base")
19
+ if parent? m, :"ActiveRecord::Base"
18
20
  models << name
19
21
  end
20
22
  end
@@ -60,6 +60,11 @@ p {
60
60
  background-color: white;
61
61
  }
62
62
 
63
+ table caption {
64
+ background-color: #FFE;
65
+ padding: 2px;
66
+ }
67
+
63
68
  table.context {
64
69
  margin-top: 5px;
65
70
  margin-bottom: 5px;
@@ -54,10 +54,6 @@ module Brakeman::Options
54
54
  options[:assume_all_routes] = true
55
55
  end
56
56
 
57
- opts.on "--ignore-model-output", "Consider model attributes XSS-safe" do
58
- options[:ignore_model_output] = true
59
- end
60
-
61
57
  opts.on "-e", "--escape-html", "Escape HTML by default" do
62
58
  options[:escape_html] = true
63
59
  end
@@ -67,6 +63,14 @@ module Brakeman::Options
67
63
  options[:skip_libs] = true
68
64
  end
69
65
 
66
+ opts.on "--ignore-model-output", "Consider model attributes XSS-safe" do
67
+ options[:ignore_model_output] = true
68
+ end
69
+
70
+ opts.on "--ignore-protected", "Consider models with attr_protected safe" do
71
+ options[:ignore_attr_protected] = true
72
+ end
73
+
70
74
  opts.on "--no-branching", "Disable flow sensitivity on conditionals" do
71
75
  options[:ignore_ifs] = true
72
76
  end
@@ -80,6 +84,11 @@ module Brakeman::Options
80
84
  options[:safe_methods].merge methods.map {|e| e.to_sym }
81
85
  end
82
86
 
87
+ opts.on "--skip-files file1,file2,etc", Array, "Skip processing of these files" do |files|
88
+ options[:skip_files] ||= Set.new
89
+ options[:skip_files].merge files.map {|f| f.to_sym }
90
+ end
91
+
83
92
  opts.on "--skip-libs", "Skip processing lib directory" do
84
93
  options[:skip_libs] = true
85
94
  end
@@ -47,7 +47,7 @@ module Brakeman
47
47
  #Process a model source
48
48
  def process_model src, file_name
49
49
  result = ModelProcessor.new(@tracker).process_model src, file_name
50
- AliasProcessor.new.process result
50
+ AliasProcessor.new(@tracker).process result
51
51
  end
52
52
 
53
53
  #Process either an ERB or HAML template
@@ -81,7 +81,7 @@ module Brakeman
81
81
  #Process source for initializing files
82
82
  def process_initializer name, src
83
83
  res = BaseProcessor.new(@tracker).process src
84
- res = AliasProcessor.new.process res
84
+ res = AliasProcessor.new(@tracker).process res
85
85
  @tracker.initializers[Pathname.new(name).basename.to_s] = res
86
86
  end
87
87
 
@@ -17,7 +17,7 @@ class Brakeman::AliasProcessor < SexpProcessor
17
17
  #The recommended usage is:
18
18
  #
19
19
  # AliasProcessor.new.process_safely src
20
- def initialize
20
+ def initialize tracker = nil
21
21
  super()
22
22
  self.strict = false
23
23
  self.auto_shift_type = false
@@ -27,7 +27,8 @@ class Brakeman::AliasProcessor < SexpProcessor
27
27
  @env = SexpProcessor::Environment.new
28
28
  @inside_if = false
29
29
  @ignore_ifs = false
30
- @tracker = nil #set in subclass as necessary
30
+ @exp_context = []
31
+ @tracker = tracker #set in subclass as necessary
31
32
  set_env_defaults
32
33
  end
33
34
 
@@ -71,9 +72,12 @@ class Brakeman::AliasProcessor < SexpProcessor
71
72
  #Process a Sexp. If the Sexp has a value associated with it in the
72
73
  #environment, that value will be returned.
73
74
  def process_default exp
75
+ @exp_context.push exp
76
+
74
77
  begin
75
- type = exp.shift
76
78
  exp.each_with_index do |e, i|
79
+ next if i == 0
80
+
77
81
  if sexp? e and not e.empty?
78
82
  exp[i] = process e
79
83
  else
@@ -82,18 +86,18 @@ class Brakeman::AliasProcessor < SexpProcessor
82
86
  end
83
87
  rescue Exception => err
84
88
  @tracker.error err if @tracker
85
- ensure
86
- #The type must be put back on, or else later processing
87
- #will trip up on it
88
- exp.unshift type
89
89
  end
90
90
 
91
91
  #Generic replace
92
- if replacement = env[exp]
93
- set_line replacement.deep_clone, exp.line
92
+ if replacement = env[exp] and not duplicate? replacement
93
+ result = set_line replacement.deep_clone, exp.line
94
94
  else
95
- exp
95
+ result = exp
96
96
  end
97
+
98
+ @exp_context.pop
99
+
100
+ result
97
101
  end
98
102
 
99
103
  #Process a method call.
@@ -102,7 +106,9 @@ class Brakeman::AliasProcessor < SexpProcessor
102
106
  exp = process_default exp
103
107
 
104
108
  #In case it is replaced with something else
105
- return exp unless call? exp
109
+ unless call? exp
110
+ return exp
111
+ end
106
112
 
107
113
  target = exp[1]
108
114
  method = exp[2]
@@ -116,12 +122,24 @@ class Brakeman::AliasProcessor < SexpProcessor
116
122
  joined = join_arrays target, args[1]
117
123
  joined.line(exp.line)
118
124
  exp = joined
119
- elsif string? target and string? args[1]
120
- joined = join_strings target, args[1]
121
- joined.line(exp.line)
122
- exp = joined
123
- elsif number? target and number? args[1]
124
- exp = Sexp.new(:lit, target[1] + args[1][1])
125
+ elsif string? args[1]
126
+ if string? target # "blah" + "blah"
127
+ joined = join_strings target, args[1]
128
+ joined.line(exp.line)
129
+ exp = joined
130
+ elsif call? target and target[2] == :+ and string? target[3][1]
131
+ joined = join_strings target[3][1], args[1]
132
+ joined.line(exp.line)
133
+ target[3][1] = joined
134
+ exp = target
135
+ end
136
+ elsif number? args[1]
137
+ if number? target
138
+ exp = Sexp.new(:lit, target[1] + args[1][1])
139
+ elsif target[2] == :+ and number? target[3][1]
140
+ target[3][1] = Sexp.new(:lit, target[3][1][1] + args[1][1])
141
+ exp = target
142
+ end
125
143
  end
126
144
  when :-
127
145
  if number? target and number? args[1]
@@ -211,10 +229,13 @@ class Brakeman::AliasProcessor < SexpProcessor
211
229
  # x = 1
212
230
  def process_lasgn exp
213
231
  exp[2] = process exp[2] if sexp? exp[2]
232
+ return exp if exp[2].nil?
233
+
214
234
  local = Sexp.new(:lvar, exp[1]).line(exp.line || -2)
215
235
 
216
236
  if @inside_if and val = env[local]
217
- if val != exp[2] #avoid setting to value it already is (e.g. "1 or 1")
237
+ #avoid setting to value it already is (e.g. "1 or 1")
238
+ if val != exp[2] and val[1] != exp[2] and val[2] != exp[2]
218
239
  env[local] = Sexp.new(:or, val, exp[2]).line(exp.line || -2)
219
240
  end
220
241
  else
@@ -283,6 +304,7 @@ class Brakeman::AliasProcessor < SexpProcessor
283
304
  tar_variable = exp[1]
284
305
  target = exp[1] = process(exp[1])
285
306
  method = exp[2]
307
+
286
308
  if method == :[]=
287
309
  index = exp[3][1] = process(exp[3][1])
288
310
  value = exp[3][2] = process(exp[3][2])
@@ -369,6 +391,10 @@ class Brakeman::AliasProcessor < SexpProcessor
369
391
  exp
370
392
  end
371
393
 
394
+ def process_svalue exp
395
+ exp[1]
396
+ end
397
+
372
398
  #Constant assignments like
373
399
  # BIG_CONSTANT = 234810983
374
400
  def process_cdecl exp
@@ -400,12 +426,18 @@ class Brakeman::AliasProcessor < SexpProcessor
400
426
  else
401
427
  exps = exp[2..-1]
402
428
  end
403
-
429
+
404
430
  was_inside = @inside_if
405
431
  @inside_if = !@ignore_ifs
406
432
 
407
433
  exps.each do |e|
408
- process e if sexp? e
434
+ if sexp? e
435
+ if e.node_type == :block
436
+ process_default e #avoid creating new scope
437
+ else
438
+ process e
439
+ end
440
+ end
409
441
  end
410
442
 
411
443
  @inside_if = was_inside
@@ -451,7 +483,11 @@ class Brakeman::AliasProcessor < SexpProcessor
451
483
  def join_strings string1, string2
452
484
  result = Sexp.new(:str)
453
485
  result[1] = string1[1] + string2[1]
454
- result
486
+ if result[1].length > 50
487
+ string1
488
+ else
489
+ result
490
+ end
455
491
  end
456
492
 
457
493
  #Returns a new SexpProcessor::Environment containing only instance variables.
@@ -487,4 +523,12 @@ class Brakeman::AliasProcessor < SexpProcessor
487
523
  exp
488
524
  end
489
525
  end
526
+
527
+ def duplicate? exp
528
+ @exp_context[0..-2].reverse_each do |e|
529
+ return true if exp == e
530
+ end
531
+
532
+ false
533
+ end
490
534
  end
@@ -84,18 +84,30 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
84
84
  #Basically, adds any instance variable assignments to the environment.
85
85
  #TODO: method arguments?
86
86
  def process_before_filter name
87
- method = find_method name, @current_class
87
+ filter = find_method name, @current_class
88
88
 
89
- if method.nil?
89
+ if filter.nil?
90
90
  Brakeman.debug "[Notice] Could not find filter #{name}"
91
91
  return
92
92
  end
93
93
 
94
- processor = Brakeman::AliasProcessor.new
95
- processor.process_safely(method[3])
94
+ method = filter[:method]
96
95
 
97
- processor.only_ivars.all.each do |variable, value|
98
- env[variable] = value
96
+ if ivars = @tracker.filter_cache[[filter[:controller], name]]
97
+ ivars.each do |variable, value|
98
+ env[variable] = value
99
+ end
100
+ else
101
+ processor = Brakeman::AliasProcessor.new @tracker
102
+ processor.process_safely(method[3])
103
+
104
+ ivars = processor.only_ivars.all
105
+
106
+ @tracker.filter_cache[[filter[:controller], name]] = ivars
107
+
108
+ ivars.each do |variable, value|
109
+ env[variable] = value
110
+ end
99
111
  end
100
112
  end
101
113
 
@@ -217,6 +229,10 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
217
229
  end
218
230
 
219
231
  #Finds a method in the given class or a parent class
232
+ #
233
+ #Returns nil if the method could not be found.
234
+ #
235
+ #If found, returns hash table with controller name and method sexp.
220
236
  def find_method method_name, klass
221
237
  return nil if sexp? method_name
222
238
  method_name = method_name.to_sym
@@ -231,12 +247,14 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
231
247
  if method.nil?
232
248
  controller[:includes].each do |included|
233
249
  method = find_method method_name, included
234
- return method if method
250
+ if method
251
+ return { :controller => controller[:name], :method => method }
252
+ end
235
253
  end
236
254
 
237
255
  find_method method_name, controller[:parent]
238
256
  else
239
- method
257
+ { :controller => controller[:name], :method => method }
240
258
  end
241
259
  else
242
260
  nil
@@ -120,7 +120,13 @@ class Brakeman::FindAllCalls < Brakeman::BaseProcessor
120
120
  when :lit
121
121
  exp[1]
122
122
  when :colon2
123
- class_name exp
123
+ begin
124
+ class_name exp
125
+ rescue StandardError
126
+ exp
127
+ end
128
+ when :self
129
+ @current_class || @current_module || nil
124
130
  else
125
131
  exp
126
132
  end
@@ -24,6 +24,8 @@ module Brakeman::ProcessorHelper
24
24
  "::#{exp[1]}".to_sym
25
25
  when :call
26
26
  process exp
27
+ when :self
28
+ @current_class || @current_module || nil
27
29
  else
28
30
  raise "Error: Cannot get class name from #{exp}"
29
31
  end
@@ -23,7 +23,7 @@ class Brakeman::Rails3ConfigProcessor < Brakeman::BaseProcessor
23
23
 
24
24
  #Use this method to process configuration file
25
25
  def process_config src
26
- res = Brakeman::AliasProcessor.new.process_safely(src)
26
+ res = Brakeman::AliasProcessor.new(@tracker).process_safely(src)
27
27
  process res
28
28
  end
29
29
 
@@ -7,7 +7,7 @@ class Brakeman::LibraryProcessor < Brakeman::BaseProcessor
7
7
  def initialize tracker
8
8
  super
9
9
  @file_name = nil
10
- @alias_processor = Brakeman::AliasProcessor.new
10
+ @alias_processor = Brakeman::AliasProcessor.new tracker
11
11
  end
12
12
 
13
13
  def process_library src, file_name = nil
@@ -206,7 +206,9 @@ class Brakeman::OutputProcessor < Ruby2Ruby
206
206
  exp.clear
207
207
  "(Unresolved Model)"
208
208
  else
209
- super exp
209
+ out = exp[0].to_s
210
+ exp.clear
211
+ out
210
212
  end
211
213
  end
212
214
 
@@ -528,7 +528,8 @@ class Brakeman::Report
528
528
  else
529
529
  CGI.escapeHTML(message)
530
530
  end <<
531
- "<table id='#{code_id}' class='context' style='display:none'>"
531
+ "<table id='#{code_id}' class='context' style='display:none'>" <<
532
+ "<caption>#{(warning.file || '').gsub(tracker.options[:app_path], "")}</caption>"
532
533
 
533
534
  unless context.empty?
534
535
  if warning.line - 1 == 1 or warning.line + 1 == 1
@@ -23,7 +23,7 @@ class Brakeman::Rescanner < Brakeman::Scanner
23
23
 
24
24
  tracker.run_checks if @changes
25
25
 
26
- Brakeman::RescanReport.new @old_results, tracker.checks
26
+ Brakeman::RescanReport.new @old_results, tracker
27
27
  end
28
28
 
29
29
  #Rescans changed files
@@ -206,9 +206,10 @@ end
206
206
  class Brakeman::RescanReport
207
207
  attr_reader :old_results, :new_results
208
208
 
209
- def initialize old_results, new_results
209
+ def initialize old_results, tracker
210
+ @tracker = tracker
210
211
  @old_results = old_results
211
- @new_results = new_results
212
+ @new_results = tracker.checks
212
213
  @all_warnings = nil
213
214
  @diff = nil
214
215
  end
@@ -253,11 +254,33 @@ class Brakeman::RescanReport
253
254
  end
254
255
 
255
256
  #Output total, fixed, and new warnings
256
- def to_s
257
+ def to_s(verbose = false)
258
+ if !verbose
257
259
  <<-OUTPUT
258
260
  Total warnings: #{all_warnings.length}
259
261
  Fixed warnings: #{fixed_warnings.length}
260
262
  New warnings: #{new_warnings.length}
261
263
  OUTPUT
264
+ else
265
+ existing_warnings = all_warnings - new_warnings
266
+
267
+ "".tap do |out|
268
+ {:fixed => fixed_warnings, :new => new_warnings, :existing => existing_warnings}.each do |warning_type, warnings|
269
+ if warnings.length > 0
270
+ out << "#{warning_type.to_s.titleize} warnings: #{warnings.length}\n"
271
+ table = Ruport::Data::Table(["Confidence", "Class", "Method", "Warning Type", "Message"])
272
+ warnings.sort_by{|w| w.confidence}.each do |warning|
273
+ next if warning.confidence > @tracker.options[:min_confidence]
274
+ w = warning.to_row
275
+ w["Confidence"] = Brakeman::Report::TEXT_CONFIDENCE[w["Confidence"]] if w["Confidence"].is_a?(Numeric)
276
+ table << warning.to_row
277
+ end
278
+ out << table.to_s
279
+ end
280
+
281
+ end
282
+ end
283
+
284
+ end
262
285
  end
263
286
  end
@@ -36,6 +36,13 @@ class Brakeman::Scanner
36
36
  @path = options[:app_path]
37
37
  @app_path = File.join(@path, "app")
38
38
  @processor = processor || Brakeman::Processor.new(options)
39
+ @skip_files = nil
40
+
41
+ #Convert files into Regexp for matching
42
+ if options[:skip_files]
43
+ list = "(?:" << options[:skip_files].map { |f| Regexp.escape f }.join("|") << ")$"
44
+ @skip_files = Regexp.new(list)
45
+ end
39
46
 
40
47
  if RUBY_1_9
41
48
  @ruby_parser = ::Ruby19Parser
@@ -123,7 +130,10 @@ class Brakeman::Scanner
123
130
  #
124
131
  #Adds parsed information to tracker.initializers
125
132
  def process_initializers
126
- Dir.glob(@path + "/config/initializers/**/*.rb").sort.each do |f|
133
+ initializer_files = Dir.glob(@path + "/config/initializers/**/*.rb").sort
134
+ initializer_files.reject! { |f| @skip_files.match f } if @skip_files
135
+
136
+ initializer_files.each do |f|
127
137
  process_initializer f
128
138
  end
129
139
  end
@@ -149,6 +159,8 @@ class Brakeman::Scanner
149
159
  end
150
160
 
151
161
  lib_files = Dir.glob(@path + "/lib/**/*.rb").sort
162
+ lib_files.reject! { |f| @skip_files.match f } if @skip_files
163
+
152
164
  total = lib_files.length
153
165
  current = 0
154
166
 
@@ -196,6 +208,8 @@ class Brakeman::Scanner
196
208
  #Adds processed controllers to tracker.controllers
197
209
  def process_controllers
198
210
  controller_files = Dir.glob(@app_path + "/controllers/**/*.rb").sort
211
+ controller_files.reject! { |f| @skip_files.match f } if @skip_files
212
+
199
213
  total = controller_files.length * 2
200
214
  current = 0
201
215
 
@@ -222,6 +236,9 @@ class Brakeman::Scanner
222
236
 
223
237
  @processor.process_controller_alias controller[:src]
224
238
  end
239
+
240
+ #No longer need these processed filter methods
241
+ tracker.filter_cache.clear
225
242
  end
226
243
 
227
244
  def process_controller path
@@ -244,6 +261,8 @@ class Brakeman::Scanner
244
261
  count = 0
245
262
 
246
263
  template_files = Dir.glob(views_path).sort
264
+ template_files.reject! { |f| @skip_files.match f } if @skip_files
265
+
247
266
  total = template_files.length
248
267
 
249
268
  template_files.each do |path|
@@ -327,6 +346,7 @@ class Brakeman::Scanner
327
346
  #Adds the processed models to tracker.models
328
347
  def process_models
329
348
  model_files = Dir.glob(@app_path + "/models/*.rb").sort
349
+ model_files.reject! { |f| @skip_files.match f } if @skip_files
330
350
 
331
351
  total = model_files.length
332
352
  current = 0
@@ -9,7 +9,7 @@ require 'brakeman/processors/lib/find_all_calls'
9
9
  class Brakeman::Tracker
10
10
  attr_accessor :controllers, :templates, :models, :errors,
11
11
  :checks, :initializers, :config, :routes, :processor, :libs,
12
- :template_cache, :options
12
+ :template_cache, :options, :filter_cache
13
13
 
14
14
  #Place holder when there should be a model, but it is not
15
15
  #clear what model it will be.
@@ -42,6 +42,7 @@ class Brakeman::Tracker
42
42
  @checks = nil
43
43
  @processed = nil
44
44
  @template_cache = Set.new
45
+ @filter_cache = {}
45
46
  @call_index = nil
46
47
  end
47
48
 
@@ -11,6 +11,8 @@ module Brakeman::Util
11
11
 
12
12
  REQUEST_PARAMETERS = Sexp.new(:call, Sexp.new(:call, nil, :request, Sexp.new(:arglist)), :request_parameters, Sexp.new(:arglist))
13
13
 
14
+ REQUEST_ENV = Sexp.new(:call, Sexp.new(:call, nil, :request, Sexp.new(:arglist)), :env, Sexp.new(:arglist))
15
+
14
16
  PARAMETERS = Sexp.new(:call, nil, :params, Sexp.new(:arglist))
15
17
 
16
18
  COOKIES = Sexp.new(:call, nil, :cookies, Sexp.new(:arglist))
@@ -179,7 +181,10 @@ module Brakeman::Util
179
181
  end
180
182
 
181
183
  false
184
+ end
182
185
 
186
+ def request_env? exp
187
+ call? exp and (exp == REQUEST_ENV or exp[1] == REQUEST_ENV)
183
188
  end
184
189
 
185
190
  #Check if _exp_ is a Sexp.
@@ -1,3 +1,3 @@
1
1
  module Brakeman
2
- Version = "1.2.2"
2
+ Version = "1.3.0"
3
3
  end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brakeman
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 27
5
+ prerelease:
5
6
  segments:
6
7
  - 1
7
- - 2
8
- - 2
9
- version: 1.2.2
8
+ - 3
9
+ - 0
10
+ version: 1.3.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Justin Collins
@@ -14,8 +15,7 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2012-01-26 00:00:00 -08:00
18
- default_executable:
18
+ date: 2012-02-09 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: activesupport
@@ -25,6 +25,7 @@ dependencies:
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
+ hash: 3
28
29
  segments:
29
30
  - 0
30
31
  version: "0"
@@ -38,6 +39,7 @@ dependencies:
38
39
  requirements:
39
40
  - - ">="
40
41
  - !ruby/object:Gem::Version
42
+ hash: 3
41
43
  segments:
42
44
  - 0
43
45
  version: "0"
@@ -51,6 +53,7 @@ dependencies:
51
53
  requirements:
52
54
  - - ~>
53
55
  - !ruby/object:Gem::Version
56
+ hash: 11
54
57
  segments:
55
58
  - 1
56
59
  - 2
@@ -65,6 +68,7 @@ dependencies:
65
68
  requirements:
66
69
  - - ~>
67
70
  - !ruby/object:Gem::Version
71
+ hash: 3
68
72
  segments:
69
73
  - 1
70
74
  - 6
@@ -79,6 +83,7 @@ dependencies:
79
83
  requirements:
80
84
  - - ~>
81
85
  - !ruby/object:Gem::Version
86
+ hash: 15
82
87
  segments:
83
88
  - 2
84
89
  - 6
@@ -93,6 +98,7 @@ dependencies:
93
98
  requirements:
94
99
  - - ~>
95
100
  - !ruby/object:Gem::Version
101
+ hash: 7
96
102
  segments:
97
103
  - 3
98
104
  - 0
@@ -107,6 +113,7 @@ dependencies:
107
113
  requirements:
108
114
  - - ~>
109
115
  - !ruby/object:Gem::Version
116
+ hash: 7
110
117
  segments:
111
118
  - 3
112
119
  - 0
@@ -195,7 +202,6 @@ files:
195
202
  - lib/brakeman/processor.rb
196
203
  - lib/brakeman.rb
197
204
  - lib/brakeman/format/style.css
198
- has_rdoc: true
199
205
  homepage: http://brakemanscanner.org
200
206
  licenses: []
201
207
 
@@ -209,6 +215,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
209
215
  requirements:
210
216
  - - ">="
211
217
  - !ruby/object:Gem::Version
218
+ hash: 3
212
219
  segments:
213
220
  - 0
214
221
  version: "0"
@@ -217,13 +224,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
217
224
  requirements:
218
225
  - - ">="
219
226
  - !ruby/object:Gem::Version
227
+ hash: 3
220
228
  segments:
221
229
  - 0
222
230
  version: "0"
223
231
  requirements: []
224
232
 
225
233
  rubyforge_project:
226
- rubygems_version: 1.3.7
234
+ rubygems_version: 1.8.15
227
235
  signing_key:
228
236
  specification_version: 3
229
237
  summary: Security vulnerability scanner for Ruby on Rails.