brakeman 2.0.0 → 2.1.0
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.
- data/CHANGES +20 -0
- data/README.md +6 -1
- data/bin/brakeman +13 -3
- data/lib/brakeman.rb +64 -7
- data/lib/brakeman/call_index.rb +6 -4
- data/lib/brakeman/checks/check_basic_auth.rb +47 -2
- data/lib/brakeman/checks/check_cross_site_scripting.rb +50 -12
- data/lib/brakeman/checks/check_execute.rb +4 -1
- data/lib/brakeman/checks/check_model_attr_accessible.rb +48 -0
- data/lib/brakeman/checks/check_sql.rb +101 -154
- data/lib/brakeman/options.rb +16 -0
- data/lib/brakeman/parsers/rails2_erubis.rb +2 -0
- data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +2 -0
- data/lib/brakeman/parsers/rails3_erubis.rb +2 -0
- data/lib/brakeman/processors/alias_processor.rb +19 -4
- data/lib/brakeman/processors/controller_alias_processor.rb +2 -3
- data/lib/brakeman/processors/gem_processor.rb +5 -4
- data/lib/brakeman/processors/lib/find_all_calls.rb +43 -16
- data/lib/brakeman/report.rb +39 -640
- data/lib/brakeman/report/ignore/config.rb +130 -0
- data/lib/brakeman/report/ignore/interactive.rb +311 -0
- data/lib/brakeman/report/renderer.rb +2 -0
- data/lib/brakeman/report/report_base.rb +279 -0
- data/lib/brakeman/report/report_csv.rb +56 -0
- data/lib/brakeman/report/report_hash.rb +22 -0
- data/lib/brakeman/report/report_html.rb +203 -0
- data/lib/brakeman/report/report_json.rb +46 -0
- data/lib/brakeman/report/report_table.rb +109 -0
- data/lib/brakeman/report/report_tabs.rb +17 -0
- data/lib/brakeman/report/templates/ignored_warnings.html.erb +21 -0
- data/lib/brakeman/report/templates/overview.html.erb +6 -0
- data/lib/brakeman/report/templates/security_warnings.html.erb +1 -1
- data/lib/brakeman/scanner.rb +14 -12
- data/lib/brakeman/tracker.rb +5 -1
- data/lib/brakeman/util.rb +2 -0
- data/lib/brakeman/version.rb +1 -1
- data/lib/ruby_parser/bm_sexp.rb +12 -1
- metadata +179 -90
- checksums.yaml +0 -7
@@ -26,6 +26,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
26
26
|
@tracker = tracker #set in subclass as necessary
|
27
27
|
@helper_method_cache = {}
|
28
28
|
@helper_method_info = Hash.new({})
|
29
|
+
@or_depth_limit = (tracker && tracker.options[:branch_limit]) || 5 #arbitrary default
|
29
30
|
set_env_defaults
|
30
31
|
end
|
31
32
|
|
@@ -354,7 +355,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
354
355
|
|
355
356
|
unless env[match]
|
356
357
|
if request_value? target
|
357
|
-
env[match] =
|
358
|
+
env[match] = match.combine(value)
|
358
359
|
else
|
359
360
|
env[match] = value
|
360
361
|
end
|
@@ -464,11 +465,18 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
464
465
|
|
465
466
|
def merge_if_branch branch_env
|
466
467
|
branch_env.each do |k, v|
|
468
|
+
next if v.nil?
|
469
|
+
|
467
470
|
current_val = env[k]
|
468
471
|
|
469
472
|
if current_val
|
470
|
-
unless same_value?
|
471
|
-
|
473
|
+
unless same_value?(current_val, v)
|
474
|
+
if too_deep? current_val
|
475
|
+
# Give up branching, start over with latest value
|
476
|
+
env[k] = v
|
477
|
+
else
|
478
|
+
env[k] = current_val.combine(v, k.line)
|
479
|
+
end
|
472
480
|
end
|
473
481
|
else
|
474
482
|
env[k] = v
|
@@ -476,6 +484,13 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
476
484
|
end
|
477
485
|
end
|
478
486
|
|
487
|
+
def too_deep? exp
|
488
|
+
@or_depth_limit >= 0 and
|
489
|
+
node_type? exp, :or and
|
490
|
+
exp.or_depth and
|
491
|
+
exp.or_depth >= @or_depth_limit
|
492
|
+
end
|
493
|
+
|
479
494
|
#Process single integer access to an array.
|
480
495
|
#
|
481
496
|
#Returns the value inside the array, if possible.
|
@@ -701,7 +716,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
701
716
|
elsif false? condition
|
702
717
|
exp.else_clause
|
703
718
|
else
|
704
|
-
|
719
|
+
exp.then_clause.combine(exp.else_clause, exp.line)
|
705
720
|
end
|
706
721
|
end
|
707
722
|
end
|
@@ -11,10 +11,9 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
|
|
11
11
|
#other methods will be skipped.
|
12
12
|
#This is for rescanning just a single action.
|
13
13
|
def initialize app_tree, tracker, only_method = nil
|
14
|
-
super
|
14
|
+
super tracker
|
15
15
|
@app_tree = app_tree
|
16
16
|
@only_method = only_method
|
17
|
-
@tracker = tracker
|
18
17
|
@rendered = false
|
19
18
|
@current_class = @current_module = @current_method = nil
|
20
19
|
@method_cache = {} #Cache method lookups
|
@@ -49,7 +48,7 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
|
|
49
48
|
#Need to process the method like it was in a controller in order
|
50
49
|
#to get the renders set
|
51
50
|
processor = Brakeman::ControllerProcessor.new(@app_tree, @tracker)
|
52
|
-
method = mixin[:public][name]
|
51
|
+
method = mixin[:public][name].deep_clone
|
53
52
|
|
54
53
|
if node_type? method, :methdef
|
55
54
|
method = processor.process_defn method
|
@@ -40,11 +40,12 @@ class Brakeman::GemProcessor < Brakeman::BaseProcessor
|
|
40
40
|
|
41
41
|
exp
|
42
42
|
end
|
43
|
-
|
44
|
-
#
|
43
|
+
|
44
|
+
# Supports .rc2 but not ~>, >=, or <=
|
45
45
|
def get_version name, gem_lock
|
46
|
-
|
47
|
-
|
46
|
+
if gem_lock =~ /\s#{name} \((\w(\.\w+)*)\)(?:\n|\r\n)/
|
47
|
+
$1
|
48
|
+
end
|
48
49
|
end
|
49
50
|
|
50
51
|
def get_rails_version gem_lock
|
@@ -37,28 +37,33 @@ class Brakeman::FindAllCalls < Brakeman::BaseProcessor
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def process_call exp
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
already_in_target = @in_target
|
44
|
-
@in_target = true
|
45
|
-
process target
|
46
|
-
@in_target = already_in_target
|
47
|
-
end
|
40
|
+
@calls << create_call_hash(exp)
|
41
|
+
exp
|
42
|
+
end
|
48
43
|
|
49
|
-
|
50
|
-
|
44
|
+
def process_call_with_block exp
|
45
|
+
call = exp.block_call
|
51
46
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
47
|
+
if call.node_type == :call
|
48
|
+
call_hash = create_call_hash(call)
|
49
|
+
|
50
|
+
call_hash[:block] = exp.block
|
51
|
+
call_hash[:block_args] = exp.block_args
|
52
|
+
|
53
|
+
@calls << call_hash
|
54
|
+
|
55
|
+
process exp.block
|
56
|
+
else
|
57
|
+
#Probably a :render call with block
|
58
|
+
process call
|
59
|
+
process exp.block
|
60
|
+
end
|
58
61
|
|
59
62
|
exp
|
60
63
|
end
|
61
64
|
|
65
|
+
alias process_iter process_call_with_block
|
66
|
+
|
62
67
|
#Calls to render() are converted to s(:render, ...) but we would
|
63
68
|
#like them in the call cache still for speed
|
64
69
|
def process_render exp
|
@@ -155,4 +160,26 @@ class Brakeman::FindAllCalls < Brakeman::BaseProcessor
|
|
155
160
|
end
|
156
161
|
|
157
162
|
end
|
163
|
+
|
164
|
+
#Return info hash for a call Sexp
|
165
|
+
def create_call_hash exp
|
166
|
+
target = get_target exp.target
|
167
|
+
|
168
|
+
if call? target
|
169
|
+
already_in_target = @in_target
|
170
|
+
@in_target = true
|
171
|
+
process target
|
172
|
+
@in_target = already_in_target
|
173
|
+
end
|
174
|
+
|
175
|
+
method = exp.method
|
176
|
+
process_call_args exp
|
177
|
+
|
178
|
+
{ :target => target,
|
179
|
+
:method => method,
|
180
|
+
:call => exp,
|
181
|
+
:nested => @in_target,
|
182
|
+
:chain => get_chain(exp),
|
183
|
+
:location => make_location }
|
184
|
+
end
|
158
185
|
end
|
data/lib/brakeman/report.rb
CHANGED
@@ -1,669 +1,68 @@
|
|
1
|
-
require '
|
2
|
-
require 'set'
|
3
|
-
require 'brakeman/processors/output_processor'
|
4
|
-
require 'brakeman/util'
|
5
|
-
require 'terminal-table'
|
6
|
-
require 'highline'
|
7
|
-
require "csv"
|
8
|
-
require 'multi_json'
|
9
|
-
require 'brakeman/version'
|
10
|
-
require 'brakeman/report/renderer'
|
11
|
-
Dir.glob(File.dirname(__FILE__) + '/report/initializers/*.rb').each {|file| require file }
|
1
|
+
require 'brakeman/report/report_base'
|
12
2
|
|
13
3
|
#Generates a report based on the Tracker and the results of
|
14
4
|
#Tracker#run_checks. Be sure to +run_checks+ before generating
|
15
5
|
#a report.
|
16
6
|
class Brakeman::Report
|
17
|
-
|
7
|
+
attr_reader :tracker
|
18
8
|
|
19
|
-
|
9
|
+
VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s]
|
20
10
|
|
21
|
-
|
22
|
-
HTML_CONFIDENCE = [ "<span class='high-confidence'>High</span>",
|
23
|
-
"<span class='med-confidence'>Medium</span>",
|
24
|
-
"<span class='weak-confidence'>Weak</span>" ]
|
25
|
-
|
26
|
-
def initialize(app_tree, tracker)
|
11
|
+
def initialize app_tree, tracker
|
27
12
|
@app_tree = app_tree
|
28
13
|
@tracker = tracker
|
29
|
-
@checks = tracker.checks
|
30
|
-
@element_id = 0 #Used for HTML ids
|
31
|
-
@warnings_summary = nil
|
32
|
-
@highlight_user_input = tracker.options[:highlight_user_input]
|
33
|
-
end
|
34
|
-
|
35
|
-
#Generate summary table of what was parsed
|
36
|
-
def generate_overview html = false
|
37
|
-
warnings = all_warnings.length
|
38
|
-
|
39
|
-
if html
|
40
|
-
locals = {
|
41
|
-
:tracker => tracker,
|
42
|
-
:warnings => warnings,
|
43
|
-
:warnings_summary => warnings_summary,
|
44
|
-
:number_of_templates => number_of_templates(@tracker),
|
45
|
-
}
|
46
|
-
|
47
|
-
Brakeman::Report::Renderer.new('overview', :locals => locals).render
|
48
|
-
else
|
49
|
-
Terminal::Table.new(:headings => ['Scanned/Reported', 'Total']) do |t|
|
50
|
-
t.add_row ['Controllers', tracker.controllers.length]
|
51
|
-
t.add_row ['Models', tracker.models.length - 1]
|
52
|
-
t.add_row ['Templates', number_of_templates(@tracker)]
|
53
|
-
t.add_row ['Errors', tracker.errors.length]
|
54
|
-
t.add_row ['Security Warnings', "#{warnings} (#{warnings_summary[:high_confidence]})"]
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
#Generate table of how many warnings of each warning type were reported
|
60
|
-
def generate_warning_overview html = false
|
61
|
-
types = warnings_summary.keys
|
62
|
-
types.delete :high_confidence
|
63
|
-
values = types.sort.collect{|warning_type| [warning_type, warnings_summary[warning_type]] }
|
64
|
-
locals = {:types => types, :warnings_summary => warnings_summary}
|
65
|
-
|
66
|
-
render_array('warning_overview', ['Warning Type', 'Total'], values, locals, html)
|
67
|
-
end
|
68
|
-
|
69
|
-
#Generate table of errors or return nil if no errors
|
70
|
-
def generate_errors html = false
|
71
|
-
values = tracker.errors.collect{|error| [error[:error], error[:backtrace][0]]}
|
72
|
-
render_array('error_overview', ['Error', 'Location'], values, {:tracker => tracker}, html)
|
73
|
-
end
|
74
|
-
|
75
|
-
def render_array(template, headings, value_array, locals, html = false)
|
76
|
-
return if value_array.empty?
|
77
|
-
if html
|
78
|
-
Brakeman::Report::Renderer.new(template, :locals => locals).render
|
79
|
-
else
|
80
|
-
Terminal::Table.new(:headings => headings) do |t|
|
81
|
-
value_array.each { |value_row| t.add_row value_row }
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
#Generate table of general security warnings
|
87
|
-
def generate_warnings html = false
|
88
|
-
warning_messages = []
|
89
|
-
checks.warnings.each do |warning|
|
90
|
-
w = warning.to_row
|
91
|
-
|
92
|
-
if html
|
93
|
-
w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
|
94
|
-
w["Message"] = with_context warning, w["Message"]
|
95
|
-
w["Warning Type"] = with_link warning, w["Warning Type"]
|
96
|
-
else
|
97
|
-
w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
|
98
|
-
w["Message"] = text_message warning, w["Message"]
|
99
|
-
end
|
100
|
-
|
101
|
-
warning_messages << w
|
102
|
-
end
|
103
|
-
|
104
|
-
stabilizer = 0
|
105
|
-
warning_messages = warning_messages.sort_by{|row| stabilizer += 1; [row['Confidence'], row['Warning Type'], row['Class'], stabilizer]}
|
106
|
-
|
107
|
-
locals = {:warning_messages => warning_messages}
|
108
|
-
values = warning_messages.collect{|row| [row["Confidence"], row["Class"], row["Method"], row["Warning Type"], row["Message"]] }
|
109
|
-
render_array('security_warnings', ["Confidence", "Class", "Method", "Warning Type", "Message"], values, locals, html)
|
110
|
-
end
|
111
|
-
|
112
|
-
#Generate table of template warnings or return nil if no warnings
|
113
|
-
def generate_template_warnings html = false
|
114
|
-
if checks.template_warnings.any?
|
115
|
-
warnings = []
|
116
|
-
checks.template_warnings.each do |warning|
|
117
|
-
w = warning.to_row :template
|
118
|
-
|
119
|
-
if html
|
120
|
-
w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
|
121
|
-
w["Message"] = with_context warning, w["Message"]
|
122
|
-
w["Warning Type"] = with_link warning, w["Warning Type"]
|
123
|
-
w["Called From"] = warning.called_from
|
124
|
-
w["Template Name"] = warning.template[:name]
|
125
|
-
else
|
126
|
-
w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
|
127
|
-
w["Message"] = text_message warning, w["Message"]
|
128
|
-
end
|
129
|
-
|
130
|
-
warnings << w
|
131
|
-
end
|
132
|
-
|
133
|
-
return nil if warnings.empty?
|
134
|
-
|
135
|
-
stabilizer = 0
|
136
|
-
warnings = warnings.sort_by{|row| stabilizer += 1; [row["Confidence"], row["Warning Type"], row["Template"], stabilizer]}
|
137
|
-
|
138
|
-
locals = {:warnings => warnings}
|
139
|
-
values = warnings.collect{|warning| [warning["Confidence"], warning["Template"], warning["Warning Type"], warning["Message"]] }
|
140
|
-
render_array('view_warnings', ["Confidence", "Template", "Warning Type", "Message"], values, locals, html)
|
141
|
-
else
|
142
|
-
nil
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
#Generate table of model warnings or return nil if no warnings
|
147
|
-
def generate_model_warnings html = false
|
148
|
-
if checks.model_warnings.any?
|
149
|
-
warnings = []
|
150
|
-
checks.model_warnings.each do |warning|
|
151
|
-
w = warning.to_row :model
|
152
|
-
|
153
|
-
if html
|
154
|
-
w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
|
155
|
-
w["Message"] = with_context warning, w["Message"]
|
156
|
-
w["Warning Type"] = with_link warning, w["Warning Type"]
|
157
|
-
else
|
158
|
-
w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
|
159
|
-
w["Message"] = text_message warning, w["Message"]
|
160
|
-
end
|
161
|
-
|
162
|
-
warnings << w
|
163
|
-
end
|
164
|
-
|
165
|
-
return nil if warnings.empty?
|
166
|
-
stabilizer = 0
|
167
|
-
warnings = warnings.sort_by{|row| stabilizer +=1; [row["Confidence"],row["Warning Type"], row["Model"], stabilizer]}
|
168
|
-
|
169
|
-
locals = {:warnings => warnings}
|
170
|
-
values = warnings.collect{|warning| [warning["Confidence"], warning["Model"], warning["Warning Type"], warning["Message"]] }
|
171
|
-
render_array('model_warnings', ["Confidence", "Model", "Warning Type", "Message"], values, locals, html)
|
172
|
-
else
|
173
|
-
nil
|
174
|
-
end
|
175
14
|
end
|
176
15
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
stabilizer = 0
|
199
|
-
warnings = warnings.sort_by{|row| stabilizer +=1; [row["Confidence"], row["Warning Type"], row["Controller"], stabilizer]}
|
200
|
-
|
201
|
-
locals = {:warnings => warnings}
|
202
|
-
values = warnings.collect{|warning| [warning["Confidence"], warning["Controller"], warning["Warning Type"], warning["Message"]] }
|
203
|
-
render_array('controller_warnings', ["Confidence", "Controller", "Warning Type", "Message"], values, locals, html)
|
16
|
+
def format format
|
17
|
+
reporter = case format
|
18
|
+
when :to_csv
|
19
|
+
require_report 'csv'
|
20
|
+
Brakeman::Report::CSV
|
21
|
+
when :to_html
|
22
|
+
require_report 'html'
|
23
|
+
Brakeman::Report::HTML
|
24
|
+
when :to_json
|
25
|
+
return self.to_json
|
26
|
+
when :to_tabs
|
27
|
+
require_report 'tabs'
|
28
|
+
Brakeman::Report::Tabs
|
29
|
+
when :to_hash
|
30
|
+
require_report 'hash'
|
31
|
+
Brakeman::Report::Hash
|
32
|
+
when :to_s
|
33
|
+
return self.to_s
|
34
|
+
when :to_pdf
|
35
|
+
raise "PDF output is not yet supported."
|
204
36
|
else
|
205
|
-
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
#Generate table of controllers and routes found for those controllers
|
210
|
-
def generate_controllers html=false
|
211
|
-
controller_rows = []
|
212
|
-
tracker.controllers.keys.map{|k| k.to_s}.sort.each do |name|
|
213
|
-
name = name.to_sym
|
214
|
-
c = tracker.controllers[name]
|
215
|
-
|
216
|
-
if tracker.routes[:allow_all_actions] or tracker.routes[name] == :allow_all_actions
|
217
|
-
routes = c[:public].keys.map{|e| e.to_s}.sort.join(", ")
|
218
|
-
elsif tracker.routes[name].nil?
|
219
|
-
#No routes defined for this controller.
|
220
|
-
#This can happen when it is only a parent class
|
221
|
-
#for other controllers, for example.
|
222
|
-
routes = "[None]"
|
223
|
-
|
224
|
-
else
|
225
|
-
routes = (Set.new(c[:public].keys) & tracker.routes[name.to_sym]).
|
226
|
-
to_a.
|
227
|
-
map {|e| e.to_s}.
|
228
|
-
sort.
|
229
|
-
join(", ")
|
230
|
-
end
|
231
|
-
|
232
|
-
if routes == ""
|
233
|
-
routes = "[None]"
|
234
|
-
end
|
235
|
-
|
236
|
-
controller_rows << { "Name" => name.to_s,
|
237
|
-
"Parent" => c[:parent].to_s,
|
238
|
-
"Includes" => c[:includes].join(", "),
|
239
|
-
"Routes" => routes
|
240
|
-
}
|
241
|
-
end
|
242
|
-
controller_rows = controller_rows.sort_by{|row| row['Name']}
|
243
|
-
|
244
|
-
locals = {:controller_rows => controller_rows}
|
245
|
-
values = controller_rows.collect{|row| [row['Name'], row['Parent'], row['Includes'], row['Routes']] }
|
246
|
-
render_array('controller_overview', ['Name', 'Parent', 'Includes', 'Routes'], values, locals, html)
|
247
|
-
end
|
248
|
-
|
249
|
-
#Generate listings of templates and their output
|
250
|
-
def generate_templates html = false
|
251
|
-
out_processor = Brakeman::OutputProcessor.new
|
252
|
-
template_rows = {}
|
253
|
-
tracker.templates.each do |name, template|
|
254
|
-
unless template[:outputs].empty?
|
255
|
-
template[:outputs].each do |out|
|
256
|
-
out = out_processor.format out
|
257
|
-
out = CGI.escapeHTML(out) if html
|
258
|
-
template_rows[name] ||= []
|
259
|
-
template_rows[name] << out.gsub("\n", ";").gsub(/\s+/, " ")
|
260
|
-
end
|
261
|
-
end
|
37
|
+
raise "Invalid format: #{format}. Should be one of #{VALID_FORMATS.inspect}"
|
262
38
|
end
|
263
39
|
|
264
|
-
|
265
|
-
|
266
|
-
if html
|
267
|
-
Brakeman::Report::Renderer.new('template_overview', :locals => {:template_rows => template_rows}).render
|
268
|
-
else
|
269
|
-
output = ''
|
270
|
-
template_rows.each do |template|
|
271
|
-
output << template.first.to_s << "\n\n"
|
272
|
-
table = Terminal::Table.new(:headings => ['Output']) do |t|
|
273
|
-
# template[1] is an array of calls
|
274
|
-
template[1].each do |v|
|
275
|
-
t.add_row [v]
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
output << table.to_s << "\n\n"
|
280
|
-
end
|
281
|
-
|
282
|
-
output
|
283
|
-
end
|
40
|
+
generate(reporter)
|
284
41
|
end
|
285
42
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
when /\.html/, :to_html
|
290
|
-
to_html
|
291
|
-
when /\.pdf/, :to_pdf
|
292
|
-
to_pdf
|
293
|
-
when /\.csv/, :to_csv
|
294
|
-
to_csv
|
295
|
-
when /\.json/, :to_json
|
296
|
-
to_json
|
297
|
-
when /\.tabs/, :to_tabs
|
298
|
-
to_tabs
|
299
|
-
when :to_test
|
300
|
-
to_test
|
43
|
+
def method_missing method, *args
|
44
|
+
if VALID_FORMATS.include? method
|
45
|
+
format method
|
301
46
|
else
|
302
|
-
|
47
|
+
super
|
303
48
|
end
|
304
49
|
end
|
305
|
-
|
306
|
-
#Generate HTML output
|
307
|
-
def to_html
|
308
|
-
out = html_header <<
|
309
|
-
generate_overview(true) <<
|
310
|
-
generate_warning_overview(true).to_s
|
311
|
-
|
312
|
-
# Return early if only summarizing
|
313
|
-
return out if tracker.options[:summary_only]
|
314
|
-
|
315
|
-
out << generate_controllers(true).to_s if tracker.options[:report_routes] or tracker.options[:debug]
|
316
|
-
out << generate_templates(true).to_s if tracker.options[:debug]
|
317
|
-
out << generate_errors(true).to_s
|
318
|
-
out << generate_warnings(true).to_s
|
319
|
-
out << generate_controller_warnings(true).to_s
|
320
|
-
out << generate_model_warnings(true).to_s
|
321
|
-
out << generate_template_warnings(true).to_s
|
322
|
-
out << "</body></html>"
|
323
|
-
end
|
324
|
-
|
325
|
-
#Output text version of the report
|
326
|
-
def to_s
|
327
|
-
out = text_header <<
|
328
|
-
"\n\n+SUMMARY+\n\n" <<
|
329
|
-
truncate_table(generate_overview.to_s) << "\n\n" <<
|
330
|
-
truncate_table(generate_warning_overview.to_s) << "\n"
|
331
|
-
|
332
|
-
#Return output early if only summarizing
|
333
|
-
return out if tracker.options[:summary_only]
|
334
|
-
|
335
|
-
if tracker.options[:report_routes] or tracker.options[:debug]
|
336
|
-
out << "\n+CONTROLLERS+\n" <<
|
337
|
-
truncate_table(generate_controllers.to_s) << "\n"
|
338
|
-
end
|
339
|
-
|
340
|
-
if tracker.options[:debug]
|
341
|
-
out << "\n+TEMPLATES+\n\n" <<
|
342
|
-
truncate_table(generate_templates.to_s) << "\n"
|
343
|
-
end
|
344
|
-
|
345
|
-
res = generate_errors
|
346
|
-
out << "+Errors+\n" << truncate_table(res.to_s) if res
|
347
|
-
|
348
|
-
res = generate_warnings
|
349
|
-
out << "\n\n+SECURITY WARNINGS+\n\n" << truncate_table(res.to_s) if res
|
350
|
-
|
351
|
-
res = generate_controller_warnings
|
352
|
-
out << "\n\n\nController Warnings:\n\n" << truncate_table(res.to_s) if res
|
353
|
-
|
354
|
-
res = generate_model_warnings
|
355
|
-
out << "\n\n\nModel Warnings:\n\n" << truncate_table(res.to_s) if res
|
356
50
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
out << "\n"
|
361
|
-
out
|
362
|
-
end
|
363
|
-
|
364
|
-
#Generate CSV output
|
365
|
-
def to_csv
|
366
|
-
output = csv_header
|
367
|
-
output << "\nSUMMARY\n"
|
368
|
-
|
369
|
-
output << table_to_csv(generate_overview) << "\n"
|
370
|
-
|
371
|
-
output << table_to_csv(generate_warning_overview) << "\n"
|
372
|
-
|
373
|
-
#Return output early if only summarizing
|
374
|
-
if tracker.options[:summary_only]
|
375
|
-
return output
|
376
|
-
end
|
377
|
-
|
378
|
-
if tracker.options[:report_routes] or tracker.options[:debug]
|
379
|
-
output << "CONTROLLERS\n"
|
380
|
-
output << table_to_csv(generate_controllers) << "\n"
|
381
|
-
end
|
382
|
-
|
383
|
-
if tracker.options[:debug]
|
384
|
-
output << "TEMPLATES\n\n"
|
385
|
-
output << table_to_csv(generate_templates) << "\n"
|
386
|
-
end
|
387
|
-
|
388
|
-
res = generate_errors
|
389
|
-
output << "ERRORS\n" << table_to_csv(res) << "\n" if res
|
390
|
-
|
391
|
-
res = generate_warnings
|
392
|
-
output << "SECURITY WARNINGS\n" << table_to_csv(res) << "\n" if res
|
393
|
-
|
394
|
-
output << "Controller Warnings\n"
|
395
|
-
res = generate_controller_warnings
|
396
|
-
output << table_to_csv(res) << "\n" if res
|
397
|
-
|
398
|
-
output << "Model Warnings\n"
|
399
|
-
res = generate_model_warnings
|
400
|
-
output << table_to_csv(res) << "\n" if res
|
401
|
-
|
402
|
-
res = generate_template_warnings
|
403
|
-
output << "Template Warnings\n"
|
404
|
-
output << table_to_csv(res) << "\n" if res
|
405
|
-
|
406
|
-
output
|
407
|
-
end
|
408
|
-
|
409
|
-
#Not yet implemented
|
410
|
-
def to_pdf
|
411
|
-
raise "PDF output is not yet supported."
|
412
|
-
end
|
413
|
-
|
414
|
-
def rails_version
|
415
|
-
return tracker.config[:rails_version] if tracker.config[:rails_version]
|
416
|
-
return "3.x" if tracker.options[:rails3]
|
417
|
-
"Unknown"
|
418
|
-
end
|
419
|
-
|
420
|
-
#Return header for HTML output. Uses CSS from tracker.options[:html_style]
|
421
|
-
def html_header
|
422
|
-
if File.exist? tracker.options[:html_style]
|
423
|
-
css = File.read tracker.options[:html_style]
|
424
|
-
else
|
425
|
-
raise "Cannot find CSS stylesheet for HTML: #{tracker.options[:html_style]}"
|
426
|
-
end
|
427
|
-
|
428
|
-
locals = {
|
429
|
-
:css => css,
|
430
|
-
:tracker => tracker,
|
431
|
-
:checks => checks,
|
432
|
-
:rails_version => rails_version,
|
433
|
-
:brakeman_version => Brakeman::Version
|
434
|
-
}
|
435
|
-
|
436
|
-
Brakeman::Report::Renderer.new('header', :locals => locals).render
|
437
|
-
end
|
438
|
-
|
439
|
-
#Generate header for text output
|
440
|
-
def text_header
|
441
|
-
<<-HEADER
|
442
|
-
|
443
|
-
+BRAKEMAN REPORT+
|
444
|
-
|
445
|
-
Application path: #{File.expand_path tracker.options[:app_path]}
|
446
|
-
Rails version: #{rails_version}
|
447
|
-
Brakeman version: #{Brakeman::Version}
|
448
|
-
Started at #{tracker.start_time}
|
449
|
-
Duration: #{tracker.duration} seconds
|
450
|
-
Checks run: #{checks.checks_run.sort.join(", ")}
|
451
|
-
HEADER
|
452
|
-
end
|
453
|
-
|
454
|
-
#Generate header for CSV output
|
455
|
-
def csv_header
|
456
|
-
header = CSV.generate_line(["Application Path", "Report Generation Time", "Checks Performed", "Rails Version"])
|
457
|
-
header << CSV.generate_line([File.expand_path(tracker.options[:app_path]), Time.now.to_s, checks.checks_run.sort.join(", "), rails_version])
|
458
|
-
"BRAKEMAN REPORT\n\n" + header
|
459
|
-
end
|
460
|
-
|
461
|
-
#Return summary of warnings in hash and store in @warnings_summary
|
462
|
-
def warnings_summary
|
463
|
-
return @warnings_summary if @warnings_summary
|
464
|
-
|
465
|
-
summary = Hash.new(0)
|
466
|
-
high_confidence_warnings = 0
|
467
|
-
|
468
|
-
[all_warnings].each do |warnings|
|
469
|
-
warnings.each do |warning|
|
470
|
-
summary[warning.warning_type.to_s] += 1
|
471
|
-
high_confidence_warnings += 1 if warning.confidence == 0
|
472
|
-
end
|
473
|
-
end
|
474
|
-
|
475
|
-
summary[:high_confidence] = high_confidence_warnings
|
476
|
-
@warnings_summary = summary
|
477
|
-
end
|
478
|
-
|
479
|
-
#Escape warning message and highlight user input in text output
|
480
|
-
def text_message warning, message
|
481
|
-
if @highlight_user_input and warning.user_input
|
482
|
-
user_input = warning.format_user_input
|
483
|
-
message.gsub(user_input, "+#{user_input}+")
|
484
|
-
else
|
485
|
-
message
|
486
|
-
end
|
487
|
-
end
|
488
|
-
|
489
|
-
#Escape warning message and highlight user input in HTML output
|
490
|
-
def html_message warning, message
|
491
|
-
message = CGI.escapeHTML(message)
|
492
|
-
|
493
|
-
if @highlight_user_input and warning.user_input
|
494
|
-
user_input = CGI.escapeHTML(warning.format_user_input)
|
495
|
-
message.gsub!(user_input, "<span class=\"user_input\">#{user_input}</span>")
|
496
|
-
end
|
497
|
-
|
498
|
-
message
|
499
|
-
end
|
500
|
-
|
501
|
-
#Generate HTML for warnings, including context show/hidden via Javascript
|
502
|
-
def with_context warning, message
|
503
|
-
context = context_for(@app_tree, warning)
|
504
|
-
full_message = nil
|
505
|
-
|
506
|
-
if tracker.options[:message_limit] and tracker.options[:message_limit] > 0 and message.length > tracker.options[:message_limit]
|
507
|
-
full_message = html_message(warning, message)
|
508
|
-
message = message[0..tracker.options[:message_limit]] << "..."
|
509
|
-
end
|
510
|
-
|
511
|
-
message = html_message(warning, message)
|
512
|
-
return message if context.empty? and not full_message
|
513
|
-
|
514
|
-
@element_id += 1
|
515
|
-
code_id = "context#@element_id"
|
516
|
-
message_id = "message#@element_id"
|
517
|
-
full_message_id = "full_message#@element_id"
|
518
|
-
alt = false
|
519
|
-
output = "<div class='warning_message' onClick=\"toggle('#{code_id}');toggle('#{message_id}');toggle('#{full_message_id}')\" >" <<
|
520
|
-
if full_message
|
521
|
-
"<span id='#{message_id}' style='display:block' >#{message}</span>" <<
|
522
|
-
"<span id='#{full_message_id}' style='display:none'>#{full_message}</span>"
|
523
|
-
else
|
524
|
-
message
|
525
|
-
end <<
|
526
|
-
"<table id='#{code_id}' class='context' style='display:none'>" <<
|
527
|
-
"<caption>#{warning_file(warning) || ''}</caption>"
|
528
|
-
|
529
|
-
unless context.empty?
|
530
|
-
if warning.line - 1 == 1 or warning.line + 1 == 1
|
531
|
-
error = " near_error"
|
532
|
-
elsif 1 == warning.line
|
533
|
-
error = " error"
|
534
|
-
else
|
535
|
-
error = ""
|
536
|
-
end
|
537
|
-
|
538
|
-
output << <<-HTML
|
539
|
-
<tr class='context first#{error}'>
|
540
|
-
<td class='context_line'>
|
541
|
-
<pre class='context'>#{context.first[0]}</pre>
|
542
|
-
</td>
|
543
|
-
<td class='context'>
|
544
|
-
<pre class='context'>#{CGI.escapeHTML context.first[1].chomp}</pre>
|
545
|
-
</td>
|
546
|
-
</tr>
|
547
|
-
HTML
|
548
|
-
|
549
|
-
if context.length > 1
|
550
|
-
output << context[1..-1].map do |code|
|
551
|
-
alt = !alt
|
552
|
-
if code[0] == warning.line - 1 or code[0] == warning.line + 1
|
553
|
-
error = " near_error"
|
554
|
-
elsif code[0] == warning.line
|
555
|
-
error = " error"
|
556
|
-
else
|
557
|
-
error = ""
|
558
|
-
end
|
559
|
-
|
560
|
-
<<-HTML
|
561
|
-
<tr class='context#{alt ? ' alt' : ''}#{error}'>
|
562
|
-
<td class='context_line'>
|
563
|
-
<pre class='context'>#{code[0]}</pre>
|
564
|
-
</td>
|
565
|
-
<td class='context'>
|
566
|
-
<pre class='context'>#{CGI.escapeHTML code[1].chomp}</pre>
|
567
|
-
</td>
|
568
|
-
</tr>
|
569
|
-
HTML
|
570
|
-
end.join
|
571
|
-
end
|
572
|
-
end
|
573
|
-
|
574
|
-
output << "</table></div>"
|
575
|
-
end
|
576
|
-
|
577
|
-
def with_link warning, message
|
578
|
-
"<a rel=\"no-referrer\" href=\"#{warning.link}\">#{message}</a>"
|
579
|
-
end
|
580
|
-
|
581
|
-
#Generated tab-separated output suitable for the Jenkins Brakeman Plugin:
|
582
|
-
#https://github.com/presidentbeef/brakeman-jenkins-plugin
|
583
|
-
def to_tabs
|
584
|
-
[[:warnings, "General"], [:controller_warnings, "Controller"],
|
585
|
-
[:model_warnings, "Model"], [:template_warnings, "Template"]].map do |meth, category|
|
586
|
-
|
587
|
-
checks.send(meth).map do |w|
|
588
|
-
line = w.line || 0
|
589
|
-
w.warning_type.gsub!(/[^\w\s]/, ' ')
|
590
|
-
"#{warning_file(w, :absolute)}\t#{line}\t#{w.warning_type}\t#{category}\t#{w.format_message}\t#{TEXT_CONFIDENCE[w.confidence]}"
|
591
|
-
end.join "\n"
|
592
|
-
|
593
|
-
end.join "\n"
|
594
|
-
end
|
595
|
-
|
596
|
-
def to_test
|
597
|
-
report = { :errors => tracker.errors,
|
598
|
-
:controllers => tracker.controllers,
|
599
|
-
:models => tracker.models,
|
600
|
-
:templates => tracker.templates
|
601
|
-
}
|
602
|
-
|
603
|
-
[:warnings, :controller_warnings, :model_warnings, :template_warnings].each do |meth|
|
604
|
-
report[meth] = @checks.send(meth)
|
605
|
-
report[meth].each do |w|
|
606
|
-
w.message = w.format_message
|
607
|
-
w.context = context_for(@app_tree, w).join("\n")
|
608
|
-
end
|
609
|
-
end
|
610
|
-
|
611
|
-
report[:config] = tracker.config
|
612
|
-
|
613
|
-
report
|
51
|
+
def require_report type
|
52
|
+
require "brakeman/report/report_#{type}"
|
614
53
|
end
|
615
54
|
|
616
55
|
def to_json
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
warnings = all_warnings.map do |w|
|
621
|
-
hash = w.to_hash
|
622
|
-
hash[:file] = warning_file w
|
623
|
-
hash
|
624
|
-
end.sort_by { |w| w[:file] }
|
625
|
-
|
626
|
-
scan_info = {
|
627
|
-
:app_path => File.expand_path(tracker.options[:app_path]),
|
628
|
-
:rails_version => rails_version,
|
629
|
-
:security_warnings => all_warnings.length,
|
630
|
-
:start_time => tracker.start_time.to_s,
|
631
|
-
:end_time => tracker.end_time.to_s,
|
632
|
-
:duration => tracker.duration,
|
633
|
-
:checks_performed => checks.checks_run.sort,
|
634
|
-
:number_of_controllers => tracker.controllers.length,
|
635
|
-
# ignore the "fake" model
|
636
|
-
:number_of_models => tracker.models.length - 1,
|
637
|
-
:number_of_templates => number_of_templates(@tracker),
|
638
|
-
:ruby_version => RUBY_VERSION,
|
639
|
-
:brakeman_version => Brakeman::Version
|
640
|
-
}
|
641
|
-
|
642
|
-
report_info = {
|
643
|
-
:scan_info => scan_info,
|
644
|
-
:warnings => warnings,
|
645
|
-
:errors => errors
|
646
|
-
}
|
647
|
-
|
648
|
-
MultiJson.dump(report_info, :pretty => true)
|
56
|
+
require_report 'json'
|
57
|
+
generate Brakeman::Report::JSON
|
649
58
|
end
|
650
59
|
|
651
|
-
def
|
652
|
-
|
60
|
+
def to_s
|
61
|
+
require_report 'table'
|
62
|
+
generate Brakeman::Report::Table
|
653
63
|
end
|
654
64
|
|
655
|
-
def
|
656
|
-
|
65
|
+
def generate reporter
|
66
|
+
reporter.new(@app_tree, @tracker).generate_report
|
657
67
|
end
|
658
|
-
|
659
|
-
def warning_file warning, absolute = @tracker.options[:absolute_paths]
|
660
|
-
return nil if warning.file.nil?
|
661
|
-
|
662
|
-
if absolute
|
663
|
-
warning.file
|
664
|
-
else
|
665
|
-
relative_path warning.file
|
666
|
-
end
|
667
|
-
end
|
668
|
-
|
669
68
|
end
|