brakeman-lib 3.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +872 -0
  3. data/FEATURES +16 -0
  4. data/README.md +169 -0
  5. data/WARNING_TYPES +95 -0
  6. data/bin/brakeman +89 -0
  7. data/lib/brakeman.rb +495 -0
  8. data/lib/brakeman/app_tree.rb +161 -0
  9. data/lib/brakeman/brakeman.rake +17 -0
  10. data/lib/brakeman/call_index.rb +219 -0
  11. data/lib/brakeman/checks.rb +191 -0
  12. data/lib/brakeman/checks/base_check.rb +518 -0
  13. data/lib/brakeman/checks/check_basic_auth.rb +88 -0
  14. data/lib/brakeman/checks/check_basic_auth_timing_attack.rb +33 -0
  15. data/lib/brakeman/checks/check_content_tag.rb +160 -0
  16. data/lib/brakeman/checks/check_create_with.rb +75 -0
  17. data/lib/brakeman/checks/check_cross_site_scripting.rb +385 -0
  18. data/lib/brakeman/checks/check_default_routes.rb +86 -0
  19. data/lib/brakeman/checks/check_deserialize.rb +57 -0
  20. data/lib/brakeman/checks/check_detailed_exceptions.rb +55 -0
  21. data/lib/brakeman/checks/check_digest_dos.rb +38 -0
  22. data/lib/brakeman/checks/check_dynamic_finders.rb +49 -0
  23. data/lib/brakeman/checks/check_escape_function.rb +21 -0
  24. data/lib/brakeman/checks/check_evaluation.rb +36 -0
  25. data/lib/brakeman/checks/check_execute.rb +167 -0
  26. data/lib/brakeman/checks/check_file_access.rb +63 -0
  27. data/lib/brakeman/checks/check_file_disclosure.rb +35 -0
  28. data/lib/brakeman/checks/check_filter_skipping.rb +31 -0
  29. data/lib/brakeman/checks/check_forgery_setting.rb +74 -0
  30. data/lib/brakeman/checks/check_header_dos.rb +31 -0
  31. data/lib/brakeman/checks/check_i18n_xss.rb +48 -0
  32. data/lib/brakeman/checks/check_jruby_xml.rb +38 -0
  33. data/lib/brakeman/checks/check_json_encoding.rb +47 -0
  34. data/lib/brakeman/checks/check_json_parsing.rb +107 -0
  35. data/lib/brakeman/checks/check_link_to.rb +132 -0
  36. data/lib/brakeman/checks/check_link_to_href.rb +115 -0
  37. data/lib/brakeman/checks/check_mail_to.rb +49 -0
  38. data/lib/brakeman/checks/check_mass_assignment.rb +198 -0
  39. data/lib/brakeman/checks/check_mime_type_dos.rb +39 -0
  40. data/lib/brakeman/checks/check_model_attr_accessible.rb +55 -0
  41. data/lib/brakeman/checks/check_model_attributes.rb +119 -0
  42. data/lib/brakeman/checks/check_model_serialize.rb +67 -0
  43. data/lib/brakeman/checks/check_nested_attributes.rb +38 -0
  44. data/lib/brakeman/checks/check_nested_attributes_bypass.rb +58 -0
  45. data/lib/brakeman/checks/check_number_to_currency.rb +74 -0
  46. data/lib/brakeman/checks/check_quote_table_name.rb +40 -0
  47. data/lib/brakeman/checks/check_redirect.rb +215 -0
  48. data/lib/brakeman/checks/check_regex_dos.rb +69 -0
  49. data/lib/brakeman/checks/check_render.rb +92 -0
  50. data/lib/brakeman/checks/check_render_dos.rb +37 -0
  51. data/lib/brakeman/checks/check_render_inline.rb +54 -0
  52. data/lib/brakeman/checks/check_response_splitting.rb +21 -0
  53. data/lib/brakeman/checks/check_route_dos.rb +42 -0
  54. data/lib/brakeman/checks/check_safe_buffer_manipulation.rb +31 -0
  55. data/lib/brakeman/checks/check_sanitize_methods.rb +79 -0
  56. data/lib/brakeman/checks/check_secrets.rb +40 -0
  57. data/lib/brakeman/checks/check_select_tag.rb +60 -0
  58. data/lib/brakeman/checks/check_select_vulnerability.rb +60 -0
  59. data/lib/brakeman/checks/check_send.rb +48 -0
  60. data/lib/brakeman/checks/check_send_file.rb +19 -0
  61. data/lib/brakeman/checks/check_session_manipulation.rb +36 -0
  62. data/lib/brakeman/checks/check_session_settings.rb +170 -0
  63. data/lib/brakeman/checks/check_simple_format.rb +59 -0
  64. data/lib/brakeman/checks/check_single_quotes.rb +101 -0
  65. data/lib/brakeman/checks/check_skip_before_filter.rb +60 -0
  66. data/lib/brakeman/checks/check_sql.rb +660 -0
  67. data/lib/brakeman/checks/check_sql_cves.rb +101 -0
  68. data/lib/brakeman/checks/check_ssl_verify.rb +49 -0
  69. data/lib/brakeman/checks/check_strip_tags.rb +89 -0
  70. data/lib/brakeman/checks/check_symbol_dos.rb +64 -0
  71. data/lib/brakeman/checks/check_symbol_dos_cve.rb +30 -0
  72. data/lib/brakeman/checks/check_translate_bug.rb +45 -0
  73. data/lib/brakeman/checks/check_unsafe_reflection.rb +51 -0
  74. data/lib/brakeman/checks/check_unscoped_find.rb +41 -0
  75. data/lib/brakeman/checks/check_validation_regex.rb +116 -0
  76. data/lib/brakeman/checks/check_weak_hash.rb +151 -0
  77. data/lib/brakeman/checks/check_without_protection.rb +80 -0
  78. data/lib/brakeman/checks/check_xml_dos.rb +51 -0
  79. data/lib/brakeman/checks/check_yaml_parsing.rb +121 -0
  80. data/lib/brakeman/differ.rb +66 -0
  81. data/lib/brakeman/file_parser.rb +50 -0
  82. data/lib/brakeman/format/style.css +133 -0
  83. data/lib/brakeman/options.rb +301 -0
  84. data/lib/brakeman/parsers/rails2_erubis.rb +6 -0
  85. data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +48 -0
  86. data/lib/brakeman/parsers/rails3_erubis.rb +74 -0
  87. data/lib/brakeman/parsers/template_parser.rb +89 -0
  88. data/lib/brakeman/processor.rb +102 -0
  89. data/lib/brakeman/processors/alias_processor.rb +1013 -0
  90. data/lib/brakeman/processors/base_processor.rb +277 -0
  91. data/lib/brakeman/processors/config_processor.rb +14 -0
  92. data/lib/brakeman/processors/controller_alias_processor.rb +273 -0
  93. data/lib/brakeman/processors/controller_processor.rb +326 -0
  94. data/lib/brakeman/processors/erb_template_processor.rb +80 -0
  95. data/lib/brakeman/processors/erubis_template_processor.rb +104 -0
  96. data/lib/brakeman/processors/gem_processor.rb +57 -0
  97. data/lib/brakeman/processors/haml_template_processor.rb +190 -0
  98. data/lib/brakeman/processors/lib/basic_processor.rb +37 -0
  99. data/lib/brakeman/processors/lib/find_all_calls.rb +223 -0
  100. data/lib/brakeman/processors/lib/find_call.rb +183 -0
  101. data/lib/brakeman/processors/lib/find_return_value.rb +134 -0
  102. data/lib/brakeman/processors/lib/processor_helper.rb +75 -0
  103. data/lib/brakeman/processors/lib/rails2_config_processor.rb +145 -0
  104. data/lib/brakeman/processors/lib/rails2_route_processor.rb +313 -0
  105. data/lib/brakeman/processors/lib/rails3_config_processor.rb +132 -0
  106. data/lib/brakeman/processors/lib/rails3_route_processor.rb +308 -0
  107. data/lib/brakeman/processors/lib/render_helper.rb +181 -0
  108. data/lib/brakeman/processors/lib/render_path.rb +107 -0
  109. data/lib/brakeman/processors/lib/route_helper.rb +68 -0
  110. data/lib/brakeman/processors/lib/safe_call_helper.rb +16 -0
  111. data/lib/brakeman/processors/library_processor.rb +119 -0
  112. data/lib/brakeman/processors/model_processor.rb +191 -0
  113. data/lib/brakeman/processors/output_processor.rb +171 -0
  114. data/lib/brakeman/processors/route_processor.rb +17 -0
  115. data/lib/brakeman/processors/slim_template_processor.rb +107 -0
  116. data/lib/brakeman/processors/template_alias_processor.rb +116 -0
  117. data/lib/brakeman/processors/template_processor.rb +74 -0
  118. data/lib/brakeman/report.rb +78 -0
  119. data/lib/brakeman/report/config/remediation.yml +71 -0
  120. data/lib/brakeman/report/ignore/config.rb +135 -0
  121. data/lib/brakeman/report/ignore/interactive.rb +311 -0
  122. data/lib/brakeman/report/renderer.rb +24 -0
  123. data/lib/brakeman/report/report_base.rb +286 -0
  124. data/lib/brakeman/report/report_codeclimate.rb +70 -0
  125. data/lib/brakeman/report/report_csv.rb +55 -0
  126. data/lib/brakeman/report/report_hash.rb +23 -0
  127. data/lib/brakeman/report/report_html.rb +216 -0
  128. data/lib/brakeman/report/report_json.rb +42 -0
  129. data/lib/brakeman/report/report_markdown.rb +156 -0
  130. data/lib/brakeman/report/report_table.rb +107 -0
  131. data/lib/brakeman/report/report_tabs.rb +17 -0
  132. data/lib/brakeman/report/templates/controller_overview.html.erb +22 -0
  133. data/lib/brakeman/report/templates/controller_warnings.html.erb +21 -0
  134. data/lib/brakeman/report/templates/error_overview.html.erb +29 -0
  135. data/lib/brakeman/report/templates/header.html.erb +58 -0
  136. data/lib/brakeman/report/templates/ignored_warnings.html.erb +25 -0
  137. data/lib/brakeman/report/templates/model_warnings.html.erb +21 -0
  138. data/lib/brakeman/report/templates/overview.html.erb +38 -0
  139. data/lib/brakeman/report/templates/security_warnings.html.erb +23 -0
  140. data/lib/brakeman/report/templates/template_overview.html.erb +21 -0
  141. data/lib/brakeman/report/templates/view_warnings.html.erb +34 -0
  142. data/lib/brakeman/report/templates/warning_overview.html.erb +17 -0
  143. data/lib/brakeman/rescanner.rb +483 -0
  144. data/lib/brakeman/scanner.rb +317 -0
  145. data/lib/brakeman/tracker.rb +347 -0
  146. data/lib/brakeman/tracker/collection.rb +93 -0
  147. data/lib/brakeman/tracker/config.rb +101 -0
  148. data/lib/brakeman/tracker/constants.rb +101 -0
  149. data/lib/brakeman/tracker/controller.rb +161 -0
  150. data/lib/brakeman/tracker/library.rb +17 -0
  151. data/lib/brakeman/tracker/model.rb +90 -0
  152. data/lib/brakeman/tracker/template.rb +33 -0
  153. data/lib/brakeman/util.rb +481 -0
  154. data/lib/brakeman/version.rb +3 -0
  155. data/lib/brakeman/warning.rb +255 -0
  156. data/lib/brakeman/warning_codes.rb +111 -0
  157. data/lib/ruby_parser/bm_sexp.rb +610 -0
  158. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  159. metadata +362 -0
@@ -0,0 +1,161 @@
1
+ require 'pathname'
2
+
3
+ module Brakeman
4
+ class AppTree
5
+ VIEW_EXTENSIONS = %w[html.erb html.haml rhtml js.erb html.slim].join(",")
6
+
7
+ attr_reader :root
8
+
9
+ def self.from_options(options)
10
+ root = File.expand_path options[:app_path]
11
+
12
+ # Convert files into Regexp for matching
13
+ init_options = {}
14
+ if options[:skip_files]
15
+ init_options[:skip_files] = regex_for_paths(options[:skip_files])
16
+ end
17
+
18
+ if options[:only_files]
19
+ init_options[:only_files] = regex_for_paths(options[:only_files])
20
+ end
21
+ init_options[:additional_libs_path] = options[:additional_libs_path]
22
+ new(root, init_options)
23
+ end
24
+
25
+ # Accepts an array of filenames and paths with the following format and
26
+ # returns a Regexp to match them:
27
+ # * "path1/file1.rb" - Matches a specific filename in the project directory.
28
+ # * "path1/" - Matches any path that conatains "path1" in the project directory.
29
+ # * "/path1/ - Matches any path that is rooted at "path1" in the project directory.
30
+ #
31
+ def self.regex_for_paths(paths)
32
+ path_regexes = paths.map do |f|
33
+ # If path ends in a file separator then we assume it is a path rather
34
+ # than a filename.
35
+ if f.end_with?(File::SEPARATOR)
36
+ # If path starts with a file separator then we assume that they
37
+ # want the project relative path to start with this path prefix.
38
+ if f.start_with?(File::SEPARATOR)
39
+ "\\A#{Regexp.escape f}"
40
+ # If it ends in a file separator, but does not begin with a file
41
+ # separator then we assume the path can match any path component in
42
+ # the project.
43
+ else
44
+ Regexp.escape f
45
+ end
46
+ else
47
+ "#{Regexp.escape f}\\z"
48
+ end
49
+ end
50
+ Regexp.new("(?:" << path_regexes.join("|") << ")")
51
+ end
52
+ private_class_method(:regex_for_paths)
53
+
54
+ def initialize(root, init_options = {})
55
+ @root = root
56
+ @skip_files = init_options[:skip_files]
57
+ @only_files = init_options[:only_files]
58
+ @additional_libs_path = init_options[:additional_libs_path] || []
59
+ end
60
+
61
+ def expand_path(path)
62
+ File.expand_path(path, @root)
63
+ end
64
+
65
+ def read(path)
66
+ File.read(File.join(@root, path))
67
+ end
68
+
69
+ # This variation requires full paths instead of paths based
70
+ # off the project root. I'd prefer to get all the code outside
71
+ # of AppTree using project-root based paths (e.g. app/models/user.rb)
72
+ # instead of full paths, but I suspect it's an incompatible change.
73
+ def read_path(path)
74
+ File.read(path)
75
+ end
76
+
77
+ def exists?(path)
78
+ File.exist?(File.join(@root, path))
79
+ end
80
+
81
+ # This is a pair for #read_path. Again, would like to kill these
82
+ def path_exists?(path)
83
+ File.exist?(path)
84
+ end
85
+
86
+ def initializer_paths
87
+ @initializer_paths ||= find_paths("config/initializers")
88
+ end
89
+
90
+ def controller_paths
91
+ @controller_paths ||= find_paths("app/**/controllers")
92
+ end
93
+
94
+ def model_paths
95
+ @model_paths ||= find_paths("app/**/models")
96
+ end
97
+
98
+ def template_paths
99
+ @template_paths ||= find_paths("app/**/views", "*.{#{VIEW_EXTENSIONS}}")
100
+ end
101
+
102
+ def layout_exists?(name)
103
+ pattern = "#{@root}/{engines/*/,}app/views/layouts/#{name}.html.{erb,haml,slim}"
104
+ !Dir.glob(pattern).empty?
105
+ end
106
+
107
+ def lib_paths
108
+ @lib_files ||= find_paths("lib").reject { |path| path.include? "/generators/" or path.include? "lib/tasks/" } +
109
+ find_additional_lib_paths
110
+ end
111
+
112
+ private
113
+
114
+ def find_additional_lib_paths
115
+ @additional_libs_path.collect{ |path| find_paths path }.flatten
116
+ end
117
+
118
+ def find_paths(directory, extensions = "*.rb")
119
+ pattern = @root + "/{engines/*/,}#{directory}/**/#{extensions}"
120
+
121
+ select_files(Dir.glob(pattern).sort)
122
+ end
123
+
124
+ def select_files(paths)
125
+ paths = select_only_files(paths)
126
+ reject_skipped_files(paths)
127
+ end
128
+
129
+ def select_only_files(paths)
130
+ return paths unless @only_files
131
+ project_root = Pathname.new(@root)
132
+ paths.select do |path|
133
+ absolute_path = Pathname.new(path)
134
+ # relative root never has a leading separator. But, we use a leading
135
+ # separator in a @skip_files entry to imply that a directory is
136
+ # "absolute" with respect to the project directory.
137
+ project_relative_path = File.join(
138
+ File::SEPARATOR,
139
+ absolute_path.relative_path_from(project_root).to_s
140
+ )
141
+ @only_files.match(project_relative_path)
142
+ end
143
+ end
144
+
145
+ def reject_skipped_files(paths)
146
+ return paths unless @skip_files
147
+ project_root = Pathname.new(@root)
148
+ paths.reject do |path|
149
+ absolute_path = Pathname.new(path)
150
+ # relative root never has a leading separator. But, we use a leading
151
+ # separator in a @skip_files entry to imply that a directory is
152
+ # "absolute" with respect to the project directory.
153
+ project_relative_path = File.join(
154
+ File::SEPARATOR,
155
+ absolute_path.relative_path_from(project_root).to_s
156
+ )
157
+ @skip_files.match(project_relative_path)
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,17 @@
1
+ namespace :brakeman do
2
+
3
+ desc "Run Brakeman"
4
+ task :run, :output_files do |t, args|
5
+ require 'brakeman'
6
+
7
+ files = args[:output_files].split(' ') if args[:output_files]
8
+ Brakeman.run :app_path => ".", :output_files => files, :print_report => true
9
+ end
10
+
11
+ desc "Check your code with Brakeman"
12
+ task :check do
13
+ require 'brakeman'
14
+ result = Brakeman.run app_path: '.', print_report: true
15
+ exit Brakeman::Warnings_Found_Exit_Code unless result.filtered_warnings.empty?
16
+ end
17
+ end
@@ -0,0 +1,219 @@
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
+
11
+ index_calls calls
12
+ end
13
+
14
+ #Find calls matching specified option hash.
15
+ #
16
+ #Options:
17
+ #
18
+ # * :target - symbol, array of symbols, or regular expression to match target(s)
19
+ # * :method - symbol, array of symbols, or regular expression to match method(s)
20
+ # * :chained - boolean, whether or not to match against a whole method chain (false by default)
21
+ # * :nested - boolean, whether or not to match against a method call that is a target itself (false by default)
22
+ def find_calls options
23
+ target = options[:target] || options[:targets]
24
+ method = options[:method] || options[:methods]
25
+ nested = options[:nested]
26
+
27
+ if options[:chained]
28
+ return find_chain options
29
+ #Find by narrowest category
30
+ elsif target and method and target.is_a? Array and method.is_a? Array
31
+ if target.length > method.length
32
+ calls = filter_by_target calls_by_methods(method), target
33
+ else
34
+ calls = calls_by_targets(target)
35
+ calls = filter_by_method calls, method
36
+ end
37
+
38
+ #Find by target, then by methods, if provided
39
+ elsif target
40
+ calls = calls_by_target target
41
+
42
+ if calls and method
43
+ calls = filter_by_method calls, method
44
+ end
45
+
46
+ #Find calls with no explicit target
47
+ #with either :target => nil or :target => false
48
+ elsif (options.key? :target or options.key? :targets) and not target and method
49
+ calls = calls_by_method method
50
+ calls = filter_by_target calls, nil
51
+
52
+ #Find calls by method
53
+ elsif method
54
+ calls = calls_by_method method
55
+ else
56
+ raise "Invalid arguments to CallCache#find_calls: #{options.inspect}"
57
+ end
58
+
59
+ return [] if calls.nil?
60
+
61
+ #Remove calls that are actually targets of other calls
62
+ #Unless those are explicitly desired
63
+ calls = filter_nested calls unless nested
64
+
65
+ calls
66
+ end
67
+
68
+ def remove_template_indexes template_name = nil
69
+ [@calls_by_method, @calls_by_target].each do |calls_by|
70
+ calls_by.each do |name, calls|
71
+ calls.delete_if do |call|
72
+ from_template call, template_name
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def remove_indexes_by_class classes
79
+ [@calls_by_method, @calls_by_target].each do |calls_by|
80
+ calls_by.each do |name, calls|
81
+ calls.delete_if do |call|
82
+ call[:location][:type] == :class and classes.include? call[:location][:class]
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def index_calls calls
89
+ calls.each do |call|
90
+ @calls_by_method[call[:method]] << call
91
+
92
+ target = call[:target]
93
+
94
+ if not target.is_a? Sexp
95
+ @calls_by_target[target] << call
96
+ elsif target.node_type == :params or target.node_type == :session
97
+ @calls_by_target[target.node_type] << call
98
+ end
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def find_chain options
105
+ target = options[:target] || options[:targets]
106
+ method = options[:method] || options[:methods]
107
+
108
+ calls = calls_by_method method
109
+
110
+ return [] if calls.nil?
111
+
112
+ calls = filter_by_chain calls, target
113
+ end
114
+
115
+ def calls_by_target target
116
+ if target.is_a? Array
117
+ calls_by_targets target
118
+ else
119
+ @calls_by_target[target]
120
+ end
121
+ end
122
+
123
+ def calls_by_targets targets
124
+ calls = []
125
+
126
+ targets.each do |target|
127
+ calls.concat @calls_by_target[target] if @calls_by_target.key? target
128
+ end
129
+
130
+ calls
131
+ end
132
+
133
+ def calls_by_method method
134
+ if method.is_a? Array
135
+ calls_by_methods method
136
+ elsif method.is_a? Regexp
137
+ calls_by_methods_regex method
138
+ else
139
+ @calls_by_method[method.to_sym]
140
+ end
141
+ end
142
+
143
+ def calls_by_methods methods
144
+ methods = methods.map { |m| m.to_sym }
145
+ calls = []
146
+
147
+ methods.each do |method|
148
+ calls.concat @calls_by_method[method] if @calls_by_method.key? method
149
+ end
150
+
151
+ calls
152
+ end
153
+
154
+ def calls_by_methods_regex methods_regex
155
+ calls = []
156
+ @calls_by_method.each do |key, value|
157
+ calls.concat value if key.to_s.match methods_regex
158
+ end
159
+ calls
160
+ end
161
+
162
+ def calls_with_no_target
163
+ @calls_by_target[nil]
164
+ end
165
+
166
+ def filter calls, key, value
167
+ if value.is_a? Array
168
+ values = Set.new value
169
+
170
+ calls.select do |call|
171
+ values.include? call[key]
172
+ end
173
+ elsif value.is_a? Regexp
174
+ calls.select do |call|
175
+ call[key].to_s.match value
176
+ end
177
+ else
178
+ calls.select do |call|
179
+ call[key] == value
180
+ end
181
+ end
182
+ end
183
+
184
+ def filter_by_method calls, method
185
+ filter calls, :method, method
186
+ end
187
+
188
+ def filter_by_target calls, target
189
+ filter calls, :target, target
190
+ end
191
+
192
+ def filter_nested calls
193
+ filter calls, :nested, false
194
+ end
195
+
196
+ def filter_by_chain calls, target
197
+ if target.is_a? Array
198
+ targets = Set.new target
199
+
200
+ calls.select do |call|
201
+ targets.include? call[:chain].first
202
+ end
203
+ elsif target.is_a? Regexp
204
+ calls.select do |call|
205
+ call[:chain].first.to_s.match target
206
+ end
207
+ else
208
+ calls.select do |call|
209
+ call[:chain].first == target
210
+ end
211
+ end
212
+ end
213
+
214
+ def from_template call, template_name
215
+ return false unless call[:location][:type] == :template
216
+ return true if template_name.nil?
217
+ call[:location][:template] == template_name
218
+ end
219
+ end
@@ -0,0 +1,191 @@
1
+ require 'thread'
2
+ require 'brakeman/differ'
3
+
4
+ #Collects up results from running different checks.
5
+ #
6
+ #Checks can be added with +Check.add(check_class)+
7
+ #
8
+ #All .rb files in checks/ will be loaded.
9
+ class Brakeman::Checks
10
+ @checks = []
11
+ @optional_checks = []
12
+
13
+ attr_reader :warnings, :controller_warnings, :model_warnings, :template_warnings, :checks_run
14
+
15
+ #Add a check. This will call +_klass_.new+ when running tests
16
+ def self.add klass
17
+ @checks << klass unless @checks.include? klass
18
+ end
19
+
20
+ #Add an optional check
21
+ def self.add_optional klass
22
+ @optional_checks << klass unless @checks.include? klass
23
+ end
24
+
25
+ def self.checks
26
+ @checks + @optional_checks
27
+ end
28
+
29
+ def self.optional_checks
30
+ @optional_checks
31
+ end
32
+
33
+ def self.initialize_checks check_directory = ""
34
+ #Load all files in check_directory
35
+ Dir.glob(File.join(check_directory, "*.rb")).sort.each do |f|
36
+ require f
37
+ end
38
+ end
39
+
40
+ #No need to use this directly.
41
+ def initialize options = { }
42
+ if options[:min_confidence]
43
+ @min_confidence = options[:min_confidence]
44
+ else
45
+ @min_confidence = Brakeman.get_defaults[:min_confidence]
46
+ end
47
+
48
+ @warnings = []
49
+ @template_warnings = []
50
+ @model_warnings = []
51
+ @controller_warnings = []
52
+ @checks_run = []
53
+ end
54
+
55
+ #Add Warning to list of warnings to report.
56
+ #Warnings are split into four different arrays
57
+ #for template, controller, model, and generic warnings.
58
+ #
59
+ #Will not add warnings which are below the minimum confidence level.
60
+ def add_warning warning
61
+ unless warning.confidence > @min_confidence
62
+ case warning.warning_set
63
+ when :template
64
+ @template_warnings << warning
65
+ when :warning
66
+ @warnings << warning
67
+ when :controller
68
+ @controller_warnings << warning
69
+ when :model
70
+ @model_warnings << warning
71
+ else
72
+ raise "Unknown warning: #{warning.warning_set}"
73
+ end
74
+ end
75
+ end
76
+
77
+ #Return a hash of arrays of new and fixed warnings
78
+ #
79
+ # diff = checks.diff old_checks
80
+ # diff[:fixed] # [...]
81
+ # diff[:new] # [...]
82
+ def diff other_checks
83
+ my_warnings = self.all_warnings
84
+ other_warnings = other_checks.all_warnings
85
+ Brakeman::Differ.new(my_warnings, other_warnings).diff
86
+ end
87
+
88
+ #Return an array of all warnings found.
89
+ def all_warnings
90
+ @warnings + @template_warnings + @controller_warnings + @model_warnings
91
+ end
92
+
93
+ #Run all the checks on the given Tracker.
94
+ #Returns a new instance of Checks with the results.
95
+ def self.run_checks(app_tree, tracker)
96
+ checks = self.checks_to_run(tracker)
97
+ check_runner = self.new :min_confidence => tracker.options[:min_confidence]
98
+ self.actually_run_checks(checks, check_runner, app_tree, tracker)
99
+ end
100
+
101
+ def self.actually_run_checks(checks, check_runner, app_tree, tracker)
102
+ threads = [] # Results for parallel
103
+ results = [] # Results for sequential
104
+ parallel = tracker.options[:parallel_checks]
105
+ error_mutex = Mutex.new
106
+
107
+ checks.each do |c|
108
+ check_name = get_check_name c
109
+ Brakeman.notify " - #{check_name}"
110
+
111
+ if parallel
112
+ threads << Thread.new do
113
+ self.run_a_check(c, error_mutex, app_tree, tracker)
114
+ end
115
+ else
116
+ results << self.run_a_check(c, error_mutex, app_tree, tracker)
117
+ end
118
+
119
+ #Maintain list of which checks were run
120
+ #mainly for reporting purposes
121
+ check_runner.checks_run << check_name[5..-1]
122
+ end
123
+
124
+ threads.each { |t| t.join }
125
+
126
+ Brakeman.notify "Checks finished, collecting results..."
127
+
128
+ if parallel
129
+ threads.each do |thread|
130
+ thread.value.each do |warning|
131
+ check_runner.add_warning warning
132
+ end
133
+ end
134
+ else
135
+ results.each do |warnings|
136
+ warnings.each do |warning|
137
+ check_runner.add_warning warning
138
+ end
139
+ end
140
+ end
141
+
142
+ check_runner
143
+ end
144
+
145
+ private
146
+
147
+ def self.get_check_name check_class
148
+ check_class.to_s.split("::").last
149
+ end
150
+
151
+ def self.checks_to_run tracker
152
+ to_run = if tracker.options[:run_all_checks] or tracker.options[:run_checks]
153
+ @checks + @optional_checks
154
+ else
155
+ @checks
156
+ end
157
+
158
+ self.filter_checks to_run, tracker
159
+ end
160
+
161
+ def self.filter_checks checks, tracker
162
+ skipped = tracker.options[:skip_checks]
163
+ explicit = tracker.options[:run_checks]
164
+
165
+ checks.reject do |c|
166
+ check_name = self.get_check_name(c)
167
+
168
+ skipped.include? check_name or
169
+ (explicit and not explicit.include? check_name)
170
+ end
171
+ end
172
+
173
+ def self.run_a_check klass, mutex, app_tree, tracker
174
+ check = klass.new(app_tree, tracker)
175
+
176
+ begin
177
+ check.run_check
178
+ rescue => e
179
+ mutex.synchronize do
180
+ tracker.error e
181
+ end
182
+ end
183
+
184
+ check.warnings
185
+ end
186
+ end
187
+
188
+ #Load all files in checks/ directory
189
+ Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/checks/*.rb").sort.each do |f|
190
+ require f.match(/(brakeman\/checks\/.*)\.rb$/)[0]
191
+ end