class Bundler::Audit::Scanner

Scans a ‘Gemfile.lock` for security issues.

Constants

INTERNAL_SUBNETS

List of internal IP address ranges.

@see tools.ietf.org/html/rfc1918#section-3 @see tools.ietf.org/html/rfc4193#section-8 @see tools.ietf.org/html/rfc6890#section-2.2.2 @see tools.ietf.org/html/rfc6890#section-2.2.3

Attributes

config[R]

The configuration loaded from the ‘.bundler-audit.yml` file from the project.

@return [Hash]

database[R]

The advisory database.

@return [Database]

lockfile[R]

The parsed ‘Gemfile.lock` from the project.

@return [Bundler::LockfileParser]

root[R]

Project root directory

Public Class Methods

new(root=Dir.pwd,gemfile_lock='Gemfile.lock',database=Database.new,config_dot_file='.bundler-audit.yml') click to toggle source

Initializes a scanner.

@param [String] root

The path to the project root.

@param [String] gemfile_lock

Alternative name for the `Gemfile.lock` file.

@param [Database] database

The database to scan against.

@param [String] config_dot_file

The file name of the bundler-audit config file.

@raise [Bundler::GemfileLockNotFound]

The `gemfile_lock` file could not be found within the `root`
directory.
# File lib/bundler/audit/scanner.rb, line 77
def initialize(root=Dir.pwd,gemfile_lock='Gemfile.lock',database=Database.new,config_dot_file='.bundler-audit.yml')
  @root     = File.expand_path(root)
  @database = database

  gemfile_lock_path = File.join(@root,gemfile_lock)

  unless File.file?(gemfile_lock_path)
    raise(Bundler::GemfileLockNotFound,"Could not find #{gemfile_lock.inspect} in #{@root.inspect}")
  end

  @lockfile = LockfileParser.new(File.read(gemfile_lock_path))

  config_dot_file_full_path = File.absolute_path(config_dot_file, @root)

  @config = if File.exist?(config_dot_file_full_path)
              Configuration.load(config_dot_file_full_path)
            else
              Configuration.new
            end
end

Public Instance Methods

report(options={}) { |result| ... } click to toggle source

Preforms a {#scan} and collects the results into a {Report report}.

@param [Hash] options

Additional options.

@option options [Array<String>] :ignore

The advisories to ignore.

@yield [result]

The given block will be passed the results of the scan.

@yieldparam [Results::InsecureSource, Results::UnpatchedGem] result

A result from the scan.

@return [Report]

@since 0.8.0

# File lib/bundler/audit/scanner.rb, line 117
def report(options={})
  report = Report.new()

  scan(options) do |result|
    report << result
    yield result if block_given?
  end

  return report
end
scan(options={},&block) click to toggle source

Scans the project for issues.

@param [Hash] options

Additional options.

@option options [Array<String>] :ignore

The advisories to ignore.

@yield [result]

The given block will be passed the results of the scan.

@yieldparam [Results::InsecureSource, Results::UnpatchedGem] result

A result from the scan.

@return [Enumerator]

If no block is given, an Enumerator will be returned.
# File lib/bundler/audit/scanner.rb, line 146
def scan(options={},&block)
  return enum_for(__method__,options) unless block

  scan_sources(options,&block)
  scan_specs(options,&block)

  return self
end
scan_sources(options={}) { |insecure_source| ... } click to toggle source

Scans the gem sources in the lockfile.

@param [Hash] options

Additional options.

@yield [result]

The given block will be passed the results of the scan.

@yieldparam [Results::InsecureSource] result

A result from the scan.

@return [Enumerator]

If no block is given, an Enumerator will be returned.

@api semipublic

@since 0.4.0

# File lib/bundler/audit/scanner.rb, line 174
def scan_sources(options={})
  return enum_for(__method__,options) unless block_given?

  @lockfile.sources.map do |source|
    case source
    when Source::Git
      case source.uri
      when /^git:/, /^http:/
        unless internal_source?(source.uri)
          yield Results::InsecureSource.new(source.uri)
        end
      end
    when Source::Rubygems
      source.remotes.each do |uri|
        if (uri.scheme == 'http' && !internal_source?(uri))
          yield Results::InsecureSource.new(uri.to_s)
        end
      end
    end
  end
end
scan_specs(options={}) { |unpatched_gem| ... } click to toggle source

Scans the gem sources in the lockfile.

@param [Hash] options

Additional options.

@option options [Array<String>] :ignore

The advisories to ignore.

@yield [result]

The given block will be passed the results of the scan.

@yieldparam [Results::UnpatchedGem] result

A result from the scan.

@return [Enumerator]

If no block is given, an Enumerator will be returned.

@api semipublic

@since 0.4.0

# File lib/bundler/audit/scanner.rb, line 218
def scan_specs(options={})
  return enum_for(__method__,options) unless block_given?

  ignore = if options[:ignore]
             Set.new(options[:ignore])
           else
             config.ignore
           end

  @lockfile.specs.each do |gem|
    @database.check_gem(gem) do |advisory|
      is_ignored = ignore.intersect?(advisory.identifiers.to_set)
      next if is_ignored

      yield Results::UnpatchedGem.new(gem,advisory)
    end
  end
end

Private Instance Methods

internal_host?(host) click to toggle source

Determines whether a host is internal.

@param [String] host

The hostname.

@return [Boolean]

# File lib/bundler/audit/scanner.rb, line 261
def internal_host?(host)
  Resolv.getaddresses(host).all? { |ip| internal_ip?(ip) }
rescue URI::Error
  false
end
internal_ip?(ip) click to toggle source

Determines whether an IP is internal.

@param [String] ip

The IPv4/IPv6 address.

@return [Boolean]

# File lib/bundler/audit/scanner.rb, line 290
def internal_ip?(ip)
  INTERNAL_SUBNETS.any? { |subnet| subnet.include?(ip) }
end
internal_source?(uri) click to toggle source

Determines whether a source is internal.

@param [URI, String] uri

The URI.

@return [Boolean]

# File lib/bundler/audit/scanner.rb, line 247
def internal_source?(uri)
  uri = URI.parse(uri.to_s)

  internal_host?(uri.host) if uri.host
end