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