railroader 4.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES.md +1091 -0
- data/FEATURES +16 -0
- data/README.md +174 -0
- data/bin/railroader +8 -0
- data/lib/railroader/app_tree.rb +191 -0
- data/lib/railroader/call_index.rb +219 -0
- data/lib/railroader/checks/base_check.rb +505 -0
- data/lib/railroader/checks/check_basic_auth.rb +88 -0
- data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
- data/lib/railroader/checks/check_content_tag.rb +200 -0
- data/lib/railroader/checks/check_create_with.rb +74 -0
- data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
- data/lib/railroader/checks/check_default_routes.rb +86 -0
- data/lib/railroader/checks/check_deserialize.rb +56 -0
- data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
- data/lib/railroader/checks/check_digest_dos.rb +38 -0
- data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
- data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
- data/lib/railroader/checks/check_escape_function.rb +21 -0
- data/lib/railroader/checks/check_evaluation.rb +35 -0
- data/lib/railroader/checks/check_execute.rb +189 -0
- data/lib/railroader/checks/check_file_access.rb +71 -0
- data/lib/railroader/checks/check_file_disclosure.rb +35 -0
- data/lib/railroader/checks/check_filter_skipping.rb +31 -0
- data/lib/railroader/checks/check_forgery_setting.rb +81 -0
- data/lib/railroader/checks/check_header_dos.rb +31 -0
- data/lib/railroader/checks/check_i18n_xss.rb +48 -0
- data/lib/railroader/checks/check_jruby_xml.rb +36 -0
- data/lib/railroader/checks/check_json_encoding.rb +47 -0
- data/lib/railroader/checks/check_json_parsing.rb +107 -0
- data/lib/railroader/checks/check_link_to.rb +132 -0
- data/lib/railroader/checks/check_link_to_href.rb +146 -0
- data/lib/railroader/checks/check_mail_to.rb +49 -0
- data/lib/railroader/checks/check_mass_assignment.rb +196 -0
- data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
- data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
- data/lib/railroader/checks/check_model_attributes.rb +119 -0
- data/lib/railroader/checks/check_model_serialize.rb +67 -0
- data/lib/railroader/checks/check_nested_attributes.rb +38 -0
- data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
- data/lib/railroader/checks/check_number_to_currency.rb +74 -0
- data/lib/railroader/checks/check_permit_attributes.rb +43 -0
- data/lib/railroader/checks/check_quote_table_name.rb +40 -0
- data/lib/railroader/checks/check_redirect.rb +256 -0
- data/lib/railroader/checks/check_regex_dos.rb +68 -0
- data/lib/railroader/checks/check_render.rb +97 -0
- data/lib/railroader/checks/check_render_dos.rb +37 -0
- data/lib/railroader/checks/check_render_inline.rb +53 -0
- data/lib/railroader/checks/check_response_splitting.rb +21 -0
- data/lib/railroader/checks/check_route_dos.rb +42 -0
- data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
- data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
- data/lib/railroader/checks/check_secrets.rb +40 -0
- data/lib/railroader/checks/check_select_tag.rb +59 -0
- data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
- data/lib/railroader/checks/check_send.rb +47 -0
- data/lib/railroader/checks/check_send_file.rb +19 -0
- data/lib/railroader/checks/check_session_manipulation.rb +35 -0
- data/lib/railroader/checks/check_session_settings.rb +176 -0
- data/lib/railroader/checks/check_simple_format.rb +58 -0
- data/lib/railroader/checks/check_single_quotes.rb +101 -0
- data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
- data/lib/railroader/checks/check_sql.rb +700 -0
- data/lib/railroader/checks/check_sql_cves.rb +106 -0
- data/lib/railroader/checks/check_ssl_verify.rb +48 -0
- data/lib/railroader/checks/check_strip_tags.rb +89 -0
- data/lib/railroader/checks/check_symbol_dos.rb +71 -0
- data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
- data/lib/railroader/checks/check_translate_bug.rb +45 -0
- data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
- data/lib/railroader/checks/check_unscoped_find.rb +57 -0
- data/lib/railroader/checks/check_validation_regex.rb +116 -0
- data/lib/railroader/checks/check_weak_hash.rb +148 -0
- data/lib/railroader/checks/check_without_protection.rb +80 -0
- data/lib/railroader/checks/check_xml_dos.rb +45 -0
- data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
- data/lib/railroader/checks.rb +209 -0
- data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
- data/lib/railroader/commandline.rb +179 -0
- data/lib/railroader/differ.rb +66 -0
- data/lib/railroader/file_parser.rb +54 -0
- data/lib/railroader/format/style.css +133 -0
- data/lib/railroader/options.rb +339 -0
- data/lib/railroader/parsers/rails2_erubis.rb +6 -0
- data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
- data/lib/railroader/parsers/rails3_erubis.rb +81 -0
- data/lib/railroader/parsers/template_parser.rb +108 -0
- data/lib/railroader/processor.rb +102 -0
- data/lib/railroader/processors/alias_processor.rb +1229 -0
- data/lib/railroader/processors/base_processor.rb +295 -0
- data/lib/railroader/processors/config_processor.rb +14 -0
- data/lib/railroader/processors/controller_alias_processor.rb +278 -0
- data/lib/railroader/processors/controller_processor.rb +249 -0
- data/lib/railroader/processors/erb_template_processor.rb +77 -0
- data/lib/railroader/processors/erubis_template_processor.rb +92 -0
- data/lib/railroader/processors/gem_processor.rb +64 -0
- data/lib/railroader/processors/haml_template_processor.rb +191 -0
- data/lib/railroader/processors/lib/basic_processor.rb +37 -0
- data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
- data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
- data/lib/railroader/processors/lib/find_call.rb +183 -0
- data/lib/railroader/processors/lib/find_return_value.rb +166 -0
- data/lib/railroader/processors/lib/module_helper.rb +111 -0
- data/lib/railroader/processors/lib/processor_helper.rb +88 -0
- data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
- data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
- data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
- data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
- data/lib/railroader/processors/lib/render_helper.rb +181 -0
- data/lib/railroader/processors/lib/render_path.rb +107 -0
- data/lib/railroader/processors/lib/route_helper.rb +68 -0
- data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
- data/lib/railroader/processors/library_processor.rb +74 -0
- data/lib/railroader/processors/model_processor.rb +91 -0
- data/lib/railroader/processors/output_processor.rb +144 -0
- data/lib/railroader/processors/route_processor.rb +17 -0
- data/lib/railroader/processors/slim_template_processor.rb +111 -0
- data/lib/railroader/processors/template_alias_processor.rb +118 -0
- data/lib/railroader/processors/template_processor.rb +85 -0
- data/lib/railroader/report/config/remediation.yml +71 -0
- data/lib/railroader/report/ignore/config.rb +153 -0
- data/lib/railroader/report/ignore/interactive.rb +362 -0
- data/lib/railroader/report/pager.rb +112 -0
- data/lib/railroader/report/renderer.rb +24 -0
- data/lib/railroader/report/report_base.rb +292 -0
- data/lib/railroader/report/report_codeclimate.rb +79 -0
- data/lib/railroader/report/report_csv.rb +55 -0
- data/lib/railroader/report/report_hash.rb +23 -0
- data/lib/railroader/report/report_html.rb +216 -0
- data/lib/railroader/report/report_json.rb +45 -0
- data/lib/railroader/report/report_markdown.rb +107 -0
- data/lib/railroader/report/report_table.rb +117 -0
- data/lib/railroader/report/report_tabs.rb +17 -0
- data/lib/railroader/report/report_text.rb +198 -0
- data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
- data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
- data/lib/railroader/report/templates/error_overview.html.erb +29 -0
- data/lib/railroader/report/templates/header.html.erb +58 -0
- data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
- data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
- data/lib/railroader/report/templates/overview.html.erb +38 -0
- data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
- data/lib/railroader/report/templates/template_overview.html.erb +21 -0
- data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
- data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
- data/lib/railroader/report.rb +88 -0
- data/lib/railroader/rescanner.rb +483 -0
- data/lib/railroader/scanner.rb +321 -0
- data/lib/railroader/tracker/collection.rb +93 -0
- data/lib/railroader/tracker/config.rb +154 -0
- data/lib/railroader/tracker/constants.rb +171 -0
- data/lib/railroader/tracker/controller.rb +161 -0
- data/lib/railroader/tracker/library.rb +17 -0
- data/lib/railroader/tracker/model.rb +90 -0
- data/lib/railroader/tracker/template.rb +33 -0
- data/lib/railroader/tracker.rb +362 -0
- data/lib/railroader/util.rb +503 -0
- data/lib/railroader/version.rb +3 -0
- data/lib/railroader/warning.rb +294 -0
- data/lib/railroader/warning_codes.rb +117 -0
- data/lib/railroader.rb +544 -0
- data/lib/ruby_parser/bm_sexp.rb +626 -0
- data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
- metadata +386 -0
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'railroader/checks/base_check'
|
2
|
+
|
3
|
+
class Railroader::CheckWeakHash < Railroader::BaseCheck
|
4
|
+
Railroader::Checks.add_optional self
|
5
|
+
|
6
|
+
@description = "Checks for use of weak hashes like MD5"
|
7
|
+
|
8
|
+
DIGEST_CALLS = [:base64digest, :digest, :hexdigest, :new]
|
9
|
+
|
10
|
+
def run_check
|
11
|
+
tracker.find_call(:targets => [:'Digest::MD5', :'Digest::SHA1', :'OpenSSL::Digest::MD5', :'OpenSSL::Digest::SHA1'], :nested => true).each do |result|
|
12
|
+
process_hash_result result
|
13
|
+
end
|
14
|
+
|
15
|
+
tracker.find_call(:target => :'Digest::HMAC', :methods => [:new, :hexdigest], :nested => true).each do |result|
|
16
|
+
process_hmac_result result
|
17
|
+
end
|
18
|
+
|
19
|
+
tracker.find_call(:targets => [:'OpenSSL::Digest::Digest', :'OpenSSL::Digest'], :method => :new).each do |result|
|
20
|
+
process_openssl_result result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def process_hash_result result
|
25
|
+
return unless original? result
|
26
|
+
|
27
|
+
input = nil
|
28
|
+
call = result[:call]
|
29
|
+
|
30
|
+
if DIGEST_CALLS.include? call.method
|
31
|
+
if input = user_input_as_arg?(call)
|
32
|
+
confidence = :high
|
33
|
+
elsif input = hashing_password?(call)
|
34
|
+
confidence = :high
|
35
|
+
else
|
36
|
+
confidence = :medium
|
37
|
+
end
|
38
|
+
else
|
39
|
+
confidence = :medium
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
alg = case call.target.last
|
44
|
+
when :MD5
|
45
|
+
" (MD5)"
|
46
|
+
when :SHA1
|
47
|
+
" (SHA1)"
|
48
|
+
else
|
49
|
+
""
|
50
|
+
end
|
51
|
+
|
52
|
+
warn :result => result,
|
53
|
+
:warning_type => "Weak Hash",
|
54
|
+
:warning_code => :weak_hash_digest,
|
55
|
+
:message => "Weak hashing algorithm#{alg} used",
|
56
|
+
:confidence => confidence,
|
57
|
+
:user_input => input
|
58
|
+
end
|
59
|
+
|
60
|
+
def process_hmac_result result
|
61
|
+
return unless original? result
|
62
|
+
|
63
|
+
call = result[:call]
|
64
|
+
|
65
|
+
alg = case call.third_arg.last
|
66
|
+
when :MD5
|
67
|
+
'MD5'
|
68
|
+
when :SHA1
|
69
|
+
'SHA1'
|
70
|
+
else
|
71
|
+
return
|
72
|
+
end
|
73
|
+
|
74
|
+
warn :result => result,
|
75
|
+
:warning_type => "Weak Hash",
|
76
|
+
:warning_code => :weak_hash_hmac,
|
77
|
+
:message => "Weak hashing algorithm (#{alg}) used in HMAC",
|
78
|
+
:confidence => :medium
|
79
|
+
end
|
80
|
+
|
81
|
+
def process_openssl_result result
|
82
|
+
return unless original? result
|
83
|
+
|
84
|
+
arg = result[:call].first_arg
|
85
|
+
|
86
|
+
if string? arg
|
87
|
+
alg = arg.value.upcase
|
88
|
+
|
89
|
+
if alg == 'MD5' or alg == 'SHA1'
|
90
|
+
warn :result => result,
|
91
|
+
:warning_type => "Weak Hash",
|
92
|
+
:warning_code => :weak_hash_digest,
|
93
|
+
:message => "Weak hashing algorithm (#{alg}) used",
|
94
|
+
:confidence => :medium
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def user_input_as_arg? call
|
100
|
+
call.each_arg do |arg|
|
101
|
+
if input = include_user_input?(arg)
|
102
|
+
return input
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def hashing_password? call
|
110
|
+
call.each_arg do |arg|
|
111
|
+
@has_password = false
|
112
|
+
|
113
|
+
process arg
|
114
|
+
|
115
|
+
if @has_password
|
116
|
+
return @has_password
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
|
123
|
+
def process_call exp
|
124
|
+
if exp.method == :password
|
125
|
+
@has_password = exp
|
126
|
+
else
|
127
|
+
process_default exp
|
128
|
+
end
|
129
|
+
|
130
|
+
exp
|
131
|
+
end
|
132
|
+
|
133
|
+
def process_ivar exp
|
134
|
+
if exp.value == :@password
|
135
|
+
@has_password = exp
|
136
|
+
end
|
137
|
+
|
138
|
+
exp
|
139
|
+
end
|
140
|
+
|
141
|
+
def process_lvar exp
|
142
|
+
if exp.value == :password
|
143
|
+
@has_password = exp
|
144
|
+
end
|
145
|
+
|
146
|
+
exp
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'railroader/checks/base_check'
|
2
|
+
|
3
|
+
#Check for bypassing mass assignment protection
|
4
|
+
#with without_protection => true
|
5
|
+
#
|
6
|
+
#Only for Rails 3.1
|
7
|
+
class Railroader::CheckWithoutProtection < Railroader::BaseCheck
|
8
|
+
Railroader::Checks.add self
|
9
|
+
|
10
|
+
@description = "Check for mass assignment using without_protection"
|
11
|
+
|
12
|
+
def run_check
|
13
|
+
if version_between? "0.0.0", "3.0.99"
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
return if active_record_models.empty?
|
18
|
+
|
19
|
+
Railroader.debug "Finding all mass assignments"
|
20
|
+
calls = tracker.find_call :targets => active_record_models.keys, :methods => [:new,
|
21
|
+
:attributes=,
|
22
|
+
:update_attributes,
|
23
|
+
:update_attributes!,
|
24
|
+
:create,
|
25
|
+
:create!]
|
26
|
+
|
27
|
+
Railroader.debug "Processing all mass assignments"
|
28
|
+
calls.each do |result|
|
29
|
+
process_result result
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
#All results should be Model.new(...) or Model.attributes=() calls
|
34
|
+
def process_result res
|
35
|
+
call = res[:call]
|
36
|
+
last_arg = call.last_arg
|
37
|
+
|
38
|
+
if hash? last_arg and not call.original_line and not duplicate? res
|
39
|
+
|
40
|
+
if value = hash_access(last_arg, :without_protection)
|
41
|
+
if true? value
|
42
|
+
add_result res
|
43
|
+
|
44
|
+
if input = include_user_input?(call.arglist)
|
45
|
+
confidence = :high
|
46
|
+
elsif all_literals? call
|
47
|
+
return
|
48
|
+
else
|
49
|
+
confidence = :medium
|
50
|
+
end
|
51
|
+
|
52
|
+
warn :result => res,
|
53
|
+
:warning_type => "Mass Assignment",
|
54
|
+
:warning_code => :mass_assign_without_protection,
|
55
|
+
:message => "Unprotected mass assignment",
|
56
|
+
:code => call,
|
57
|
+
:user_input => input,
|
58
|
+
:confidence => confidence
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def all_literals? call
|
66
|
+
call.each_arg do |arg|
|
67
|
+
if hash? arg
|
68
|
+
hash_iterate arg do |k, v|
|
69
|
+
unless node_type? k, :str, :lit, :false, :true and node_type? v, :str, :lit, :false, :true
|
70
|
+
return false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
else
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
true
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'railroader/checks/base_check'
|
2
|
+
|
3
|
+
class Railroader::CheckXMLDoS < Railroader::BaseCheck
|
4
|
+
Railroader::Checks.add self
|
5
|
+
|
6
|
+
@description = "Checks for XML denial of service (CVE-2015-3227)"
|
7
|
+
|
8
|
+
def run_check
|
9
|
+
version = rails_version
|
10
|
+
|
11
|
+
fix_version = case
|
12
|
+
when version_between?("2.0.0", "3.2.21")
|
13
|
+
"3.2.22"
|
14
|
+
when version_between?("4.1.0", "4.1.10")
|
15
|
+
"4.1.11"
|
16
|
+
when version_between?("4.2.0", "4.2.1")
|
17
|
+
"4.2.2"
|
18
|
+
when version_between?("4.0.0", "4.0.99")
|
19
|
+
"4.2.2"
|
20
|
+
else
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
return if has_workaround?
|
25
|
+
|
26
|
+
message = "Rails #{version} is vulnerable to denial of service via XML parsing (CVE-2015-3227). Upgrade to Rails version #{fix_version}"
|
27
|
+
|
28
|
+
warn :warning_type => "Denial of Service",
|
29
|
+
:warning_code => :CVE_2015_3227,
|
30
|
+
:message => message,
|
31
|
+
:confidence => :medium,
|
32
|
+
:gem_info => gemfile_or_environment,
|
33
|
+
:link_path => "https://groups.google.com/d/msg/rubyonrails-security/bahr2JLnxvk/x4EocXnHPp8J"
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_workaround?
|
37
|
+
tracker.check_initializers(:"ActiveSupport::XmlMini", :backend=).any? do |match|
|
38
|
+
arg = match.call.first_arg
|
39
|
+
if string? arg
|
40
|
+
value = arg.value
|
41
|
+
value == 'Nokogiri' or value == 'LibXML'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'railroader/checks/base_check'
|
2
|
+
|
3
|
+
class Railroader::CheckYAMLParsing < Railroader::BaseCheck
|
4
|
+
Railroader::Checks.add self
|
5
|
+
|
6
|
+
@description = "Checks for YAML parsing vulnerabilities (CVE-2013-0156)"
|
7
|
+
|
8
|
+
def run_check
|
9
|
+
return unless version_between? "0.0.0", "2.3.14" or
|
10
|
+
version_between? "3.0.0", "3.0.18" or
|
11
|
+
version_between? "3.1.0", "3.1.9" or
|
12
|
+
version_between? "3.2.0", "3.2.10"
|
13
|
+
|
14
|
+
unless disabled_xml_parser? or disabled_xml_dangerous_types?
|
15
|
+
new_version = if version_between? "0.0.0", "2.3.14"
|
16
|
+
"2.3.15"
|
17
|
+
elsif version_between? "3.0.0", "3.0.18"
|
18
|
+
"3.0.19"
|
19
|
+
elsif version_between? "3.1.0", "3.1.9"
|
20
|
+
"3.1.10"
|
21
|
+
elsif version_between? "3.2.0", "3.2.10"
|
22
|
+
"3.2.11"
|
23
|
+
end
|
24
|
+
|
25
|
+
message = "Rails #{rails_version} has a remote code execution vulnerability: upgrade to #{new_version} or disable XML parsing"
|
26
|
+
|
27
|
+
warn :warning_type => "Remote Code Execution",
|
28
|
+
:warning_code => :CVE_2013_0156,
|
29
|
+
:message => message,
|
30
|
+
:confidence => :high,
|
31
|
+
:gem_info => gemfile_or_environment,
|
32
|
+
:link_path => "https://groups.google.com/d/topic/rubyonrails-security/61bkgvnSGTQ/discussion"
|
33
|
+
end
|
34
|
+
|
35
|
+
#Warn if app accepts YAML
|
36
|
+
if version_between?("0.0.0", "2.3.14") and enabled_yaml_parser?
|
37
|
+
message = "Parsing YAML request parameters enables remote code execution: disable YAML parser"
|
38
|
+
|
39
|
+
warn :warning_type => "Remote Code Execution",
|
40
|
+
:warning_code => :CVE_2013_0156,
|
41
|
+
:message => message,
|
42
|
+
:confidence => :high,
|
43
|
+
:gem_info => gemfile_or_environment,
|
44
|
+
:link_path => "https://groups.google.com/d/topic/rubyonrails-security/61bkgvnSGTQ/discussion"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def disabled_xml_parser?
|
49
|
+
if version_between? "0.0.0", "2.3.14"
|
50
|
+
#Look for ActionController::Base.param_parsers.delete(Mime::XML)
|
51
|
+
params_parser = s(:call,
|
52
|
+
s(:colon2, s(:const, :ActionController), :Base),
|
53
|
+
:param_parsers)
|
54
|
+
|
55
|
+
matches = tracker.check_initializers(params_parser, :delete)
|
56
|
+
else
|
57
|
+
#Look for ActionDispatch::ParamsParser::DEFAULT_PARSERS.delete(Mime::XML)
|
58
|
+
matches = tracker.check_initializers(:"ActionDispatch::ParamsParser::DEFAULT_PARSERS", :delete)
|
59
|
+
end
|
60
|
+
|
61
|
+
unless matches.empty?
|
62
|
+
mime_xml = s(:colon2, s(:const, :Mime), :XML)
|
63
|
+
|
64
|
+
matches.each do |result|
|
65
|
+
if result.call.first_arg == mime_xml
|
66
|
+
return true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
#Look for ActionController::Base.param_parsers[Mime::YAML] = :yaml
|
75
|
+
#in Rails 2.x apps
|
76
|
+
def enabled_yaml_parser?
|
77
|
+
param_parsers = s(:call,
|
78
|
+
s(:colon2, s(:const, :ActionController), :Base),
|
79
|
+
:param_parsers)
|
80
|
+
|
81
|
+
matches = tracker.check_initializers(param_parsers, :[]=)
|
82
|
+
|
83
|
+
mime_yaml = s(:colon2, s(:const, :Mime), :YAML)
|
84
|
+
|
85
|
+
matches.each do |result|
|
86
|
+
if result.call.first_arg == mime_yaml and
|
87
|
+
symbol? result.call.second_arg and
|
88
|
+
result.call.second_arg.value == :yaml
|
89
|
+
|
90
|
+
return true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
def disabled_xml_dangerous_types?
|
98
|
+
if version_between? "0.0.0", "2.3.14"
|
99
|
+
matches = tracker.check_initializers(:"ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING", :delete)
|
100
|
+
else
|
101
|
+
matches = tracker.check_initializers(:"ActiveSupport::XmlMini::PARSING", :delete)
|
102
|
+
end
|
103
|
+
|
104
|
+
symbols_off = false
|
105
|
+
yaml_off = false
|
106
|
+
|
107
|
+
matches.each do |result|
|
108
|
+
arg = result.call.first_arg
|
109
|
+
|
110
|
+
if string? arg
|
111
|
+
if arg.value == "yaml"
|
112
|
+
yaml_off = true
|
113
|
+
elsif arg.value == "symbol"
|
114
|
+
symbols_off = true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
symbols_off and yaml_off
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'railroader/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 Railroader::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
|
+
def self.missing_checks included_checks, excluded_checks
|
41
|
+
included_checks = included_checks.map(&:to_s).to_set
|
42
|
+
excluded_checks = excluded_checks.map(&:to_s).to_set
|
43
|
+
|
44
|
+
if included_checks == Set['CheckNone']
|
45
|
+
return []
|
46
|
+
else
|
47
|
+
loaded = self.checks.map { |name| name.to_s.gsub('Railroader::', '') }.to_set
|
48
|
+
missing = (included_checks - loaded) + (excluded_checks - loaded)
|
49
|
+
|
50
|
+
unless missing.empty?
|
51
|
+
return missing
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
[]
|
56
|
+
end
|
57
|
+
|
58
|
+
#No need to use this directly.
|
59
|
+
def initialize options = { }
|
60
|
+
if options[:min_confidence]
|
61
|
+
@min_confidence = options[:min_confidence]
|
62
|
+
else
|
63
|
+
@min_confidence = Railroader.get_defaults[:min_confidence]
|
64
|
+
end
|
65
|
+
|
66
|
+
@warnings = []
|
67
|
+
@template_warnings = []
|
68
|
+
@model_warnings = []
|
69
|
+
@controller_warnings = []
|
70
|
+
@checks_run = []
|
71
|
+
end
|
72
|
+
|
73
|
+
#Add Warning to list of warnings to report.
|
74
|
+
#Warnings are split into four different arrays
|
75
|
+
#for template, controller, model, and generic warnings.
|
76
|
+
#
|
77
|
+
#Will not add warnings which are below the minimum confidence level.
|
78
|
+
def add_warning warning
|
79
|
+
unless warning.confidence > @min_confidence
|
80
|
+
case warning.warning_set
|
81
|
+
when :template
|
82
|
+
@template_warnings << warning
|
83
|
+
when :warning
|
84
|
+
@warnings << warning
|
85
|
+
when :controller
|
86
|
+
@controller_warnings << warning
|
87
|
+
when :model
|
88
|
+
@model_warnings << warning
|
89
|
+
else
|
90
|
+
raise "Unknown warning: #{warning.warning_set}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
#Return a hash of arrays of new and fixed warnings
|
96
|
+
#
|
97
|
+
# diff = checks.diff old_checks
|
98
|
+
# diff[:fixed] # [...]
|
99
|
+
# diff[:new] # [...]
|
100
|
+
def diff other_checks
|
101
|
+
my_warnings = self.all_warnings
|
102
|
+
other_warnings = other_checks.all_warnings
|
103
|
+
Railroader::Differ.new(my_warnings, other_warnings).diff
|
104
|
+
end
|
105
|
+
|
106
|
+
#Return an array of all warnings found.
|
107
|
+
def all_warnings
|
108
|
+
@warnings + @template_warnings + @controller_warnings + @model_warnings
|
109
|
+
end
|
110
|
+
|
111
|
+
#Run all the checks on the given Tracker.
|
112
|
+
#Returns a new instance of Checks with the results.
|
113
|
+
def self.run_checks(app_tree, tracker)
|
114
|
+
checks = self.checks_to_run(tracker)
|
115
|
+
check_runner = self.new :min_confidence => tracker.options[:min_confidence]
|
116
|
+
self.actually_run_checks(checks, check_runner, app_tree, tracker)
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.actually_run_checks(checks, check_runner, app_tree, tracker)
|
120
|
+
threads = [] # Results for parallel
|
121
|
+
results = [] # Results for sequential
|
122
|
+
parallel = tracker.options[:parallel_checks]
|
123
|
+
error_mutex = Mutex.new
|
124
|
+
|
125
|
+
checks.each do |c|
|
126
|
+
check_name = get_check_name c
|
127
|
+
Railroader.notify " - #{check_name}"
|
128
|
+
|
129
|
+
if parallel
|
130
|
+
threads << Thread.new do
|
131
|
+
self.run_a_check(c, error_mutex, app_tree, tracker)
|
132
|
+
end
|
133
|
+
else
|
134
|
+
results << self.run_a_check(c, error_mutex, app_tree, tracker)
|
135
|
+
end
|
136
|
+
|
137
|
+
#Maintain list of which checks were run
|
138
|
+
#mainly for reporting purposes
|
139
|
+
check_runner.checks_run << check_name[5..-1]
|
140
|
+
end
|
141
|
+
|
142
|
+
threads.each { |t| t.join }
|
143
|
+
|
144
|
+
Railroader.notify "Checks finished, collecting results..."
|
145
|
+
|
146
|
+
if parallel
|
147
|
+
threads.each do |thread|
|
148
|
+
thread.value.each do |warning|
|
149
|
+
check_runner.add_warning warning
|
150
|
+
end
|
151
|
+
end
|
152
|
+
else
|
153
|
+
results.each do |warnings|
|
154
|
+
warnings.each do |warning|
|
155
|
+
check_runner.add_warning warning
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
check_runner
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
def self.get_check_name check_class
|
166
|
+
check_class.to_s.split("::").last
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.checks_to_run tracker
|
170
|
+
to_run = if tracker.options[:run_all_checks] or tracker.options[:run_checks]
|
171
|
+
@checks + @optional_checks
|
172
|
+
else
|
173
|
+
@checks
|
174
|
+
end
|
175
|
+
|
176
|
+
self.filter_checks to_run, tracker
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.filter_checks checks, tracker
|
180
|
+
skipped = tracker.options[:skip_checks]
|
181
|
+
explicit = tracker.options[:run_checks]
|
182
|
+
|
183
|
+
checks.reject do |c|
|
184
|
+
check_name = self.get_check_name(c)
|
185
|
+
|
186
|
+
skipped.include? check_name or
|
187
|
+
(explicit and not explicit.include? check_name)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.run_a_check klass, mutex, app_tree, tracker
|
192
|
+
check = klass.new(app_tree, tracker)
|
193
|
+
|
194
|
+
begin
|
195
|
+
check.run_check
|
196
|
+
rescue => e
|
197
|
+
mutex.synchronize do
|
198
|
+
tracker.error e
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
check.warnings
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
#Load all files in checks/ directory
|
207
|
+
Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/checks/*.rb").sort.each do |f|
|
208
|
+
require f.match(/(railroader\/checks\/.*)\.rb$/)[0]
|
209
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Railroader
|
4
|
+
module Codeclimate
|
5
|
+
class EngineConfiguration
|
6
|
+
|
7
|
+
def initialize(engine_config = {})
|
8
|
+
@engine_config = engine_config
|
9
|
+
end
|
10
|
+
|
11
|
+
def options
|
12
|
+
default_options.merge(configured_options)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :engine_config
|
18
|
+
|
19
|
+
def default_options
|
20
|
+
@default_options = {
|
21
|
+
:output_format => :codeclimate,
|
22
|
+
:quiet => true,
|
23
|
+
:pager => false,
|
24
|
+
:app_path => Dir.pwd
|
25
|
+
}
|
26
|
+
if system("test -w /dev/stdout")
|
27
|
+
@default_options[:output_files] = ["/dev/stdout"]
|
28
|
+
end
|
29
|
+
@default_options
|
30
|
+
end
|
31
|
+
|
32
|
+
def configured_options
|
33
|
+
@configured_options = {}
|
34
|
+
# ATM this gets parsed as a string instead of bool: "config":{ "debug":"true" }
|
35
|
+
if railroader_configuration["debug"] && railroader_configuration["debug"].to_s.downcase == "true"
|
36
|
+
@configured_options[:debug] = true
|
37
|
+
@configured_options[:report_progress] = false
|
38
|
+
end
|
39
|
+
|
40
|
+
if active_include_paths
|
41
|
+
@configured_options[:only_files] = active_include_paths
|
42
|
+
end
|
43
|
+
|
44
|
+
if railroader_configuration["app_path"]
|
45
|
+
@configured_options[:path_prefix] = railroader_configuration["app_path"]
|
46
|
+
path = Pathname.new(Dir.pwd) + railroader_configuration["app_path"]
|
47
|
+
@configured_options[:app_path] = path.to_s
|
48
|
+
end
|
49
|
+
@configured_options
|
50
|
+
end
|
51
|
+
|
52
|
+
def railroader_configuration
|
53
|
+
if engine_config["config"]
|
54
|
+
engine_config["config"]
|
55
|
+
else
|
56
|
+
{}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def active_include_paths
|
61
|
+
@active_include_paths ||=
|
62
|
+
if railroader_configuration["app_path"]
|
63
|
+
stripped_include_paths(railroader_configuration["app_path"])
|
64
|
+
else
|
65
|
+
engine_config["include_paths"] && engine_config["include_paths"].compact
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def stripped_include_paths(prefix)
|
70
|
+
subprefixes = path_subprefixes(prefix)
|
71
|
+
engine_config["include_paths"] && engine_config["include_paths"].map do |path|
|
72
|
+
next unless path
|
73
|
+
stripped_include_path(prefix, subprefixes, path)
|
74
|
+
end.compact
|
75
|
+
end
|
76
|
+
|
77
|
+
def path_subprefixes(path)
|
78
|
+
Pathname.new(path).each_filename.inject([]) do |memo, piece|
|
79
|
+
memo <<
|
80
|
+
if memo.any?
|
81
|
+
File.join(memo.last, piece)
|
82
|
+
else
|
83
|
+
File.join(piece)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def stripped_include_path(prefix, subprefixes, path)
|
89
|
+
if path.start_with?(prefix)
|
90
|
+
path.sub(%r{^#{prefix}/?}, "/")
|
91
|
+
elsif subprefixes.any? { |subprefix| path =~ %r{^#{subprefix}/?$} }
|
92
|
+
"/"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|