railroader 4.3.4
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.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,216 @@
|
|
|
1
|
+
require 'cgi'
|
|
2
|
+
|
|
3
|
+
class Railroader::Report::HTML < Railroader::Report::Base
|
|
4
|
+
HTML_CONFIDENCE = [ "<span class='high-confidence'>High</span>",
|
|
5
|
+
"<span class='med-confidence'>Medium</span>",
|
|
6
|
+
"<span class='weak-confidence'>Weak</span>" ]
|
|
7
|
+
|
|
8
|
+
def initialize *args
|
|
9
|
+
super
|
|
10
|
+
|
|
11
|
+
@element_id = 0 #Used for HTML ids
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def generate_report
|
|
15
|
+
out = html_header <<
|
|
16
|
+
generate_overview <<
|
|
17
|
+
generate_warning_overview.to_s
|
|
18
|
+
|
|
19
|
+
# Return early if only summarizing
|
|
20
|
+
return out if tracker.options[:summary_only]
|
|
21
|
+
|
|
22
|
+
out << generate_controllers.to_s if tracker.options[:report_routes] or tracker.options[:debug]
|
|
23
|
+
out << generate_templates.to_s if tracker.options[:debug]
|
|
24
|
+
out << generate_errors.to_s
|
|
25
|
+
out << generate_warnings.to_s
|
|
26
|
+
out << generate_controller_warnings.to_s
|
|
27
|
+
out << generate_model_warnings.to_s
|
|
28
|
+
out << generate_template_warnings.to_s
|
|
29
|
+
out << generate_ignored_warnings.to_s
|
|
30
|
+
out << "</body></html>"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def generate_overview
|
|
34
|
+
locals = {
|
|
35
|
+
:tracker => tracker,
|
|
36
|
+
:warnings => all_warnings.length,
|
|
37
|
+
:warnings_summary => warnings_summary,
|
|
38
|
+
:number_of_templates => number_of_templates(@tracker),
|
|
39
|
+
:ignored_warnings => ignored_warnings.length
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Railroader::Report::Renderer.new('overview', :locals => locals).render
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#Generate listings of templates and their output
|
|
46
|
+
def generate_templates
|
|
47
|
+
out_processor = Railroader::OutputProcessor.new
|
|
48
|
+
template_rows = {}
|
|
49
|
+
tracker.templates.each do |name, template|
|
|
50
|
+
template.each_output do |out|
|
|
51
|
+
out = CGI.escapeHTML(out_processor.format(out))
|
|
52
|
+
template_rows[name] ||= []
|
|
53
|
+
template_rows[name] << out.gsub("\n", ";").gsub(/\s+/, " ")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
template_rows = template_rows.sort_by{|name, value| name.to_s}
|
|
58
|
+
|
|
59
|
+
Railroader::Report::Renderer.new('template_overview', :locals => {:template_rows => template_rows}).render
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def render_array template, headings, value_array, locals
|
|
63
|
+
return if value_array.empty?
|
|
64
|
+
|
|
65
|
+
Railroader::Report::Renderer.new(template, :locals => locals).render
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def convert_warning warning, original
|
|
69
|
+
warning["Confidence"] = HTML_CONFIDENCE[warning["Confidence"]]
|
|
70
|
+
warning["Message"] = with_context original, warning["Message"]
|
|
71
|
+
warning["Warning Type"] = with_link original, warning["Warning Type"]
|
|
72
|
+
warning
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def with_link warning, message
|
|
76
|
+
"<a rel=\"no-referrer\" href=\"#{warning.link}\">#{message}</a>"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def convert_template_warning warning, original
|
|
80
|
+
warning["Confidence"] = HTML_CONFIDENCE[warning["Confidence"]]
|
|
81
|
+
warning["Message"] = with_context original, warning["Message"]
|
|
82
|
+
warning["Warning Type"] = with_link original, warning["Warning Type"]
|
|
83
|
+
warning["Called From"] = original.called_from
|
|
84
|
+
warning["Template Name"] = original.template.name
|
|
85
|
+
warning
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def convert_ignored_warning warning, original
|
|
89
|
+
warning = convert_warning(warning, original)
|
|
90
|
+
warning['File'] = original.relative_path
|
|
91
|
+
warning['Note'] = CGI.escapeHTML(@ignore_filter.note_for(original) || "")
|
|
92
|
+
warning
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
#Return header for HTML output. Uses CSS from tracker.options[:html_style]
|
|
96
|
+
def html_header
|
|
97
|
+
if File.exist? tracker.options[:html_style]
|
|
98
|
+
css = File.read tracker.options[:html_style]
|
|
99
|
+
else
|
|
100
|
+
raise "Cannot find CSS stylesheet for HTML: #{tracker.options[:html_style]}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
locals = {
|
|
104
|
+
:css => css,
|
|
105
|
+
:tracker => tracker,
|
|
106
|
+
:checks => checks,
|
|
107
|
+
:rails_version => rails_version,
|
|
108
|
+
:railroader_version => Railroader::Version
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Railroader::Report::Renderer.new('header', :locals => locals).render
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
#Generate HTML for warnings, including context show/hidden via Javascript
|
|
115
|
+
def with_context warning, message
|
|
116
|
+
context = context_for(@app_tree, warning)
|
|
117
|
+
full_message = nil
|
|
118
|
+
|
|
119
|
+
if tracker.options[:message_limit] and tracker.options[:message_limit] > 0 and message.length > tracker.options[:message_limit]
|
|
120
|
+
full_message = html_message(warning, message)
|
|
121
|
+
message = message[0..tracker.options[:message_limit]] << "..."
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
message = html_message(warning, message)
|
|
125
|
+
return message if context.empty? and not full_message
|
|
126
|
+
|
|
127
|
+
@element_id += 1
|
|
128
|
+
code_id = "context#@element_id"
|
|
129
|
+
message_id = "message#@element_id"
|
|
130
|
+
full_message_id = "full_message#@element_id"
|
|
131
|
+
alt = false
|
|
132
|
+
output = "<div class='warning_message' onClick=\"toggle('#{code_id}');toggle('#{message_id}');toggle('#{full_message_id}')\" >" <<
|
|
133
|
+
if full_message
|
|
134
|
+
"<span id='#{message_id}' style='display:block' >#{message}</span>" <<
|
|
135
|
+
"<span id='#{full_message_id}' style='display:none'>#{full_message}</span>"
|
|
136
|
+
else
|
|
137
|
+
message
|
|
138
|
+
end <<
|
|
139
|
+
"<table id='#{code_id}' class='context' style='display:none'>" <<
|
|
140
|
+
"<caption>#{CGI.escapeHTML warning_file(warning) || ''}</caption>"
|
|
141
|
+
|
|
142
|
+
output << <<-HTML
|
|
143
|
+
<thead style='display:none'>
|
|
144
|
+
<tr>
|
|
145
|
+
<th>line number</th>
|
|
146
|
+
<th>line content</th>
|
|
147
|
+
</tr>
|
|
148
|
+
</thead>
|
|
149
|
+
<tbody>
|
|
150
|
+
HTML
|
|
151
|
+
|
|
152
|
+
unless context.empty?
|
|
153
|
+
if warning.line - 1 == 1 or warning.line + 1 == 1
|
|
154
|
+
error = " near_error"
|
|
155
|
+
elsif 1 == warning.line
|
|
156
|
+
error = " error"
|
|
157
|
+
else
|
|
158
|
+
error = ""
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
output << <<-HTML
|
|
162
|
+
<tr class='context first#{error}'>
|
|
163
|
+
<td class='context_line'>
|
|
164
|
+
<pre class='context'>#{context.first[0]}</pre>
|
|
165
|
+
</td>
|
|
166
|
+
<td class='context'>
|
|
167
|
+
<pre class='context'>#{CGI.escapeHTML context.first[1].chomp}</pre>
|
|
168
|
+
</td>
|
|
169
|
+
</tr>
|
|
170
|
+
HTML
|
|
171
|
+
|
|
172
|
+
if context.length > 1
|
|
173
|
+
output << context[1..-1].map do |code|
|
|
174
|
+
alt = !alt
|
|
175
|
+
if code[0] == warning.line - 1 or code[0] == warning.line + 1
|
|
176
|
+
error = " near_error"
|
|
177
|
+
elsif code[0] == warning.line
|
|
178
|
+
error = " error"
|
|
179
|
+
else
|
|
180
|
+
error = ""
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
<<-HTML
|
|
184
|
+
<tr class='context#{alt ? ' alt' : ''}#{error}'>
|
|
185
|
+
<td class='context_line'>
|
|
186
|
+
<pre class='context'>#{code[0]}</pre>
|
|
187
|
+
</td>
|
|
188
|
+
<td class='context'>
|
|
189
|
+
<pre class='context'>#{CGI.escapeHTML code[1].chomp}</pre>
|
|
190
|
+
</td>
|
|
191
|
+
</tr>
|
|
192
|
+
HTML
|
|
193
|
+
end.join
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
output << "</tbody></table></div>"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
#Escape warning message and highlight user input in HTML output
|
|
201
|
+
def html_message warning, message
|
|
202
|
+
message = CGI.escapeHTML(message)
|
|
203
|
+
|
|
204
|
+
if warning.file
|
|
205
|
+
github_url = github_url warning.file, warning.line
|
|
206
|
+
message.gsub!(/(near line \d+)/, "<a href=\"#{github_url}\" target='_blank'>\\1</a>") if github_url
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
if @highlight_user_input and warning.user_input
|
|
210
|
+
user_input = CGI.escapeHTML(warning.format_user_input)
|
|
211
|
+
message.gsub!(user_input, "<span class=\"user_input\">#{user_input}</span>")
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
message
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
class Railroader::Report::JSON < Railroader::Report::Base
|
|
2
|
+
def generate_report
|
|
3
|
+
errors = tracker.errors.map{|e| { :error => e[:error], :location => e[:backtrace][0] }}
|
|
4
|
+
|
|
5
|
+
obsolete = tracker.unused_fingerprints
|
|
6
|
+
|
|
7
|
+
warnings = convert_to_hashes all_warnings
|
|
8
|
+
|
|
9
|
+
ignored = convert_to_hashes ignored_warnings
|
|
10
|
+
|
|
11
|
+
scan_info = {
|
|
12
|
+
:app_path => tracker.app_path,
|
|
13
|
+
:rails_version => rails_version,
|
|
14
|
+
:security_warnings => all_warnings.length,
|
|
15
|
+
:start_time => tracker.start_time.to_s,
|
|
16
|
+
:end_time => tracker.end_time.to_s,
|
|
17
|
+
:duration => tracker.duration,
|
|
18
|
+
:checks_performed => checks.checks_run.sort,
|
|
19
|
+
:number_of_controllers => tracker.controllers.length,
|
|
20
|
+
# ignore the "fake" model
|
|
21
|
+
:number_of_models => tracker.models.length - 1,
|
|
22
|
+
:number_of_templates => number_of_templates(@tracker),
|
|
23
|
+
:ruby_version => RUBY_VERSION,
|
|
24
|
+
:railroader_version => Railroader::Version
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
report_info = {
|
|
28
|
+
:scan_info => scan_info,
|
|
29
|
+
:warnings => warnings,
|
|
30
|
+
:ignored_warnings => ignored,
|
|
31
|
+
:errors => errors,
|
|
32
|
+
:obsolete => obsolete
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
JSON.pretty_generate report_info
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def convert_to_hashes warnings
|
|
39
|
+
warnings.map do |w|
|
|
40
|
+
hash = w.to_hash
|
|
41
|
+
hash[:file] = warning_file w
|
|
42
|
+
hash
|
|
43
|
+
end.sort_by { |w| "#{w[:fingerprint]}#{w[:line]}" }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'railroader/report/report_table'
|
|
2
|
+
|
|
3
|
+
class Railroader::Report::Markdown < Railroader::Report::Table
|
|
4
|
+
|
|
5
|
+
class MarkdownTable < Terminal::Table
|
|
6
|
+
|
|
7
|
+
def initialize options = {}, &block
|
|
8
|
+
options[:style] ||= {}
|
|
9
|
+
options[:style].merge!({
|
|
10
|
+
:border_x => '-',
|
|
11
|
+
:border_y => '|',
|
|
12
|
+
:border_i => '|'
|
|
13
|
+
})
|
|
14
|
+
super options, &block
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def render
|
|
18
|
+
super.split("\n")[1...-1].join("\n")
|
|
19
|
+
end
|
|
20
|
+
alias :to_s :render
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize *args
|
|
25
|
+
super
|
|
26
|
+
@table = MarkdownTable
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def generate_report
|
|
30
|
+
out = "# BRAKEMAN REPORT\n\n" <<
|
|
31
|
+
generate_metadata.to_s << "\n\n" <<
|
|
32
|
+
generate_checks.to_s << "\n\n" <<
|
|
33
|
+
"### SUMMARY\n\n" <<
|
|
34
|
+
generate_overview.to_s << "\n\n" <<
|
|
35
|
+
generate_warning_overview.to_s << "\n\n"
|
|
36
|
+
|
|
37
|
+
#Return output early if only summarizing
|
|
38
|
+
return out if tracker.options[:summary_only]
|
|
39
|
+
|
|
40
|
+
if tracker.options[:report_routes] or tracker.options[:debug]
|
|
41
|
+
out << "### CONTROLLERS" << "\n\n" <<
|
|
42
|
+
generate_controllers.to_s << "\n\n"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if tracker.options[:debug]
|
|
46
|
+
out << "### TEMPLATES\n\n" <<
|
|
47
|
+
generate_templates.to_s << "\n\n"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
output_table("Errors", generate_errors, out)
|
|
51
|
+
output_table("SECURITY WARNINGS", generate_warnings, out)
|
|
52
|
+
output_table("Controller Warnings:", generate_controller_warnings, out)
|
|
53
|
+
output_table("Model Warnings:", generate_model_warnings, out)
|
|
54
|
+
output_table("View Warnings:", generate_template_warnings, out)
|
|
55
|
+
|
|
56
|
+
out
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def output_table title, result, output
|
|
60
|
+
return unless result
|
|
61
|
+
|
|
62
|
+
output << "### #{title}\n\n#{result.to_s}\n\n"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def generate_metadata
|
|
66
|
+
MarkdownTable.new(
|
|
67
|
+
:headings =>
|
|
68
|
+
['Application path', 'Rails version', 'Railroader version', 'Started at', 'Duration']
|
|
69
|
+
) do |t|
|
|
70
|
+
t.add_row([
|
|
71
|
+
tracker.app_path,
|
|
72
|
+
rails_version,
|
|
73
|
+
Railroader::Version,
|
|
74
|
+
tracker.start_time,
|
|
75
|
+
"#{tracker.duration} seconds",
|
|
76
|
+
])
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def generate_checks
|
|
81
|
+
MarkdownTable.new(:headings => ['Checks performed']) do |t|
|
|
82
|
+
t.add_row([checks.checks_run.sort.join(", ")])
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def convert_warning warning, original
|
|
87
|
+
warning["Confidence"] = TEXT_CONFIDENCE[warning["Confidence"]]
|
|
88
|
+
warning["Message"] = markdown_message original, warning["Message"]
|
|
89
|
+
warning["Warning Type"] = "[#{warning['Warning Type']}](#{original.link})" if original.link
|
|
90
|
+
warning
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Escape and code format warning message
|
|
94
|
+
def markdown_message warning, message
|
|
95
|
+
if warning.file
|
|
96
|
+
github_url = github_url warning.file, warning.line
|
|
97
|
+
message.gsub!(/(near line \d+)/, "[\\1](#{github_url})") if github_url
|
|
98
|
+
end
|
|
99
|
+
if warning.code
|
|
100
|
+
code = warning.format_code
|
|
101
|
+
message.gsub(code, "`#{code.gsub('`','``').gsub(/\A``|``\z/, '` `')}`")
|
|
102
|
+
else
|
|
103
|
+
message
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Railroader.load_railroader_dependency 'terminal-table'
|
|
2
|
+
|
|
3
|
+
class Railroader::Report::Table < Railroader::Report::Base
|
|
4
|
+
def initialize *args
|
|
5
|
+
super
|
|
6
|
+
@table = Terminal::Table
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def generate_report
|
|
10
|
+
summary_option = tracker.options[:summary_only]
|
|
11
|
+
out = ""
|
|
12
|
+
|
|
13
|
+
unless summary_option == :no_summary
|
|
14
|
+
out << text_header <<
|
|
15
|
+
"\n\n+SUMMARY+\n\n" <<
|
|
16
|
+
truncate_table(generate_overview.to_s) << "\n\n" <<
|
|
17
|
+
truncate_table(generate_warning_overview.to_s) << "\n"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#Return output early if only summarizing
|
|
21
|
+
if summary_option == :summary_only or summary_option == true
|
|
22
|
+
return out
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if tracker.options[:report_routes] or tracker.options[:debug]
|
|
26
|
+
out << "\n+CONTROLLERS+\n" <<
|
|
27
|
+
truncate_table(generate_controllers.to_s) << "\n"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if tracker.options[:debug]
|
|
31
|
+
out << "\n+TEMPLATES+\n\n" <<
|
|
32
|
+
truncate_table(generate_templates.to_s) << "\n"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
output_table("+Obsolete Ignore Entries+", generate_obsolete, out)
|
|
36
|
+
output_table("+Errors+", generate_errors, out)
|
|
37
|
+
output_table("+SECURITY WARNINGS+", generate_warnings, out)
|
|
38
|
+
output_table("Controller Warnings:", generate_controller_warnings, out)
|
|
39
|
+
output_table("Model Warnings:", generate_model_warnings, out)
|
|
40
|
+
output_table("View Warnings:", generate_template_warnings, out)
|
|
41
|
+
|
|
42
|
+
out << "\n"
|
|
43
|
+
out
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def output_table title, result, output
|
|
47
|
+
return unless result
|
|
48
|
+
|
|
49
|
+
output << "\n\n#{title}\n\n#{truncate_table(result.to_s)}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def generate_overview
|
|
53
|
+
num_warnings = all_warnings.length
|
|
54
|
+
|
|
55
|
+
@table.new(:headings => ['Scanned/Reported', 'Total']) do |t|
|
|
56
|
+
t.add_row ['Controllers', tracker.controllers.length]
|
|
57
|
+
t.add_row ['Models', tracker.models.length - 1]
|
|
58
|
+
t.add_row ['Templates', number_of_templates(@tracker)]
|
|
59
|
+
t.add_row ['Errors', tracker.errors.length]
|
|
60
|
+
t.add_row ['Security Warnings', "#{num_warnings} (#{warnings_summary[:high_confidence]})"]
|
|
61
|
+
t.add_row ['Ignored Warnings', ignored_warnings.length] unless ignored_warnings.empty?
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#Generate listings of templates and their output
|
|
66
|
+
def generate_templates
|
|
67
|
+
out_processor = Railroader::OutputProcessor.new
|
|
68
|
+
template_rows = {}
|
|
69
|
+
tracker.templates.each do |name, template|
|
|
70
|
+
template.each_output do |out|
|
|
71
|
+
out = out_processor.format out
|
|
72
|
+
template_rows[name] ||= []
|
|
73
|
+
template_rows[name] << out.gsub("\n", ";").gsub(/\s+/, " ")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
template_rows = template_rows.sort_by{|name, value| name.to_s}
|
|
78
|
+
|
|
79
|
+
output = ''
|
|
80
|
+
template_rows.each do |template|
|
|
81
|
+
output << template.first.to_s << "\n\n"
|
|
82
|
+
table = @table.new(:headings => ['Output']) do |t|
|
|
83
|
+
# template[1] is an array of calls
|
|
84
|
+
template[1].each do |v|
|
|
85
|
+
t.add_row [v]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
output << table.to_s << "\n\n"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
output
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def render_array template, headings, value_array, locals
|
|
96
|
+
return if value_array.empty?
|
|
97
|
+
|
|
98
|
+
@table.new(:headings => headings) do |t|
|
|
99
|
+
value_array.each { |value_row| t.add_row value_row }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
#Generate header for text output
|
|
104
|
+
def text_header
|
|
105
|
+
<<-HEADER
|
|
106
|
+
|
|
107
|
+
+BRAKEMAN REPORT+
|
|
108
|
+
|
|
109
|
+
Application path: #{tracker.app_path}
|
|
110
|
+
Rails version: #{rails_version}
|
|
111
|
+
Railroader version: #{Railroader::Version}
|
|
112
|
+
Started at #{tracker.start_time}
|
|
113
|
+
Duration: #{tracker.duration} seconds
|
|
114
|
+
Checks run: #{checks.checks_run.sort.join(", ")}
|
|
115
|
+
HEADER
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#Generated tab-separated output suitable for the Jenkins Railroader Plugin:
|
|
2
|
+
#https://github.com/presidentbeef/railroader-jenkins-plugin
|
|
3
|
+
class Railroader::Report::Tabs < Railroader::Report::Base
|
|
4
|
+
def generate_report
|
|
5
|
+
[[:generic_warnings, "General"], [:controller_warnings, "Controller"],
|
|
6
|
+
[:model_warnings, "Model"], [:template_warnings, "Template"]].map do |meth, category|
|
|
7
|
+
|
|
8
|
+
self.send(meth).map do |w|
|
|
9
|
+
line = w.line || 0
|
|
10
|
+
w.warning_type.gsub!(/[^\w\s]/, ' ')
|
|
11
|
+
"#{warning_file(w, :absolute)}\t#{line}\t#{w.warning_type}\t#{category}\t#{w.format_message}\t#{TEXT_CONFIDENCE[w.confidence]}"
|
|
12
|
+
end.join "\n"
|
|
13
|
+
|
|
14
|
+
end.join "\n"
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Railroader.load_railroader_dependency 'highline'
|
|
2
|
+
|
|
3
|
+
class Railroader::Report::Text < Railroader::Report::Base
|
|
4
|
+
def generate_report
|
|
5
|
+
HighLine.use_color = !!tracker.options[:output_color]
|
|
6
|
+
summary_option = tracker.options[:summary_only]
|
|
7
|
+
@output_string = "\n"
|
|
8
|
+
|
|
9
|
+
unless summary_option == :no_summary
|
|
10
|
+
add_chunk generate_header
|
|
11
|
+
add_chunk generate_overview
|
|
12
|
+
add_chunk generate_warning_overview
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if summary_option == :summary_only or summary_option == true
|
|
16
|
+
return @output_string
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
add_chunk generate_controllers if tracker.options[:debug] or tracker.options[:report_routes]
|
|
20
|
+
add_chunk generate_templates if tracker.options[:debug]
|
|
21
|
+
add_chunk generate_obsolete
|
|
22
|
+
add_chunk generate_errors
|
|
23
|
+
add_chunk generate_warnings
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_chunk chunk, out = @output_string
|
|
27
|
+
if chunk and not chunk.empty?
|
|
28
|
+
if chunk.is_a? Array
|
|
29
|
+
chunk = chunk.join("\n")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
out << chunk << "\n\n"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def generate_header
|
|
37
|
+
[
|
|
38
|
+
header("Railroader Report"),
|
|
39
|
+
label("Application Path", tracker.app_path),
|
|
40
|
+
label("Rails Version", rails_version),
|
|
41
|
+
label("Railroader Version", Railroader::Version),
|
|
42
|
+
label("Scan Date", tracker.start_time),
|
|
43
|
+
label("Duration", "#{tracker.duration} seconds"),
|
|
44
|
+
label("Checks Run", checks.checks_run.sort.join(", "))
|
|
45
|
+
]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def generate_overview
|
|
49
|
+
overview = [
|
|
50
|
+
header("Overview"),
|
|
51
|
+
label('Controllers', tracker.controllers.length),
|
|
52
|
+
label('Models', tracker.models.length - 1),
|
|
53
|
+
label('Templates', number_of_templates(@tracker)),
|
|
54
|
+
label('Errors', tracker.errors.length),
|
|
55
|
+
label('Security Warnings', all_warnings.length)
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
unless ignored_warnings.empty?
|
|
59
|
+
overview << label('Ignored Warnings', ignored_warnings.length)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
overview
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def generate_warning_overview
|
|
66
|
+
warning_types = warnings_summary
|
|
67
|
+
warning_types.delete :high_confidence
|
|
68
|
+
|
|
69
|
+
warning_types.sort_by { |t, c| t }.map do |type, count|
|
|
70
|
+
label(type, count)
|
|
71
|
+
end.unshift(header('Warning Types'))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def generate_warnings
|
|
75
|
+
if tracker.filtered_warnings.empty?
|
|
76
|
+
HighLine.color("No warnings found", :bold, :green)
|
|
77
|
+
else
|
|
78
|
+
warnings = tracker.filtered_warnings.sort_by do |w|
|
|
79
|
+
[w.confidence, w.warning_type, w.fingerprint]
|
|
80
|
+
end.map do |w|
|
|
81
|
+
output_warning w
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
double_space "Warnings", warnings
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def generate_errors
|
|
89
|
+
return if tracker.errors.empty?
|
|
90
|
+
full_trace = tracker.options[:debug]
|
|
91
|
+
|
|
92
|
+
errors = tracker.errors.map do |e|
|
|
93
|
+
trace = if full_trace
|
|
94
|
+
e[:backtrace].join("\n")
|
|
95
|
+
else
|
|
96
|
+
e[:backtrace][0]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
[
|
|
100
|
+
label("Error", e[:error]),
|
|
101
|
+
label("Location", trace)
|
|
102
|
+
]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
double_space "Errors", errors
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def generate_obsolete
|
|
109
|
+
return if tracker.unused_fingerprints.empty?
|
|
110
|
+
|
|
111
|
+
[header("Obsolete Ignore Entries")] + tracker.unused_fingerprints
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def generate_templates
|
|
115
|
+
out_processor = Railroader::OutputProcessor.new
|
|
116
|
+
|
|
117
|
+
template_rows = {}
|
|
118
|
+
tracker.templates.each do |name, template|
|
|
119
|
+
template.each_output do |out|
|
|
120
|
+
out = out_processor.format out
|
|
121
|
+
template_rows[name] ||= []
|
|
122
|
+
template_rows[name] << out.gsub("\n", ";").gsub(/\s+/, " ")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
double_space "Template Output", template_rows.sort_by { |name, value| name.to_s }.map { |template|
|
|
127
|
+
[HighLine.new.color(template.first.to_s << "\n", :cyan)] + template[1]
|
|
128
|
+
}.compact
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def output_warning w
|
|
132
|
+
out = [
|
|
133
|
+
label('Confidence', confidence(w.confidence)),
|
|
134
|
+
label('Category', w.warning_type.to_s),
|
|
135
|
+
label('Check', w.check.gsub(/^Railroader::Check/, '')),
|
|
136
|
+
label('Message', w.message)
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
if w.code
|
|
140
|
+
out << label('Code', format_code(w))
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
out << label('File', warning_file(w))
|
|
144
|
+
|
|
145
|
+
if w.line
|
|
146
|
+
out << label('Line', w.line)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
out
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def double_space title, values
|
|
153
|
+
values = values.map { |v| v.join("\n") }.join("\n\n")
|
|
154
|
+
[header(title), values]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def format_code w
|
|
158
|
+
if @highlight_user_input and w.user_input
|
|
159
|
+
w.format_with_user_input do |exp, text|
|
|
160
|
+
HighLine.new.color(text, :yellow)
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
w.format_code
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def confidence c
|
|
168
|
+
case c
|
|
169
|
+
when 0
|
|
170
|
+
HighLine.new.color("High", :red)
|
|
171
|
+
when 1
|
|
172
|
+
HighLine.new.color("Medium", :yellow)
|
|
173
|
+
when 2
|
|
174
|
+
HighLine.new.color("Weak", :none)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def label l, value, color = :green
|
|
179
|
+
"#{HighLine.new.color(l, color)}: #{value}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def header text
|
|
183
|
+
HighLine.new.color("== #{text} ==\n", :bold, :magenta)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# ONLY used for generate_controllers to avoid duplication
|
|
187
|
+
def render_array name, cols, values, locals
|
|
188
|
+
controllers = values.map do |name, parent, includes, routes|
|
|
189
|
+
c = [ label("Controller", name) ]
|
|
190
|
+
c << label("Parent", parent) unless parent.empty?
|
|
191
|
+
c << label("Includes", includes) unless includes.empty?
|
|
192
|
+
c << label("Routes", routes)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
double_space "Controller Overview", controllers
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|