module MyPrecious::CVEs

Constants

CONFIG_FILE
CVE_DATA_CACHE_DIR
MIN_GAP_SECONDS

Attributes

config_dir[R]

Public Class Methods

config() click to toggle source
# File lib/myprecious/cves.rb, line 78
def self.config
  if !@config && config_dir
    if (config_path = config_dir / CONFIG_FILE).exist?
      @config = begin
        config_prog_output, status = Open3.capture2(RbConfig.ruby, config_path.to_s)
        if status.success?
          JSON.parse(config_prog_output)
        else
          $stderr.puts "#{config_path} did not exit cleanly (code ${status.exitstatus})"
          {}
        end
      rescue StandardError
      end
      
      unless @config.kind_of?(Hash)
        $stderr.puts "#{config_path} did not output a JSON configuration"
        @config = {}
      end
    else
      @config = {}
    end
  end
  @config ||= {}
end
config_dir=(val) click to toggle source
# File lib/myprecious/cves.rb, line 22
def config_dir=(val)
  @config_dir = Pathname(val)
end
get_for(package_name, version='*') click to toggle source

If you don't specify version, you get to match against the applicable configurations on your own to determine which CVEs returned apply to the versions of the named package in which you are interested

# File lib/myprecious/cves.rb, line 40
def self.get_for(package_name, version='*')
  nvd_url = URI("https://services.nvd.nist.gov/rest/json/cves/1.0")
  nvd_url.query = URI.encode_www_form(
    cpeMatchString: "cpe:2.3:a:*:#{package_name.downcase}:#{version}:*:*:*:*:*:*:*",
  )
  
  cache = CVE_DATA_CACHE_DIR / "#{Digest::SHA256.hexdigest(nvd_url.to_s)}.json"
  cve_data = apply_cache(cache) do
    # Use last_query_time to sleep if necessary
    wait_time = MIN_GAP_SECONDS - (DateTime.now - last_query_time) * 24 * 3600
    if wait_time > 0
      sleep(wait_time)
    end
    
    response = RestClient.get(nvd_url.to_s)
    queried!
    
    JSON.parse(response.body)
  end
  
  begin
    return cve_data['result']['CVE_Items'].map do |e|
      applicability = objectify_configurations(package_name, e['configurations'])
      score = (((e['impact'] || {})['baseMetricV3'] || {})['cvssV3'] || {})['baseScore']
      cve = CVERecord.new(
        e['cve']['CVE_data_meta']['ID'],
        applicability.respond_to?(:vendors) ? applicability.vendors : nil,
        score
      )
      
      [cve, applicability]
    end.reject {|cve, a| a.respond_to?(:applies_to?) && !a.applies_to?(version)}
  rescue StandardError => e
    $stderr.puts "[WARN] #{e}\n\n#{JSON.dump(cve_data)}\n\n"
    []
  end
end
last_query_time() click to toggle source
# File lib/myprecious/cves.rb, line 27
def self.last_query_time
  @last_query_time ||= DateTime.now - 1
end
objectify_configurations(package_name, configs) click to toggle source
# File lib/myprecious/cves.rb, line 103
def self.objectify_configurations(package_name, configs)
  if configs.kind_of?(Hash) && configs['CVE_data_version'] == "4.0"
    Applicability_V4_0.new(package_name, configs)
  else
    configs
  end
end
queried!() click to toggle source
# File lib/myprecious/cves.rb, line 31
def self.queried!
  @last_query_time = DateTime.now
end