lib/bundler/audit/scanner.rb in bundler-audit-0.3.1 vs lib/bundler/audit/scanner.rb in bundler-audit-0.4.0

- old
+ new

@@ -1,10 +1,13 @@ require 'bundler' require 'bundler/audit/database' require 'bundler/lockfile_parser' +require 'ipaddr' +require 'resolv' require 'set' +require 'uri' module Bundler module Audit class Scanner @@ -57,41 +60,136 @@ # A result from the scan. # # @return [Enumerator] # If no block is given, an Enumerator will be returned. # - def scan(options={}) - return enum_for(__method__,options) unless block_given? + 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) + + return self + end + + # + # 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 [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 + # + 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:/ + next if internal_host?(source.uri) yield InsecureSource.new(source.uri) end when Source::Rubygems source.remotes.each do |uri| if uri.scheme == 'http' yield InsecureSource.new(uri.to_s) end end end end + end + # + # 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 [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 + # + def scan_specs(options={}) + return enum_for(__method__,options) unless block_given? + + ignore = Set[] + ignore += options[:ignore] if options[:ignore] + @lockfile.specs.each do |gem| @database.check_gem(gem) do |advisory| unless ignore.include?(advisory.id) yield UnpatchedGem.new(gem,advisory) end end end + end - return self + private + + # + # Determines whether a URI is internal. + # + # @param [String] uri + # The source URI. + # + # @return [Boolean] + # + def internal_host?(uri) + return unless host = URI.parse(uri).host + Resolv.getaddresses(host).all? { |ip| internal_ip?(ip) } + rescue URI::Error + false end + # List of internal IP address ranges. + # + # @see https://tools.ietf.org/html/rfc1918#section-3 + # @see https://tools.ietf.org/html/rfc4193#section-8 + INTERNAL_SUBNETS = %w[ + 10.0.0.0/8 + 172.16.0.0/12 + 192.168.0.0/16 + fc00::/7 + ].map(&IPAddr.method(:new)) + + # + # Determines whether an IP is internal. + # + # @param [String] ip + # The IPv4/IPv6 address. + # + # @return [Boolean] + # + def internal_ip?(ip) + INTERNAL_SUBNETS.any? { |subnet| subnet.include?(ip) } + end end end end