diff --git a/README.md b/README.md index 30abaf82..7532e624 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,10 @@ Ignore specific advisories: $ bundle audit check --ignore OSVDB-108664 +Ignore gems from specific Gemfile groups: + + $ bundle audit check --ignore-groups development test + Rake task: ```ruby diff --git a/data/ruby-advisory-db b/data/ruby-advisory-db index 3333cf26..ec04dbda 160000 --- a/data/ruby-advisory-db +++ b/data/ruby-advisory-db @@ -1 +1 @@ -Subproject commit 3333cf26613bfe95926d61289503bfb6d07699fa +Subproject commit ec04dbda59b01b47ec97a1543385f4f08544f3a3 diff --git a/data/ruby-advisory-db.ts b/data/ruby-advisory-db.ts index 9729e95e..2854957c 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-19 13:52:52 UTC \ No newline at end of file diff --git a/lib/bundler/audit/cli.rb b/lib/bundler/audit/cli.rb index 8461404f..88089b79 100644 --- a/lib/bundler/audit/cli.rb +++ b/lib/bundler/audit/cli.rb @@ -33,6 +33,7 @@ class CLI < ::Thor method_option :quiet, :type => :boolean, :aliases => '-q' method_option :verbose, :type => :boolean, :aliases => '-v' method_option :ignore, :type => :array, :aliases => '-i' + method_option :ignore_groups, :type => :array method_option :update, :type => :boolean, :aliases => '-u' def check @@ -41,7 +42,7 @@ def check scanner = Scanner.new vulnerable = false - scanner.scan(:ignore => options.ignore) do |result| + scanner.scan(:ignore => options.ignore, :ignore_groups => options.ignore_groups) do |result| vulnerable = true case result diff --git a/lib/bundler/audit/gemfile.rb b/lib/bundler/audit/gemfile.rb new file mode 100644 index 00000000..d53fc8b2 --- /dev/null +++ b/lib/bundler/audit/gemfile.rb @@ -0,0 +1,42 @@ +require 'set' + +module Bundler + module Audit + class Gemfile + def initialize(env) + @env = env + end + + def groups + groups = Set.new + @env.current_dependencies.each do |dependency| + groups += dependency.groups + end + groups.to_a + end + + def dependencies_for(groups) + groups.map!(&:to_sym) + dependencies = Set.new + parent_dependencies = @env.current_dependencies.select { |dep| (dep.groups & groups).any? } + + while parent_dependencies.any? + tmp = Set.new + parent_dependencies.each do |dependency| + dependencies << dependency + child_dependencies = spec_for_dependency(dependency).runtime_dependencies.to_set + tmp += child_dependencies + end + parent_dependencies = tmp + end + dependencies.to_a + end + + private + + def spec_for_dependency(dependency) + @env.requested_specs.find { |spec| spec.name == dependency.name } + end + end + end +end diff --git a/lib/bundler/audit/scanner.rb b/lib/bundler/audit/scanner.rb index 145a0dea..9f04a09d 100644 --- a/lib/bundler/audit/scanner.rb +++ b/lib/bundler/audit/scanner.rb @@ -1,5 +1,6 @@ require 'bundler' require 'bundler/audit/database' +require 'bundler/audit/gemfile' require 'bundler/lockfile_parser' require 'ipaddr' @@ -68,9 +69,6 @@ def initialize(root=Dir.pwd,gemfile_lock='Gemfile.lock') def scan(options={},&block) return enum_for(__method__,options) unless block - ignore = Set[] - ignore += options[:ignore] if options[:ignore] - scan_sources(options,&block) scan_specs(options,&block) @@ -127,6 +125,9 @@ def scan_sources(options={}) # @option options [Array] :ignore # The advisories to ignore. # + # @option options [Array] :ignore_groups + # The groups from Gemfile to ignore. + # # @yield [result] # The given block will be passed the results of the scan. # @@ -146,7 +147,12 @@ def scan_specs(options={}) ignore = Set[] ignore += options[:ignore] if options[:ignore] + gem_names = gems_to_check(options) + @lockfile.specs.each do |gem| + is_ignored = gem_names != :all && !gem_names.include?(gem.name) + next if is_ignored + @database.check_gem(gem) do |advisory| is_ignored = ignore.intersect?(advisory.identifiers.to_set) next if is_ignored @@ -208,6 +214,17 @@ def internal_host?(host) def internal_ip?(ip) INTERNAL_SUBNETS.any? { |subnet| subnet.include?(ip) } end + + def gems_to_check(options) + if options[:ignore_groups] + ENV["BUNDLE_GEMFILE"] = File.join(@root, "Gemfile") + gemfile = Gemfile.new(Bundler.load) + groups_to_check = gemfile.groups - options[:ignore_groups].map!(&:to_sym) + gemfile.dependencies_for(groups_to_check).map(&:name).to_set + else + :all + end + end end end end diff --git a/spec/bundle/insecure_sources/Gemfile b/spec/bundle/insecure_sources/Gemfile index a0f7fd19..ae1c34d2 100644 --- a/spec/bundle/insecure_sources/Gemfile +++ b/spec/bundle/insecure_sources/Gemfile @@ -1,4 +1,4 @@ source 'http://rubygems.org' -gem 'rails' +gem 'rails', '~> 5.2' # rails >= 6 requires ruby >= 2.5.0 gem 'jquery-rails', git: 'git://github.com/rails/jquery-rails.git' diff --git a/spec/bundle/unpatched_gems/Gemfile b/spec/bundle/unpatched_gems/Gemfile index 40f61778..840ac9bb 100644 --- a/spec/bundle/unpatched_gems/Gemfile +++ b/spec/bundle/unpatched_gems/Gemfile @@ -1,3 +1,7 @@ source 'https://rubygems.org' gem 'activerecord', '3.2.10' + +group :test do + gem 'redis-store', '1.3.0' +end diff --git a/spec/scanner_spec.rb b/spec/scanner_spec.rb index 19cc92d9..ba11ec33 100644 --- a/spec/scanner_spec.rb +++ b/spec/scanner_spec.rb @@ -33,6 +33,7 @@ it "should match unpatched gems to their advisories" do ids = subject.map { |result| result.advisory.id } expect(ids).to include('OSVDB-89025') + expect(ids).to include('CVE-2017-1000248') expect(subject.all? { |result| result.advisory.vulnerable?(result.gem.version) }).to be_truthy @@ -46,6 +47,15 @@ expect(ids).not_to include('OSVDB-89025') end end + + context "when the :ignore_groups option is given" do + subject { scanner.scan(:ignore_groups => ['test']) } + + it "should ignore gems from the specified groups" do + ids = subject.map { |result| result.advisory.id } + expect(ids).not_to include('OSVDB-89025') + end + end end context "when auditing a bundle with insecure sources" do