diff --git a/lib/bundler/audit/cli.rb b/lib/bundler/audit/cli.rb index 5f496f11..d1c12966 100644 --- a/lib/bundler/audit/cli.rb +++ b/lib/bundler/audit/cli.rb @@ -45,6 +45,7 @@ class CLI < ::Thor method_option :gemfile_lock, type: :string, aliases: '-G', default: 'Gemfile.lock' method_option :output, type: :string, aliases: '-o' + method_option :strict_ignore, type: :boolean, default: false def check(dir=Dir.pwd) unless File.directory?(dir) @@ -86,6 +87,7 @@ def check(dir=Dir.pwd) output.close if options[:output] exit(1) if report.vulnerable? + exit(1) if options[:strict_ignore] && report.unseen_ignored_identifiers? end desc 'stats', 'Prints ruby-advisory-db stats' diff --git a/lib/bundler/audit/cli/formats/text.rb b/lib/bundler/audit/cli/formats/text.rb index 966544a4..20bc7d72 100644 --- a/lib/bundler/audit/cli/formats/text.rb +++ b/lib/bundler/audit/cli/formats/text.rb @@ -47,6 +47,10 @@ def print_report(report,output=$stdout) end end + if report.unseen_ignored_identifiers? + print_warning "These identifiers were ignored but not found: #{report.unseen_ignored_identifiers.to_a.join(', ')}" + end + if report.vulnerable? say "Vulnerabilities found!", :red else diff --git a/lib/bundler/audit/report.rb b/lib/bundler/audit/report.rb index 51847f5f..117465c9 100644 --- a/lib/bundler/audit/report.rb +++ b/lib/bundler/audit/report.rb @@ -37,6 +37,16 @@ class Report # @return [Array] attr_reader :unpatched_gems + # Identifiers of all seen vulnerabilities (including ignored vulns). + # + # @return [Set] + attr_accessor :seen_identifiers + + # Identifiers that were omitted from results + # + # @return [Set] + attr_accessor :ignored_identifiers + # # Initializes the report. # @@ -49,6 +59,8 @@ def initialize(results=[]) @results = [] @insecure_sources = [] @unpatched_gems = [] + @seen_identifiers = Set.new + @ignored_identifiers = Set.new results.each { |result| self << result } end @@ -133,6 +145,14 @@ def vulnerable_gems @unpatched_gems.map(&:gem) end + def unseen_ignored_identifiers? + unseen_ignored_identifiers.any? + end + + def unseen_ignored_identifiers + @ignored_identifiers - @seen_identifiers + end + # # @return [Hash{Symbol => Object}] # diff --git a/lib/bundler/audit/scanner.rb b/lib/bundler/audit/scanner.rb index 731a93ee..c520dc0a 100644 --- a/lib/bundler/audit/scanner.rb +++ b/lib/bundler/audit/scanner.rb @@ -121,6 +121,13 @@ def report(options={}) yield result if block_given? end + report.seen_identifiers = @seen + report.ignored_identifiers = if options[:ignore] + Set.new(options[:ignore]) + else + config.ignore + end + return report end @@ -222,9 +229,11 @@ def scan_specs(options={}) else config.ignore end + @seen = Set.new @lockfile.specs.each do |gem| @database.check_gem(gem) do |advisory| + @seen.merge(advisory.identifiers) is_ignored = ignore.intersect?(advisory.identifiers.to_set) next if is_ignored diff --git a/spec/cli/formats/text_spec.rb b/spec/cli/formats/text_spec.rb index 88f5de8e..56a30c25 100644 --- a/spec/cli/formats/text_spec.rb +++ b/spec/cli/formats/text_spec.rb @@ -266,6 +266,19 @@ end end + context "when there are unseen ignored identifiers" do + let(:report) do + Bundler::Audit::Report.new.tap do |r| + r.ignored_identifiers = Set.new(unseen) + end + end + let(:unseen) { %w[CVE-2020-8833 OSVDB-108664] } + + it "must print the unseen identifiers" do + expect(output_lines).to include("These identifiers were ignored but not found: #{unseen.join(', ')}") + end + end + it "must restore $stdout" do expect($stdout).to_not be(stdout) end diff --git a/spec/cli_spec.rb b/spec/cli_spec.rb index c240b02d..0e47a1e7 100644 --- a/spec/cli_spec.rb +++ b/spec/cli_spec.rb @@ -175,4 +175,25 @@ end end end + + describe "#check" do + context "--strict-ignore" do + let(:directory) { File.join("spec", "bundle", "secure") } + + subject do + described_class.new([], strict_ignore: true, ignore: ["CVE-2006-1982"], format: "text", database: File.expand_path("fixtures/database/", File.dirname(__FILE__)), gemfile_lock: "Gemfile.lock", config: ".bundler-audit.yml") + end + + context "when the ignored CVE is not found" do + it "exits with an error status code" do + expect { + subject.check(directory) + }.to raise_error(SystemExit) do |error| + expect(error.success?).to eq(false) + expect(error.status).to eq(1) + end + end + end + end + end end diff --git a/spec/scanner_spec.rb b/spec/scanner_spec.rb index 6c742a77..668a2f33 100644 --- a/spec/scanner_spec.rb +++ b/spec/scanner_spec.rb @@ -36,12 +36,12 @@ end context "when the :ignore option is given" do - subject { super().scan(ignore: ['OSVDB-89026']) } + subject { super().scan(ignore: ['OSVDB-89025']) } it "should ignore the specified advisories" do ids = subject.map { |result| result.advisory.id } - expect(ids).not_to include('OSVDB-89026') + expect(ids).not_to include('OSVDB-89025') end end end @@ -116,6 +116,29 @@ expect(report.results).to all(be_kind_of(Bundler::Audit::Results::Result)) end + it "should return a Report containing all identifiers seen during scanning" do + report = subject.report + expected_identifiers = %w[CVE-2013-0155 CVE-2013-0276 CVE-2013-1854 CVE-2013-1856 CVE-2014-3482 CVE-2015-3227 CVE-2015-7577 OSVDB-108664 OSVDB-89025 OSVDB-90072 OSVDB-91451 OSVDB-91453] + + expect(report.seen_identifiers).to contain_exactly(*expected_identifiers) + end + + context "when some identifiers are ignored" do + it "should return a Report containing the seen but ignored identifiers" do + ignored_identifiers = %w[CVE-2013-0155 OSVDB-108664] + report = subject.report(ignore: ignored_identifiers) + + expect(report.seen_identifiers).to include(*ignored_identifiers) + end + + it "should return a Report listing the ignored identifiers" do + ignored_identifiers = %w[CVE-2013-0155 OSVDB-108664] + report = subject.report(ignore: ignored_identifiers) + + expect(report.ignored_identifiers).to contain_exactly(*ignored_identifiers) + end + end + context "when given a block" do it "should yield results" do results = []