brakeman 0.9.2 → 1.0.rc1

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 (70) hide show
  1. data/bin/brakeman +20 -5
  2. data/lib/brakeman.rb +54 -14
  3. data/lib/brakeman/call_index.rb +204 -0
  4. data/lib/{checks.rb → brakeman/checks.rb} +27 -12
  5. data/lib/{checks → brakeman/checks}/base_check.rb +36 -15
  6. data/lib/{checks → brakeman/checks}/check_basic_auth.rb +3 -3
  7. data/lib/{checks → brakeman/checks}/check_cross_site_scripting.rb +22 -19
  8. data/lib/{checks → brakeman/checks}/check_default_routes.rb +7 -5
  9. data/lib/{checks → brakeman/checks}/check_escape_function.rb +3 -4
  10. data/lib/{checks → brakeman/checks}/check_evaluation.rb +8 -6
  11. data/lib/{checks → brakeman/checks}/check_execute.rb +14 -14
  12. data/lib/brakeman/checks/check_file_access.rb +52 -0
  13. data/lib/{checks → brakeman/checks}/check_filter_skipping.rb +3 -4
  14. data/lib/{checks → brakeman/checks}/check_forgery_setting.rb +3 -3
  15. data/lib/{checks → brakeman/checks}/check_mail_to.rb +7 -6
  16. data/lib/{checks → brakeman/checks}/check_mass_assignment.rb +27 -7
  17. data/lib/{checks → brakeman/checks}/check_model_attributes.rb +7 -7
  18. data/lib/{checks → brakeman/checks}/check_nested_attributes.rb +3 -4
  19. data/lib/{checks → brakeman/checks}/check_quote_table_name.rb +6 -5
  20. data/lib/{checks → brakeman/checks}/check_redirect.rb +31 -15
  21. data/lib/{checks → brakeman/checks}/check_render.rb +5 -3
  22. data/lib/{checks → brakeman/checks}/check_response_splitting.rb +3 -4
  23. data/lib/brakeman/checks/check_send_file.rb +17 -0
  24. data/lib/{checks → brakeman/checks}/check_session_settings.rb +16 -12
  25. data/lib/{checks → brakeman/checks}/check_sql.rb +29 -17
  26. data/lib/{checks → brakeman/checks}/check_strip_tags.rb +6 -5
  27. data/lib/{checks → brakeman/checks}/check_translate_bug.rb +7 -6
  28. data/lib/{checks → brakeman/checks}/check_validation_regex.rb +3 -3
  29. data/lib/{checks → brakeman/checks}/check_without_protection.rb +8 -8
  30. data/lib/brakeman/processor.rb +93 -0
  31. data/lib/{processors → brakeman/processors}/alias_processor.rb +9 -7
  32. data/lib/{processors → brakeman/processors}/base_processor.rb +5 -5
  33. data/lib/brakeman/processors/config_processor.rb +14 -0
  34. data/lib/{processors → brakeman/processors}/controller_alias_processor.rb +8 -8
  35. data/lib/{processors → brakeman/processors}/controller_processor.rb +6 -6
  36. data/lib/{processors → brakeman/processors}/erb_template_processor.rb +2 -2
  37. data/lib/{processors → brakeman/processors}/erubis_template_processor.rb +2 -3
  38. data/lib/{processors → brakeman/processors}/gem_processor.rb +3 -2
  39. data/lib/{processors → brakeman/processors}/haml_template_processor.rb +3 -3
  40. data/lib/brakeman/processors/lib/find_all_calls.rb +105 -0
  41. data/lib/{processors → brakeman/processors}/lib/find_call.rb +4 -4
  42. data/lib/{processors → brakeman/processors}/lib/processor_helper.rb +1 -1
  43. data/lib/{processors → brakeman/processors}/lib/rails2_config_processor.rb +10 -13
  44. data/lib/{processors → brakeman/processors}/lib/rails2_route_processor.rb +6 -5
  45. data/lib/{processors → brakeman/processors}/lib/rails3_config_processor.rb +7 -10
  46. data/lib/{processors → brakeman/processors}/lib/rails3_route_processor.rb +13 -5
  47. data/lib/{processors → brakeman/processors}/lib/render_helper.rb +12 -4
  48. data/lib/{processors → brakeman/processors}/lib/route_helper.rb +1 -1
  49. data/lib/{processors → brakeman/processors}/library_processor.rb +4 -4
  50. data/lib/{processors → brakeman/processors}/model_processor.rb +3 -3
  51. data/lib/{processors → brakeman/processors}/output_processor.rb +5 -6
  52. data/lib/{processors → brakeman/processors}/params_processor.rb +1 -2
  53. data/lib/brakeman/processors/route_processor.rb +17 -0
  54. data/lib/{processors → brakeman/processors}/template_alias_processor.rb +5 -5
  55. data/lib/{processors → brakeman/processors}/template_processor.rb +2 -2
  56. data/lib/{report.rb → brakeman/report.rb} +54 -48
  57. data/lib/{scanner.rb → brakeman/scanner.rb} +26 -17
  58. data/lib/{tracker.rb → brakeman/tracker.rb} +47 -38
  59. data/lib/{util.rb → brakeman/util.rb} +16 -1
  60. data/lib/brakeman/version.rb +3 -0
  61. data/lib/{warning.rb → brakeman/warning.rb} +9 -9
  62. metadata +81 -69
  63. data/lib/checks/check_file_access.rb +0 -48
  64. data/lib/checks/check_send_file.rb +0 -15
  65. data/lib/format/style.css +0 -105
  66. data/lib/processor.rb +0 -91
  67. data/lib/processors/config_processor.rb +0 -5
  68. data/lib/processors/lib/find_model_call.rb +0 -43
  69. data/lib/processors/route_processor.rb +0 -11
  70. data/lib/version.rb +0 -1
@@ -4,14 +4,16 @@ require 'set'
4
4
 
5
5
  $:.unshift "#{File.expand_path(File.dirname(__FILE__))}/../lib"
6
6
 
7
- require 'version'
8
7
  require 'brakeman'
8
+ require 'brakeman/version'
9
9
 
10
10
  trap("INT") do
11
11
  $stderr.puts "\nInterrupted - exiting."
12
12
  exit!
13
13
  end
14
14
 
15
+ Brakeman::Warnings_Found_Exit_Code = 3
16
+
15
17
  #Parse command line options
16
18
  options = {}
17
19
 
@@ -28,10 +30,13 @@ OptionParser.new do |opts|
28
30
 
29
31
  opts.on "-q", "--quiet", "Suppress informational messages" do
30
32
  options[:quiet] = true
31
- $VERBOSE = nil
32
33
  end
33
34
 
34
- opts.on "-3", "--rails3", "[Experimental] Rails 3 support" do
35
+ opts.on( "-z", "--exit-on-warn", "Exit code is non-zero if warnings found") do |s|
36
+ options[:exit_on_warn] = s
37
+ end
38
+
39
+ opts.on "-3", "--rails3", "[Experimental] Force Rails 3 mode" do
35
40
  options[:rails3] = true
36
41
  end
37
42
 
@@ -101,7 +106,7 @@ OptionParser.new do |opts|
101
106
  options[:output_format] = ("to_" << type.to_s).to_sym
102
107
  end
103
108
 
104
- opts.on "--css-file CSSFile" do |file|
109
+ opts.on "--css-file CSSFile", "Specify CSS to use for HTML output" do |file|
105
110
  options[:html_style] = File.expand_path file
106
111
  end
107
112
 
@@ -154,10 +159,20 @@ OptionParser.new do |opts|
154
159
  options[:list_checks] = true
155
160
  end
156
161
 
162
+ opts.on "-v", "--version", "Show Brakeman version" do
163
+ puts "brakeman #{Brakeman::Version}"
164
+ exit
165
+ end
166
+
157
167
  opts.on_tail "-h", "--help", "Display this message" do
158
168
  puts opts
159
169
  exit
160
170
  end
161
171
  end.parse!(ARGV)
162
172
 
163
- Brakeman.run options
173
+ clean = Brakeman.run options
174
+
175
+ if options[:exit_on_warn] && !clean
176
+ exit Brakeman::Warnings_Found_Exit_Code
177
+ end
178
+
@@ -1,8 +1,38 @@
1
+ require 'rubygems'
1
2
  require 'yaml'
2
-
3
- OPTIONS = {}
3
+ require 'set'
4
4
 
5
5
  module Brakeman
6
+
7
+ #Run Brakeman scan. Returns Tracker object.
8
+ #
9
+ #Options:
10
+ #
11
+ # * :app_path - path to root of Rails app (required)
12
+ # * :assume_all_routes - assume all methods are routes (default: false)
13
+ # * :check_arguments - check arguments of methods (default: true)
14
+ # * :collapse_mass_assignment - report unprotected models in single warning (default: true)
15
+ # * :combine_locations - combine warning locations (default: true)
16
+ # * :config_file - configuration file
17
+ # * :create_config - output configuration file
18
+ # * :escape_html - escape HTML by default (automatic)
19
+ # * :exit_on_warn - return error exit code on warnings (default: false)
20
+ # * :html_style - path to CSS file
21
+ # * :ignore_model_output - consider models safe (default: false)
22
+ # * :list_checks - list all checks (does not run scan)
23
+ # * :message_limit - limit length of messages
24
+ # * :min_confidence - minimum confidence (0-2, 0 is highest)
25
+ # * :output_file - file for output
26
+ # * :output_format - format for output (:to_s, :to_tabs, :to_csv, :to_html)
27
+ # * :parallel_checks - run checks in parallel (default: true)
28
+ # * :quiet - suppress most messages (default: false)
29
+ # * :rails3 - force Rails 3 mode (automatic)
30
+ # * :report_routes - show found routes on controllers (default: false)
31
+ # * :run_checks - array of checks to run (run all if not specified)
32
+ # * :safe_methods - array of methods to consider safe
33
+ # * :skip_libs - do not process lib/ directory (default: false)
34
+ # * :skip_checks - checks not to run (run all if not specified)
35
+ #
6
36
  def self.run options
7
37
  if options[:list_checks]
8
38
  list_checks
@@ -14,6 +44,10 @@ module Brakeman
14
44
  exit
15
45
  end
16
46
 
47
+ if options[:quiet]
48
+ $VERBOSE = nil
49
+ end
50
+
17
51
  scan set_options(options)
18
52
  end
19
53
 
@@ -82,7 +116,7 @@ module Brakeman
82
116
  :ignore_model_output => false,
83
117
  :message_limit => 100,
84
118
  :parallel_checks => true,
85
- :html_style => "#{File.expand_path(File.dirname(__FILE__))}/../lib/format/style.css"
119
+ :html_style => "#{File.expand_path(File.dirname(__FILE__))}/brakeman/format/style.css"
86
120
  }
87
121
  end
88
122
 
@@ -118,7 +152,7 @@ module Brakeman
118
152
  end
119
153
 
120
154
  def self.list_checks
121
- require 'scanner'
155
+ require 'brakeman/scanner'
122
156
  $stderr.puts "Available Checks:"
123
157
  $stderr.puts "-" * 30
124
158
  $stderr.puts Checks.checks.map { |c| c.to_s }.sort.join "\n"
@@ -151,20 +185,17 @@ module Brakeman
151
185
  end
152
186
 
153
187
  def self.scan options
154
- OPTIONS.clear
155
- OPTIONS.merge! options
156
-
157
188
  #Load scanner
158
189
  warn "Loading scanner..."
159
190
 
160
191
  begin
161
- require 'scanner'
192
+ require 'brakeman/scanner'
162
193
  rescue LoadError
163
194
  abort "Cannot find lib/ directory."
164
195
  end
165
196
 
166
197
  #Start scanning
167
- scanner = Scanner.new options[:app_path]
198
+ scanner = Scanner.new options
168
199
 
169
200
  warn "[Notice] Using Ruby #{RUBY_VERSION}. Please make sure this matches the one used to run your Rails application."
170
201
 
@@ -175,13 +206,22 @@ module Brakeman
175
206
  tracker.run_checks
176
207
 
177
208
  warn "Generating report..."
178
- if OPTIONS[:output_file]
179
- File.open OPTIONS[:output_file], "w" do |f|
180
- f.puts tracker.report.send(OPTIONS[:output_format])
209
+ if options[:output_file]
210
+ File.open options[:output_file], "w" do |f|
211
+ f.puts tracker.report.send(options[:output_format])
181
212
  end
182
- warn "Report saved in '#{OPTIONS[:output_file]}'"
213
+ warn "Report saved in '#{options[:output_file]}'"
183
214
  else
184
- puts tracker.report.send(OPTIONS[:output_format])
215
+ puts tracker.report.send(options[:output_format])
185
216
  end
217
+
218
+ if options[:exit_on_warn]
219
+ tracker.checks.all_warnings.each do |warning|
220
+ next if warning.confidence > options[:min_confidence]
221
+ return false
222
+ end
223
+ end
224
+ return true
225
+
186
226
  end
187
227
  end
@@ -0,0 +1,204 @@
1
+ require 'set'
2
+
3
+ #Stores call sites to look up later.
4
+ class Brakeman::CallIndex
5
+
6
+ #Initialize index with calls from FindAllCalls
7
+ def initialize calls
8
+ @calls_by_method = Hash.new { |h,k| h[k] = [] }
9
+ @calls_by_target = Hash.new { |h,k| h[k] = [] }
10
+ @methods = Set.new
11
+ @targets = Set.new
12
+
13
+ index_calls calls
14
+ end
15
+
16
+ #Find calls matching specified option hash.
17
+ #
18
+ #Options:
19
+ #
20
+ # * :target - symbol, array of symbols, or regular expression to match target(s)
21
+ # * :method - symbol, array of symbols, or regular expression to match method(s)
22
+ # * :chained - boolean, whether or not to match against a whole method chain (false by default)
23
+ # * :nested - boolean, whether or not to match against a method call that is a target itself (false by default)
24
+ def find_calls options
25
+ target = options[:target] || options[:targets]
26
+ method = options[:method] || options[:methods]
27
+ nested = options[:nested]
28
+
29
+ if options[:chained]
30
+ return find_chain options
31
+ #Find by narrowest category
32
+ elsif target and method and target.is_a? Array and method.is_a? Array
33
+ if target.length > method.length
34
+ calls = filter_by_target calls_by_methods(method), target
35
+ else
36
+ calls = calls_by_targets(target)
37
+ calls = filter_by_method calls, method
38
+ end
39
+
40
+ #Find by target, then by methods, if provided
41
+ elsif target
42
+ calls = calls_by_target target
43
+
44
+ if calls and method
45
+ calls = filter_by_method calls, method
46
+ end
47
+
48
+ #Find calls with no explicit target
49
+ #with either :target => nil or :target => false
50
+ elsif options.key? :target and not target and method
51
+ calls = calls_by_method method
52
+ calls = filter_by_target calls, nil
53
+
54
+ #Find calls by method
55
+ elsif method
56
+ calls = calls_by_method method
57
+ else
58
+ warn "Invalid arguments to CallCache#find_calls: #{options.inspect}"
59
+ end
60
+
61
+ return [] if calls.nil?
62
+
63
+ #Remove calls that are actually targets of other calls
64
+ #Unless those are explicitly desired
65
+ calls = filter_nested calls unless nested
66
+
67
+ calls
68
+ end
69
+
70
+ private
71
+
72
+ def index_calls calls
73
+ calls.each do |call|
74
+ @methods << call[:method].to_s
75
+ @targets << call[:target].to_s
76
+ @calls_by_method[call[:method]] << call
77
+ @calls_by_target[call[:target]] << call
78
+ end
79
+ end
80
+
81
+ def find_chain options
82
+ target = options[:target] || options[:targets]
83
+ method = options[:method] || options[:methods]
84
+
85
+ calls = calls_by_method method
86
+
87
+ return [] if calls.nil?
88
+
89
+ calls = filter_by_chain calls, target
90
+ end
91
+
92
+ def calls_by_target target
93
+ if target.is_a? Array
94
+ calls_by_targets target
95
+ elsif target.is_a? Regexp
96
+ targets = @targets.select do |t|
97
+ t.match target
98
+ end
99
+
100
+ if targets.empty?
101
+ []
102
+ elsif targets.length > 1
103
+ calls_by_targets targets
104
+ else
105
+ calls_by_target[targets.first]
106
+ end
107
+ else
108
+ @calls_by_target[target]
109
+ end
110
+ end
111
+
112
+ def calls_by_targets targets
113
+ calls = []
114
+
115
+ targets.each do |target|
116
+ calls.concat @calls_by_target[target] if @calls_by_target.key? target
117
+ end
118
+
119
+ calls
120
+ end
121
+
122
+ def calls_by_method method
123
+ if method.is_a? Array
124
+ calls_by_methods method
125
+ elsif method.is_a? Regexp
126
+ methods = @methods.select do |m|
127
+ m.match method
128
+ end
129
+
130
+ if methods.empty?
131
+ []
132
+ elsif methods.length > 1
133
+ calls_by_methods methods
134
+ else
135
+ @calls_by_method[methods.first.to_sym]
136
+ end
137
+ else
138
+ @calls_by_method[method.to_sym]
139
+ end
140
+ end
141
+
142
+ def calls_by_methods methods
143
+ methods = methods.map { |m| m.to_sym }
144
+ calls = []
145
+
146
+ methods.each do |method|
147
+ calls.concat @calls_by_method[method] if @calls_by_method.key? method
148
+ end
149
+
150
+ calls
151
+ end
152
+
153
+ def calls_with_no_target
154
+ @calls_by_target[nil]
155
+ end
156
+
157
+ def filter calls, key, value
158
+ if value.is_a? Array
159
+ values = Set.new value
160
+
161
+ calls.select do |call|
162
+ values.include? call[key]
163
+ end
164
+ elsif value.is_a? Regexp
165
+ calls.select do |call|
166
+ call[key].to_s.match value
167
+ end
168
+ else
169
+ calls.select do |call|
170
+ call[key] == value
171
+ end
172
+ end
173
+ end
174
+
175
+ def filter_by_method calls, method
176
+ filter calls, :method, method
177
+ end
178
+
179
+ def filter_by_target calls, target
180
+ filter calls, :target, target
181
+ end
182
+
183
+ def filter_nested calls
184
+ filter calls, :nested, false
185
+ end
186
+
187
+ def filter_by_chain calls, target
188
+ if target.is_a? Array
189
+ targets = Set.new target
190
+
191
+ calls.select do |call|
192
+ targets.include? call[:chain].first
193
+ end
194
+ elsif target.is_a? Regexp
195
+ calls.select do |call|
196
+ call[:chain].first.to_s.match target
197
+ end
198
+ else
199
+ calls.select do |call|
200
+ call[:chain].first == target
201
+ end
202
+ end
203
+ end
204
+ end
@@ -5,7 +5,7 @@ require 'thread'
5
5
  #Checks can be added with +Check.add(check_class)+
6
6
  #
7
7
  #All .rb files in checks/ will be loaded.
8
- class Checks
8
+ class Brakeman::Checks
9
9
  @checks = []
10
10
 
11
11
  attr_reader :warnings, :controller_warnings, :model_warnings, :template_warnings, :checks_run
@@ -46,10 +46,15 @@ class Checks
46
46
  end
47
47
  end
48
48
 
49
+ #Return an array of all warnings found.
50
+ def all_warnings
51
+ @warnings + @template_warnings + @controller_warnings + @model_warnings
52
+ end
53
+
49
54
  #Run all the checks on the given Tracker.
50
55
  #Returns a new instance of Checks with the results.
51
56
  def self.run_checks tracker
52
- if OPTIONS[:parallel_checks]
57
+ if tracker.options[:parallel_checks]
53
58
  self.run_checks_parallel tracker
54
59
  else
55
60
  self.run_checks_sequential tracker
@@ -61,11 +66,13 @@ class Checks
61
66
  check_runner = self.new
62
67
 
63
68
  @checks.each do |c|
69
+ check_name = get_check_name c
70
+
64
71
  #Run or don't run check based on options
65
- unless OPTIONS[:skip_checks].include? c.to_s or
66
- (OPTIONS[:run_checks] and not OPTIONS[:run_checks].include? c.to_s)
72
+ unless tracker.options[:skip_checks].include? check_name or
73
+ (tracker.options[:run_checks] and not tracker.options[:run_checks].include? check_name)
67
74
 
68
- warn " - #{c}"
75
+ warn " - #{check_name}"
69
76
 
70
77
  check = c.new(tracker)
71
78
  check.run_check
@@ -76,7 +83,7 @@ class Checks
76
83
 
77
84
  #Maintain list of which checks were run
78
85
  #mainly for reporting purposes
79
- check_runner.checks_run << c.to_s[5..-1]
86
+ check_runner.checks_run << check_name[5..-1]
80
87
  end
81
88
  end
82
89
 
@@ -90,11 +97,13 @@ class Checks
90
97
  check_runner = self.new
91
98
 
92
99
  @checks.each do |c|
100
+ check_name = get_check_name c
101
+
93
102
  #Run or don't run check based on options
94
- unless OPTIONS[:skip_checks].include? c.to_s or
95
- (OPTIONS[:run_checks] and not OPTIONS[:run_checks].include? c.to_s)
103
+ unless tracker.options[:skip_checks].include? check_name or
104
+ (tracker.options[:run_checks] and not tracker.options[:run_checks].include? check_name)
96
105
 
97
- warn " - #{c}"
106
+ warn " - #{check_name}"
98
107
 
99
108
  threads << Thread.new do
100
109
  begin
@@ -102,14 +111,14 @@ class Checks
102
111
  check.run_check
103
112
  check.warnings
104
113
  rescue Exception => e
105
- warn "[#{c.to_s}] #{e}"
114
+ warn "[#{check_name}] #{e}"
106
115
  []
107
116
  end
108
117
  end
109
118
 
110
119
  #Maintain list of which checks were run
111
120
  #mainly for reporting purposes
112
- check_runner.checks_run << c.to_s[5..-1]
121
+ check_runner.checks_run << check_name[5..-1]
113
122
  end
114
123
  end
115
124
 
@@ -126,9 +135,15 @@ class Checks
126
135
 
127
136
  check_runner
128
137
  end
138
+
139
+ private
140
+
141
+ def self.get_check_name check_class
142
+ check_class.to_s.split("::").last
143
+ end
129
144
  end
130
145
 
131
146
  #Load all files in checks/ directory
132
147
  Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/checks/*.rb").sort.each do |f|
133
- require f.match(/(checks\/.*)\.rb$/)[0]
148
+ require f.match(/(brakeman\/checks\/.*)\.rb$/)[0]
134
149
  end