brakeman-lib 3.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES +872 -0
- data/FEATURES +16 -0
- data/README.md +169 -0
- data/WARNING_TYPES +95 -0
- data/bin/brakeman +89 -0
- data/lib/brakeman.rb +495 -0
- data/lib/brakeman/app_tree.rb +161 -0
- data/lib/brakeman/brakeman.rake +17 -0
- data/lib/brakeman/call_index.rb +219 -0
- data/lib/brakeman/checks.rb +191 -0
- data/lib/brakeman/checks/base_check.rb +518 -0
- data/lib/brakeman/checks/check_basic_auth.rb +88 -0
- data/lib/brakeman/checks/check_basic_auth_timing_attack.rb +33 -0
- data/lib/brakeman/checks/check_content_tag.rb +160 -0
- data/lib/brakeman/checks/check_create_with.rb +75 -0
- data/lib/brakeman/checks/check_cross_site_scripting.rb +385 -0
- data/lib/brakeman/checks/check_default_routes.rb +86 -0
- data/lib/brakeman/checks/check_deserialize.rb +57 -0
- data/lib/brakeman/checks/check_detailed_exceptions.rb +55 -0
- data/lib/brakeman/checks/check_digest_dos.rb +38 -0
- data/lib/brakeman/checks/check_dynamic_finders.rb +49 -0
- data/lib/brakeman/checks/check_escape_function.rb +21 -0
- data/lib/brakeman/checks/check_evaluation.rb +36 -0
- data/lib/brakeman/checks/check_execute.rb +167 -0
- data/lib/brakeman/checks/check_file_access.rb +63 -0
- data/lib/brakeman/checks/check_file_disclosure.rb +35 -0
- data/lib/brakeman/checks/check_filter_skipping.rb +31 -0
- data/lib/brakeman/checks/check_forgery_setting.rb +74 -0
- data/lib/brakeman/checks/check_header_dos.rb +31 -0
- data/lib/brakeman/checks/check_i18n_xss.rb +48 -0
- data/lib/brakeman/checks/check_jruby_xml.rb +38 -0
- data/lib/brakeman/checks/check_json_encoding.rb +47 -0
- data/lib/brakeman/checks/check_json_parsing.rb +107 -0
- data/lib/brakeman/checks/check_link_to.rb +132 -0
- data/lib/brakeman/checks/check_link_to_href.rb +115 -0
- data/lib/brakeman/checks/check_mail_to.rb +49 -0
- data/lib/brakeman/checks/check_mass_assignment.rb +198 -0
- data/lib/brakeman/checks/check_mime_type_dos.rb +39 -0
- data/lib/brakeman/checks/check_model_attr_accessible.rb +55 -0
- data/lib/brakeman/checks/check_model_attributes.rb +119 -0
- data/lib/brakeman/checks/check_model_serialize.rb +67 -0
- data/lib/brakeman/checks/check_nested_attributes.rb +38 -0
- data/lib/brakeman/checks/check_nested_attributes_bypass.rb +58 -0
- data/lib/brakeman/checks/check_number_to_currency.rb +74 -0
- data/lib/brakeman/checks/check_quote_table_name.rb +40 -0
- data/lib/brakeman/checks/check_redirect.rb +215 -0
- data/lib/brakeman/checks/check_regex_dos.rb +69 -0
- data/lib/brakeman/checks/check_render.rb +92 -0
- data/lib/brakeman/checks/check_render_dos.rb +37 -0
- data/lib/brakeman/checks/check_render_inline.rb +54 -0
- data/lib/brakeman/checks/check_response_splitting.rb +21 -0
- data/lib/brakeman/checks/check_route_dos.rb +42 -0
- data/lib/brakeman/checks/check_safe_buffer_manipulation.rb +31 -0
- data/lib/brakeman/checks/check_sanitize_methods.rb +79 -0
- data/lib/brakeman/checks/check_secrets.rb +40 -0
- data/lib/brakeman/checks/check_select_tag.rb +60 -0
- data/lib/brakeman/checks/check_select_vulnerability.rb +60 -0
- data/lib/brakeman/checks/check_send.rb +48 -0
- data/lib/brakeman/checks/check_send_file.rb +19 -0
- data/lib/brakeman/checks/check_session_manipulation.rb +36 -0
- data/lib/brakeman/checks/check_session_settings.rb +170 -0
- data/lib/brakeman/checks/check_simple_format.rb +59 -0
- data/lib/brakeman/checks/check_single_quotes.rb +101 -0
- data/lib/brakeman/checks/check_skip_before_filter.rb +60 -0
- data/lib/brakeman/checks/check_sql.rb +660 -0
- data/lib/brakeman/checks/check_sql_cves.rb +101 -0
- data/lib/brakeman/checks/check_ssl_verify.rb +49 -0
- data/lib/brakeman/checks/check_strip_tags.rb +89 -0
- data/lib/brakeman/checks/check_symbol_dos.rb +64 -0
- data/lib/brakeman/checks/check_symbol_dos_cve.rb +30 -0
- data/lib/brakeman/checks/check_translate_bug.rb +45 -0
- data/lib/brakeman/checks/check_unsafe_reflection.rb +51 -0
- data/lib/brakeman/checks/check_unscoped_find.rb +41 -0
- data/lib/brakeman/checks/check_validation_regex.rb +116 -0
- data/lib/brakeman/checks/check_weak_hash.rb +151 -0
- data/lib/brakeman/checks/check_without_protection.rb +80 -0
- data/lib/brakeman/checks/check_xml_dos.rb +51 -0
- data/lib/brakeman/checks/check_yaml_parsing.rb +121 -0
- data/lib/brakeman/differ.rb +66 -0
- data/lib/brakeman/file_parser.rb +50 -0
- data/lib/brakeman/format/style.css +133 -0
- data/lib/brakeman/options.rb +301 -0
- data/lib/brakeman/parsers/rails2_erubis.rb +6 -0
- data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +48 -0
- data/lib/brakeman/parsers/rails3_erubis.rb +74 -0
- data/lib/brakeman/parsers/template_parser.rb +89 -0
- data/lib/brakeman/processor.rb +102 -0
- data/lib/brakeman/processors/alias_processor.rb +1013 -0
- data/lib/brakeman/processors/base_processor.rb +277 -0
- data/lib/brakeman/processors/config_processor.rb +14 -0
- data/lib/brakeman/processors/controller_alias_processor.rb +273 -0
- data/lib/brakeman/processors/controller_processor.rb +326 -0
- data/lib/brakeman/processors/erb_template_processor.rb +80 -0
- data/lib/brakeman/processors/erubis_template_processor.rb +104 -0
- data/lib/brakeman/processors/gem_processor.rb +57 -0
- data/lib/brakeman/processors/haml_template_processor.rb +190 -0
- data/lib/brakeman/processors/lib/basic_processor.rb +37 -0
- data/lib/brakeman/processors/lib/find_all_calls.rb +223 -0
- data/lib/brakeman/processors/lib/find_call.rb +183 -0
- data/lib/brakeman/processors/lib/find_return_value.rb +134 -0
- data/lib/brakeman/processors/lib/processor_helper.rb +75 -0
- data/lib/brakeman/processors/lib/rails2_config_processor.rb +145 -0
- data/lib/brakeman/processors/lib/rails2_route_processor.rb +313 -0
- data/lib/brakeman/processors/lib/rails3_config_processor.rb +132 -0
- data/lib/brakeman/processors/lib/rails3_route_processor.rb +308 -0
- data/lib/brakeman/processors/lib/render_helper.rb +181 -0
- data/lib/brakeman/processors/lib/render_path.rb +107 -0
- data/lib/brakeman/processors/lib/route_helper.rb +68 -0
- data/lib/brakeman/processors/lib/safe_call_helper.rb +16 -0
- data/lib/brakeman/processors/library_processor.rb +119 -0
- data/lib/brakeman/processors/model_processor.rb +191 -0
- data/lib/brakeman/processors/output_processor.rb +171 -0
- data/lib/brakeman/processors/route_processor.rb +17 -0
- data/lib/brakeman/processors/slim_template_processor.rb +107 -0
- data/lib/brakeman/processors/template_alias_processor.rb +116 -0
- data/lib/brakeman/processors/template_processor.rb +74 -0
- data/lib/brakeman/report.rb +78 -0
- data/lib/brakeman/report/config/remediation.yml +71 -0
- data/lib/brakeman/report/ignore/config.rb +135 -0
- data/lib/brakeman/report/ignore/interactive.rb +311 -0
- data/lib/brakeman/report/renderer.rb +24 -0
- data/lib/brakeman/report/report_base.rb +286 -0
- data/lib/brakeman/report/report_codeclimate.rb +70 -0
- data/lib/brakeman/report/report_csv.rb +55 -0
- data/lib/brakeman/report/report_hash.rb +23 -0
- data/lib/brakeman/report/report_html.rb +216 -0
- data/lib/brakeman/report/report_json.rb +42 -0
- data/lib/brakeman/report/report_markdown.rb +156 -0
- data/lib/brakeman/report/report_table.rb +107 -0
- data/lib/brakeman/report/report_tabs.rb +17 -0
- data/lib/brakeman/report/templates/controller_overview.html.erb +22 -0
- data/lib/brakeman/report/templates/controller_warnings.html.erb +21 -0
- data/lib/brakeman/report/templates/error_overview.html.erb +29 -0
- data/lib/brakeman/report/templates/header.html.erb +58 -0
- data/lib/brakeman/report/templates/ignored_warnings.html.erb +25 -0
- data/lib/brakeman/report/templates/model_warnings.html.erb +21 -0
- data/lib/brakeman/report/templates/overview.html.erb +38 -0
- data/lib/brakeman/report/templates/security_warnings.html.erb +23 -0
- data/lib/brakeman/report/templates/template_overview.html.erb +21 -0
- data/lib/brakeman/report/templates/view_warnings.html.erb +34 -0
- data/lib/brakeman/report/templates/warning_overview.html.erb +17 -0
- data/lib/brakeman/rescanner.rb +483 -0
- data/lib/brakeman/scanner.rb +317 -0
- data/lib/brakeman/tracker.rb +347 -0
- data/lib/brakeman/tracker/collection.rb +93 -0
- data/lib/brakeman/tracker/config.rb +101 -0
- data/lib/brakeman/tracker/constants.rb +101 -0
- data/lib/brakeman/tracker/controller.rb +161 -0
- data/lib/brakeman/tracker/library.rb +17 -0
- data/lib/brakeman/tracker/model.rb +90 -0
- data/lib/brakeman/tracker/template.rb +33 -0
- data/lib/brakeman/util.rb +481 -0
- data/lib/brakeman/version.rb +3 -0
- data/lib/brakeman/warning.rb +255 -0
- data/lib/brakeman/warning_codes.rb +111 -0
- data/lib/ruby_parser/bm_sexp.rb +610 -0
- data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
- 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
|