brakeman-lib 3.3.1

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 (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