提交 36670f57 编写于 作者: J Justin Collins

Add ability to ignore warnings

上级 791e3ede
......@@ -290,8 +290,11 @@ module Brakeman
else
notify "Runnning checks..."
end
tracker.run_checks
self.filter_warnings tracker, options
if options[:output_files]
notify "Generating report..."
......@@ -384,6 +387,34 @@ module Brakeman
end
end
def self.filter_warnings tracker, options
require 'brakeman/report/ignore/config'
app_tree = Brakeman::AppTree.from_options(options)
if options[:ignore_file]
file = options[:ignore_file]
elsif app_tree.exists? "config/brakeman.ignore"
file = app_tree.expand_path("config/brakeman.ignore")
elsif not options[:interactive_ignore]
return
end
notify "Filtering warnings..."
if options[:interactive_ignore]
require 'brakeman/report/ignore/interactive'
config = InteractiveIgnorer.new(file, tracker.warnings).start
else
notify "[Notice] Using '#{file}' to filter warnings"
config = IgnoreConfig.new(file, tracker.warnings)
config.read_from_file
config.filter_ignored
end
tracker.ignored_filter = config
end
class DependencyError < RuntimeError; end
class RakeInstallError < RuntimeError; end
class NoBrakemanError < RuntimeError; end
......
......@@ -153,6 +153,14 @@ module Brakeman::Options
options[:html_style] = File.expand_path file
end
opts.on "-i IGNOREFILE", "--ignore-config IGNOREFILE", "Use configuration to ignore warnings" do |file|
options[:ignore_file] = file
end
opts.on "-I", "--interactive-ignore", "Interactively ignore warnings" do
options[:interactive_ignore] = true
end
opts.on "-l", "--[no-]combine-locations", "Combine warning locations (Default)" do |combine|
options[:combine_locations] = combine
end
......
......@@ -8,9 +8,10 @@ class Brakeman::Report
VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s]
def initialize(app_tree, tracker)
def initialize app_tree, tracker, ignored = nil
@app_tree = app_tree
@tracker = tracker
@ignored = ignored
end
def format format
......@@ -63,6 +64,10 @@ class Brakeman::Report
end
def generate reporter
reporter.new(@app_tree, @tracker).generate_report
reporter.new(@app_tree, @tracker, @ignored).generate_report
end
def ignored_warning? warning
@ignore_warnings and @ignorer.ignored? warning
end
end
require 'set'
require 'multi_json'
module Brakeman
class IgnoreConfig
attr_reader :shown_warnings, :ignored_warnings
attr_accessor :file
def initialize file, new_warnings
@file = file
@new_warnings = new_warnings
@already_ignored = []
@ignored_fingerprints = Set.new
@notes = {}
@shown_warnings = @ignored_warnings = nil
end
# Populate ignored_warnings and shown_warnings based on ignore
# configuration
def filter_ignored
@shown_warnings = []
@ignored_warnings = []
@new_warnings.each do |w|
if ignored? w
@ignored_warnings << w
else
@shown_warnings << w
end
end
@shown_warnings
end
# Remove warning from ignored list
def unignore warning
@ignored_fingerprints.delete warning.fingerprint
@already_ignored.reject! do |w|
w[:fingerprint] == warning.fingerprint
end
end
# Determine if warning should be ignored
def ignored? warning
@ignored_fingerprints.include? warning.fingerprint
end
def ignore warning
@ignored_fingerprints << warning.fingerprint
end
# Add note for warning
def add_note warning, note
@notes[warning.fingerprint] = note
end
# Retrieve note for warning if it exists. Returns nil if no
# note is found
def note_for warning
if warning.is_a? Warning
fingerprint = warning.fingerprint
else
fingerprint = warning[:fingerprint]
end
@already_ignored.each do |w|
if fingerprint == w[:fingerprint]
return w[:note]
end
end
nil
end
# Read configuration to file
def read_from_file file = @file
if File.exist? file
@already_ignored = MultiJson.load(File.read(file), :symbolize_keys => true)[:ignored_warnings]
else
Brakeman.notify "[Notice] Could not find ignore configuration in #{file}"
@already_ignored = []
end
@already_ignored.each do |w|
@ignored_fingerprints << w[:fingerprint]
@notes[w[:fingerprint]] = w[:note]
end
end
# Save configuration to file
def save_to_file warnings, file = @file
warnings = warnings.map do |w|
if w.is_a? Warning
w_hash = w.to_hash
w_hash[:file] = w.relative_path
w = w_hash
end
w[:note] = @notes[w[:fingerprint]] || ""
w
end
output = {
:ignored_warnings => warnings,
:updated => Time.now.to_s,
:brakeman_version => Brakeman::Version
}
File.open file, "w" do |f|
f.puts MultiJson.dump(output, :pretty => true)
end
end
# Save old ignored warnings and newly ignored ones
def save_with_old
warnings = @ignored_warnings.dup
# Only add ignored warnings not already ignored
@already_ignored.each do |w|
fingerprint = w[:fingerprint]
unless @ignored_warnings.find { |w| w.fingerprint == fingerprint }
warnings << w
end
end
save_to_file warnings
end
end
end
Brakeman.load_dependency 'highline'
module Brakeman
class InteractiveIgnorer
def initialize file, warnings
@ignore_config = Brakeman::IgnoreConfig.new(file, warnings)
@new_warnings = warnings
@skip_ignored = false
@skip_rest = false
@ignore_rest = false
@quit = false
@restart = false
end
def start
file_menu
initial_menu
@ignore_config.filter_ignored
unless @quit
final_menu
end
if @restart
@restart = false
start
end
@ignore_config
end
private
def file_menu
loop do
@ignore_config.file = HighLine.new.ask "Input file: " do |q|
if @ignore_config.file and not @ignore_config.file.empty?
q.default = @ignore_config.file
else
q.default = "config/brakeman.ignore"
end
end
if File.exist? @ignore_config.file
@ignore_config.read_from_file
return
else
if yes_or_no "No such file. Continue with empty config? "
return
end
end
end
end
def initial_menu
HighLine.new.choose do |m|
m.choice "Inspect all warnings" do
@skip_ignored = false
process_warnings
end
m.choice "Hide previously ignored warnings" do
@skip_ignored = true
process_warnings
end
m.choice "Skip - use current ignore configuration" do
@quit = true
@ignore_config.filter_ignored
end
end
end
def warning_menu
HighLine.new.choose do |m|
m.prompt = "Action: "
m.layout = :one_line
m.list_option = ", "
m.select_by = :name
m.choice "i"
m.choice "n"
m.choice "k"
m.choice "u"
m.choice "a"
m.choice "s"
m.choice "q"
m.choice "?" do
say <<-HELP
i - Add warning to ignore list
n - Add warning to ignore list and add note
s - Skip this warning (will remain ignored or shown)
u - Remove this warning from ignore list
a - Ignore this warning and all remaining warnings
k - Skip this warning and all remaining warnings
q - Quit, do not update ignored warnings
? - Display this help
HELP
"?"
end
end
end
def final_menu
summarize_changes
HighLine.new.choose do |m|
m.choice "Save changes" do
save
end
m.choice "Start over" do
start_over
end
m.choice "Quit, do not save changes" do
quit
end
end
end
def save
@ignore_config.file = HighLine.new.ask "Output file: " do |q|
if @ignore_config.file and not @ignore_config.file.empty?
q.default = @ignore_config.file
else
q.default = "config/brakeman.ignore"
end
end
@ignore_config.save_with_old
end
def start_over
reset_config
@restart = true
end
def reset_config
@ignore_config = Brakeman::IgnoreConfig.new(@ignore_config.file, @new_warnings)
end
def process_warnings
@new_warnings.each do |w|
if skip_ignored? w or @skip_rest
next
elsif @ignore_rest
ignore w
elsif @quit or @restart
return
else
ask_about w
end
end
end
def ask_about warning
pretty_display warning
warning_action warning_menu, warning
end
def warning_action action, warning
case action
when "i"
ignore warning
when "n"
ignore_and_note warning
when "s"
# do nothing
when "u"
unignore warning
when "a"
ignore_rest warning
when "k"
skip_rest warning
when "q"
quit
when "?"
ask_about warning
else
raise "Unexpected action"
end
end
def ignore warning
@ignore_config.ignore warning
end
def ignore_and_note warning
note = HighLine.new.ask("Note: ")
@ignore_config.ignore warning
@ignore_config.add_note warning, note
end
def unignore warning
@ignore_config.unignore warning
end
def skip_rest warning
@skip_rest = true
end
def ignore_rest warning
ignore warning
@ignore_rest = true
end
def quit
puts "REST CONFIG?!"
reset_config
@ignore_config.read_from_file
@ignore_config.filter_ignored
@quit = true
end
def pretty_display warning
say "-" * 20
show_confidence warning
label "Category"
say warning.warning_type
label "Message"
say warning.message
if warning.code
label "Code"
say warning.format_code
end
if warning.relative_path
label "File"
say warning.relative_path
end
if warning.line
label "Line"
say warning.line
end
if already_ignored? warning
show_note warning
say "Already ignored", :red
end
say ""
end
def already_ignored? warning
@ignore_config.ignored? warning
end
def skip_ignored? warning
@skip_ignored and already_ignored? warning
end
def summarize_changes
say "-" * 20
say "Ignoring #{@ignore_config.ignored_warnings.length} warnings", :yellow
say "Showing #{@ignore_config.shown_warnings.length} warnings", :green
end
def label name
say "#{name}: ", :green
end
def show_confidence warning
label "Confidence"
case warning.confidence
when 0
say "High", :red
when 1
say "Medium", :yellow
when 2
say "Weak", :cyan
else
say "Unknown"
end
end
def show_note warning
note = @ignore_config.note_for warning
if note
label "Note"
say note
end
end
def say text, color = nil
text = text.to_s
if color
HighLine.new.say HighLine.new.color(text, color)
else
HighLine.new.say text
end
end
def yes_or_no message
answer = HighLine.new.ask message do |q|
q.in = ["y", "n", "yes", "no"]
end
answer.match /^y/i
end
end
end
......@@ -12,10 +12,11 @@ class Brakeman::Report::Base
TEXT_CONFIDENCE = [ "High", "Medium", "Weak" ]
def initialize app_tree, tracker
def initialize app_tree, tracker, ignore_filter = nil
@app_tree = app_tree
@tracker = tracker
@checks = tracker.checks
@ignore_filter = ignore_filter
@highlight_user_input = tracker.options[:highlight_user_input]
@warnings_summary = nil
end
......@@ -79,7 +80,7 @@ class Brakeman::Report::Base
end
def generate_warnings
render_warnings checks.warnings,
render_warnings generic_warnings,
:warning,
'security_warnings',
["Confidence", "Class", "Method", "Warning Type", "Message"],
......@@ -88,7 +89,7 @@ class Brakeman::Report::Base
#Generate table of template warnings or return nil if no warnings
def generate_template_warnings
render_warnings checks.template_warnings,
render_warnings template_warnings,
:template,
'view_warnings',
['Confidence', 'Template', 'Warning Type', 'Message'],
......@@ -98,7 +99,7 @@ class Brakeman::Report::Base
#Generate table of model warnings or return nil if no warnings
def generate_model_warnings
render_warnings checks.model_warnings,
render_warnings model_warnings,
:model,
'model_warnings',
['Confidence', 'Model', 'Warning Type', 'Message'],
......@@ -107,13 +108,21 @@ class Brakeman::Report::Base
#Generate table of controller warnings or nil if no warnings
def generate_controller_warnings
render_warnings checks.controller_warnings,
render_warnings controller_warnings,
:controller,
'controller_warnings',
['Confidence', 'Controller', 'Warning Type', 'Message'],
'Controller'
end
def generate_ignored_warnings
render_warnings ignored_warnings,
:ignored,
'ignored_warnings',
['Confidence', 'Warning Type', 'File', 'Message'],
'Warning Type'
end
def render_warnings warnings, type, template, cols, sort_col
unless warnings.empty?
rows = sort(convert_to_rows(warnings, type), sort_col)
......@@ -141,6 +150,8 @@ class Brakeman::Report::Base
convert_model_warning w, warning
when :controller
convert_controller_warning w, warning
when :ignored
convert_ignored_warning w, warning
end
end
end
......@@ -163,6 +174,9 @@ class Brakeman::Report::Base
convert_warning warning, original
end
def convert_ignored_warning warning, original
convert_warning warning, original
end
def sort rows, sort_col
stabilizer = 0
......@@ -192,7 +206,45 @@ class Brakeman::Report::Base
end
def all_warnings
@all_warnings ||= @tracker.warnings
if @ignore_filter
@all_warnings ||= @ignore_filter.shown_warnings
else
@all_warnings ||= tracker.checks.all_warnings
end
end
def filter_warnings warnings
if @ignore_filter
warnings.reject do |w|
@ignore_filter.ignored? w
end
else
warnings
end
end
def generic_warnings
filter_warnings tracker.checks.warnings
end
def template_warnings
filter_warnings tracker.checks.template_warnings
end
def model_warnings
filter_warnings tracker.checks.model_warnings
end
def controller_warnings
filter_warnings tracker.checks.controller_warnings
end
def ignored_warnings
if @ignore_filter
@ignore_filter.ignored_warnings
else
[]
end
end
def number_of_templates tracker
......
......@@ -7,8 +7,8 @@ class Brakeman::Report::Hash < Brakeman::Report::Base
:templates => tracker.templates
}
[:warnings, :controller_warnings, :model_warnings, :template_warnings].each do |meth|
report[meth] = @checks.send(meth)
[:generic_warnings, :controller_warnings, :model_warnings, :template_warnings].each do |meth|
report[meth] = self.send(meth)
report[meth].each do |w|
w.message = w.format_message
w.context = context_for(@app_tree, w).join("\n")
......
......@@ -26,6 +26,7 @@ class Brakeman::Report::HTML < Brakeman::Report::Base
out << generate_controller_warnings.to_s
out << generate_model_warnings.to_s
out << generate_template_warnings.to_s
out << generate_ignored_warnings.to_s
out << "</body></html>"
end
......@@ -35,6 +36,7 @@ class Brakeman::Report::HTML < Brakeman::Report::Base
:warnings => all_warnings.length,
:warnings_summary => warnings_summary,
:number_of_templates => number_of_templates(@tracker),
:ignored_warnings => ignored_warnings.length
}
Brakeman::Report::Renderer.new('overview', :locals => locals).render
......@@ -59,7 +61,6 @@ class Brakeman::Report::HTML < Brakeman::Report::Base
Brakeman::Report::Renderer.new('template_overview', :locals => {:template_rows => template_rows}).render
end
def render_array template, headings, value_array, locals
return if value_array.empty?
......@@ -73,7 +74,6 @@ class Brakeman::Report::HTML < Brakeman::Report::Base
warning
end
def with_link warning, message
"<a rel=\"no-referrer\" href=\"#{warning.link}\">#{message}</a>"
end
......@@ -87,6 +87,13 @@ class Brakeman::Report::HTML < Brakeman::Report::Base
warning
end
def convert_ignored_warning warning, original
warning = convert_warning(warning, original)
warning['File'] = original.relative_path
warning['Note'] = CGI.escapeHTML(@ignore_filter.note_for(original) || "")
warning
end
#Return header for HTML output. Uses CSS from tracker.options[:html_style]
def html_header
if File.exist? tracker.options[:html_style]
......
......@@ -6,11 +6,9 @@ class Brakeman::Report::JSON < Brakeman::Report::Base
errors = tracker.errors.map{|e| { :error => e[:error], :location => e[:backtrace][0] }}
app_path = tracker.options[:app_path]
warnings = all_warnings.map do |w|
hash = w.to_hash
hash[:file] = warning_file w
hash
end.sort_by { |w| w[:file] }
warnings = convert_to_hashes all_warnings
ignored = convert_to_hashes ignored_warnings
scan_info = {
:app_path => File.expand_path(tracker.options[:app_path]),
......@@ -31,9 +29,18 @@ class Brakeman::Report::JSON < Brakeman::Report::Base
report_info = {
:scan_info => scan_info,
:warnings => warnings,
:ignored_warnings => ignored,
:errors => errors
}
MultiJson.dump(report_info, :pretty => true)
end
def convert_to_hashes warnings
warnings.map do |w|
hash = w.to_hash
hash[:file] = warning_file w
hash
end.sort_by { |w| w[:file] }
end
end
......@@ -48,6 +48,7 @@ class Brakeman::Report::Table < Brakeman::Report::Base
t.add_row ['Templates', number_of_templates(@tracker)]
t.add_row ['Errors', tracker.errors.length]
t.add_row ['Security Warnings', "#{num_warnings} (#{warnings_summary[:high_confidence]})"]
t.add_row ['Ignored Warnings', ignored_warnings.length] unless ignored_warnings.empty?
end
end
......
<div onClick="toggle('ignored_table');"> <h2><%= warnings.length %> Ignored Warnings (click to see them)</h2 ></div>
<div>
<table style="display:none" id="ignored_table">
<tr>
<th>Confidence</th>
<th>File</th>
<th>Warning Type</th>
<th>Message</th>
<th>Note</th>
</tr>
<% warnings.each do |warning| %>
<tr>
<td><%= warning['Confidence']%></td>
<td><%= warning['File']%></td>
<td><%= warning['Warning Type']%></td>
<td><%= warning['Message']%></td>
<td><%= warning['Note']%></td>
</tr>
<% end %>
</table>
</div>
......@@ -24,5 +24,11 @@
<td>Security Warnings</td>
<td><%= warnings %> <span class='high-confidence'>(<%= warnings_summary[:high_confidence] %>)</span></td>
</tr>
<% if warnings_summary['Ignored Warnings'] %>
<tr>
<td>Ignored Warnings</td>
<td><%= ignored_warnings %></td>
</tr>
<% end %>
</table>
<br>
......@@ -10,7 +10,7 @@ class Brakeman::Tracker
attr_accessor :controllers, :templates, :models, :errors,
:checks, :initializers, :config, :routes, :processor, :libs,
:template_cache, :options, :filter_cache, :start_time, :end_time,
:duration
:duration, :ignored_filter
#Place holder when there should be a model, but it is not
#clear what model it will be.
......@@ -148,8 +148,8 @@ class Brakeman::Tracker
end
#Returns a Report with this Tracker's information
def report
Brakeman::Report.new(@app_tree, self)
def report filter = nil
Brakeman::Report.new(@app_tree, self, filter || @ignored_filter)
end
def warnings
......
......@@ -65,7 +65,7 @@ module BrakemanTester::FindWarning
def find opts = {}, &block
t = opts[:type]
if t.nil? or t == :warning
warnings = report[:warnings]
warnings = report[:generic_warnings]
else
warnings = report[(t.to_s << "_warnings").to_sym]
end
......
......@@ -13,7 +13,7 @@ class JSONOutputTests < Test::Unit::TestCase
end
def test_for_expected_keys
assert (@json.keys - ["warnings", "scan_info", "errors"]).empty?
assert (@json.keys - ["warnings", "ignored_warnings", "scan_info", "errors"]).empty?
end
def test_for_scan_info_keys
......
......@@ -11,11 +11,11 @@ class OnlyFilesOptionTests < Test::Unit::TestCase
:controller => 0,
:model => 0,
:template => 1,
:warning => 4 }
:generic => 4 }
if RUBY_PLATFORM == 'java'
@expected[:warning] += 1
@expected[:generic] += 1
end
@expected
......
......@@ -12,13 +12,13 @@ class Rails2Tests < Test::Unit::TestCase
:controller => 1,
:model => 3,
:template => 45,
:warning => 46 }
:generic => 46 }
else
@expected ||= {
:controller => 1,
:model => 3,
:template => 45,
:warning => 47 }
:generic => 47 }
end
end
......@@ -1209,13 +1209,13 @@ class Rails2WithOptionsTests < Test::Unit::TestCase
:controller => 1,
:model => 4,
:template => 45,
:warning => 46 }
:generic => 46 }
else
@expected ||= {
:controller => 1,
:model => 4,
:template => 45,
:warning => 47 }
:generic => 47 }
end
end
......
......@@ -16,11 +16,11 @@ class Rails3Tests < Test::Unit::TestCase
:controller => 1,
:model => 8,
:template => 38,
:warning => 63
:generic => 63
}
if RUBY_PLATFORM == 'java'
@expected[:warning] += 1
@expected[:generic] += 1
end
@expected
......
......@@ -15,7 +15,7 @@ class Rails31Tests < Test::Unit::TestCase
:model => 3,
:template => 22,
:controller => 4,
:warning => 72 }
:generic => 72 }
end
def test_without_protection
......
......@@ -11,11 +11,11 @@ class Rails32Tests < Test::Unit::TestCase
:controller => 0,
:model => 0,
:template => 11,
:warning => 7 }
:generic => 7 }
if RUBY_PLATFORM == 'java'
@expected[:warning] += 1
@expected[:generic] += 1
end
@expected
......
......@@ -15,12 +15,12 @@ class Rails4Tests < Test::Unit::TestCase
:controller => 0,
:model => 0,
:template => 0,
:warning => 1
:generic => 1
}
end
def test_session_secret_token
assert_warning :type => :warning,
assert_warning :type => :generic,
:warning_type => "Session Setting",
:fingerprint => "715ad9c0d76f57a6a657192574d528b620176a80fec969e2f63c88eacab0b984",
:line => 12,
......
......@@ -11,7 +11,7 @@ class RailsWithXssPluginTests < Test::Unit::TestCase
:controller => 1,
:model => 3,
:template => 2,
:warning => 20 }
:generic => 20 }
end
def report
......@@ -285,7 +285,7 @@ class RailsWithXssPluginTests < Test::Unit::TestCase
end
def test_absolute_paths
assert report[:warnings].all? { |w| w.file.start_with? "/" }
assert report[:generic_warnings].all? { |w| w.file.start_with? "/" }
end
def test_sql_injection_CVE_2013_0155
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册