From 26b97e0b863b1bb1de1ea0025436ea9380ddeabb Mon Sep 17 00:00:00 2001 From: fatkodima Date: Thu, 29 Aug 2019 18:14:39 +0300 Subject: [PATCH 1/2] Allow passing Gemfile.lock paths to bundler audit --- lib/bundler/audit/cli.rb | 56 ++++++++++++++++++++++++------------ lib/bundler/audit/scanner.rb | 47 +++++++++++++----------------- spec/integration_spec.rb | 21 +++++++++++++- spec/scanner_spec.rb | 2 +- 4 files changed, 79 insertions(+), 47 deletions(-) diff --git a/lib/bundler/audit/cli.rb b/lib/bundler/audit/cli.rb index 8461404f..7984aaf4 100644 --- a/lib/bundler/audit/cli.rb +++ b/lib/bundler/audit/cli.rb @@ -29,35 +29,24 @@ class CLI < ::Thor default_task :check map '--version' => :version - desc 'check', 'Checks the Gemfile.lock for insecure dependencies' + desc 'check [lockfile ...]', 'Checks specified lockfiles (the default is Gemfile.lock) for insecure dependencies' method_option :quiet, :type => :boolean, :aliases => '-q' method_option :verbose, :type => :boolean, :aliases => '-v' method_option :ignore, :type => :array, :aliases => '-i' method_option :update, :type => :boolean, :aliases => '-u' - def check + def check(*lockfiles) update if options[:update] - scanner = Scanner.new + lockfiles << 'Gemfile.lock' if lockfiles.empty? + scanner = Scanner.new(Dir.pwd, :ignore => options.ignore) vulnerable = false - scanner.scan(:ignore => options.ignore) do |result| - vulnerable = true - - case result - when Scanner::InsecureSource - print_warning "Insecure Source URI found: #{result.source}" - when Scanner::UnpatchedGem - print_advisory result.gem, result.advisory - end + lockfiles.each do |lockfile| + vulnerable = check_lockfile(lockfile, scanner) || vulnerable end - if vulnerable - say "Vulnerabilities found!", :red - exit 1 - else - say("No vulnerabilities found", :green) unless options.quiet? - end + exit 1 if vulnerable end desc 'update', 'Updates the ruby-advisory-db' @@ -90,6 +79,32 @@ def version protected + def check_lockfile(lockfile, scanner) + print_header(lockfile) + + vulnerable = false + + scanner.scan(lockfile) do |result| + vulnerable = true + + case result + when Scanner::InsecureSource + print_warning "Insecure Source URI found: #{result.source}" + when Scanner::UnpatchedGem + print_advisory result.gem, result.advisory + end + end + + if vulnerable + say("Vulnerabilities found!", :red) + else + say("No vulnerabilities found", :green) unless options.quiet? + end + say + + vulnerable + end + def say(message="", color=nil) color = nil unless $stdout.tty? super(message.to_s, color) @@ -99,6 +114,11 @@ def print_warning(message) say message, :yellow end + def print_header(lockfile) + say lockfile, :bold + say + end + def print_advisory(gem, advisory) say "Name: ", :red say gem.name diff --git a/lib/bundler/audit/scanner.rb b/lib/bundler/audit/scanner.rb index 145a0dea..03ae772c 100644 --- a/lib/bundler/audit/scanner.rb +++ b/lib/bundler/audit/scanner.rb @@ -25,26 +25,19 @@ class Scanner # Project root directory attr_reader :root - # The parsed `Gemfile.lock` from the project - # - # @return [Bundler::LockfileParser] - attr_reader :lockfile - # # Initializes a scanner. # # @param [String] root # The path to the project root. # - # @param [String] gemfile_lock + # @param [String] lockfile # Alternative name for the `Gemfile.lock` file. # - def initialize(root=Dir.pwd,gemfile_lock='Gemfile.lock') + def initialize(root=Dir.pwd, options={}) @root = File.expand_path(root) + @ignore = (options[:ignore] || Set.new).to_set @database = Database.new - @lockfile = LockfileParser.new( - File.read(File.join(@root,gemfile_lock)) - ) end # @@ -65,14 +58,11 @@ def initialize(root=Dir.pwd,gemfile_lock='Gemfile.lock') # @return [Enumerator] # If no block is given, an Enumerator will be returned. # - def scan(options={},&block) - return enum_for(__method__,options) unless block - - ignore = Set[] - ignore += options[:ignore] if options[:ignore] + def scan(lockfile='Gemfile.lock', &block) + return enum_for(__method__, lockfile) unless block - scan_sources(options,&block) - scan_specs(options,&block) + scan_sources(lockfile, &block) + scan_specs(lockfile, &block) return self end @@ -96,10 +86,10 @@ def scan(options={},&block) # # @since 0.4.0 # - def scan_sources(options={}) - return enum_for(__method__,options) unless block_given? + def scan_sources(lockfile='Gemfile.lock') + return enum_for(__method__, lockfile) unless block_given? - @lockfile.sources.map do |source| + gems(lockfile).sources.map do |source| case source when Source::Git case source.uri @@ -140,15 +130,12 @@ def scan_sources(options={}) # # @since 0.4.0 # - def scan_specs(options={}) - return enum_for(__method__,options) unless block_given? + def scan_specs(lockfile='Gemfile.lock') + return enum_for(__method__, lockfile) unless block_given? - ignore = Set[] - ignore += options[:ignore] if options[:ignore] - - @lockfile.specs.each do |gem| + gems(lockfile).specs.each do |gem| @database.check_gem(gem) do |advisory| - is_ignored = ignore.intersect?(advisory.identifiers.to_set) + is_ignored = @ignore.intersect?(advisory.identifiers.to_set) next if is_ignored yield UnpatchedGem.new(gem,advisory) @@ -208,6 +195,12 @@ def internal_host?(host) def internal_ip?(ip) INTERNAL_SUBNETS.any? { |subnet| subnet.include?(ip) } end + + def gems(lockfile) + LockfileParser.new( + File.read(File.join(root, lockfile)) + ) + end end end end diff --git a/spec/integration_spec.rb b/spec/integration_spec.rb index ce664a48..65ad0ef8 100644 --- a/spec/integration_spec.rb +++ b/spec/integration_spec.rb @@ -33,6 +33,25 @@ end end + context "when auditing multiple bundles" do + let(:bundle) { 'unpatched_gems' } + let(:directory) { File.join('spec','bundle',bundle) } + let(:command) do + File.expand_path(File.join(File.dirname(__FILE__),'..','bin','bundler-audit check Gemfile.lock ../insecure_sources/Gemfile.lock')) + end + + subject do + Dir.chdir(directory) { sh(command, :fail => true) } + end + + it "should print advisory information for multiple lockfiles" do + output = subject + expect(output).to include('Gemfile.lock') + expect(output).to include('../insecure_sources/Gemfile.lock') + expect(output.scan("Vulnerabilities found!").size).to eq(2) + end + end + context "when auditing a bundle with ignored gems" do let(:bundle) { 'unpatched_gems' } let(:directory) { File.join('spec','bundle',bundle) } @@ -75,7 +94,7 @@ end it "should print nothing when everything is fine" do - expect(subject.strip).to eq("No vulnerabilities found") + expect(subject.strip).to match("No vulnerabilities found") end end diff --git a/spec/scanner_spec.rb b/spec/scanner_spec.rb index 19cc92d9..56554b9b 100644 --- a/spec/scanner_spec.rb +++ b/spec/scanner_spec.rb @@ -39,7 +39,7 @@ end context "when the :ignore option is given" do - subject { scanner.scan(:ignore => ['OSVDB-89025']) } + let(:scanner) { described_class.new(directory, :ignore => ['OSVDB-89025']) } it "should ignore the specified advisories" do ids = subject.map { |result| result.advisory.id } From 8a4037eeaf0f391749df78d45c8d064e6dacbbda Mon Sep 17 00:00:00 2001 From: fatkodima Date: Thu, 29 Aug 2019 23:15:09 +0300 Subject: [PATCH 2/2] Updated ruby-advisory-db --- data/ruby-advisory-db | 2 +- data/ruby-advisory-db.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/ruby-advisory-db b/data/ruby-advisory-db index 3333cf26..faa13f2c 160000 --- a/data/ruby-advisory-db +++ b/data/ruby-advisory-db @@ -1 +1 @@ -Subproject commit 3333cf26613bfe95926d61289503bfb6d07699fa +Subproject commit faa13f2cded226d6c6c20491be66b8fe5b300529 diff --git a/data/ruby-advisory-db.ts b/data/ruby-advisory-db.ts index 9729e95e..0a5131f9 100644 --- a/data/ruby-advisory-db.ts +++ b/data/ruby-advisory-db.ts @@ -1 +1 @@ -2017-06-13 16:51:56 UTC \ No newline at end of file +2019-08-22 12:28:25 UTC \ No newline at end of file