class Pipeline::RetireJS

Public Class Methods

new(trigger, tracker) click to toggle source
Calls superclass method Pipeline::BaseTask::new
# File lib/pipeline/tasks/retirejs.rb, line 13
def initialize(trigger, tracker)
  super(trigger, tracker)
  @name = "RetireJS"
  @description = "Dependency analysis for JavaScript"
  @stage = :code
  @labels << "code" << "javascript"
  @results = []
end

Public Instance Methods

analyze() click to toggle source
# File lib/pipeline/tasks/retirejs.rb, line 31
def analyze
  begin
    @results.each do |result|
      parsed_json = JSON.parse(result)
      vulnerabilities = parse_retire_json(parsed_json) if parsed_json

      vulnerabilities.each do |vuln|
        report "Package #{vuln[:package]} has known security issues", vuln[:detail], vuln[:source], vuln[:severity], fingerprint("#{vuln[:package]}#{vuln[:source]}#{vuln[:severity]}")
      end
    end
  rescue JSON::ParserError => e
    Pipeline.debug e.message
  rescue Exception => e
    Pipeline.warn e.message
    Pipeline.warn e.backtrace
  end
end
parse_retire_json(result) click to toggle source
# File lib/pipeline/tasks/retirejs.rb, line 49
def parse_retire_json result
  Pipeline.debug "Retire JSON Raw Result:  #{result}"
  vulnerabilities = []
  # This is very ugly, but so is the json retire.js spits out
  # Loop through each component/version combo and pull all results for it
  JsonPath.on(result, '$..component').uniq.each do |comp|
    JsonPath.on(result, "$..results[?(@.component == \'#{comp}\')].version").uniq.each do |version|
      vuln_hash = {}
      vuln_hash[:package] = "#{comp}-#{version}"

      version_results = JsonPath.on(result, "$..results[?(@.component == \'#{comp}\')]").select { |r| r['version'] == version }.uniq

      # If we see the parent-->component relationship, dig through the dependency tree to try and make a dep map
      deps = []
      obj = version_results[0]
      while !obj['parent'].nil?
        deps << obj['parent']['component']
        obj = obj['parent']
      end
      if deps.length > 0
        vuln_hash[:source] = { :scanner => @name, :file => "#{deps.reverse.join('->')}->#{comp}-#{version}", :line => nil, :code => nil }
      end

      vuln_hash[:severity] = 'unknown'
      # pull detail/severity
      version_results.each do |version_result|
        JsonPath.on(version_result, '$..vulnerabilities').uniq.each do |vuln|
          vuln_hash[:severity] = severity(vuln[0]['severity'])
          vuln_hash[:detail] = vuln[0]['info'].join('\n')
        end
      end

      vulnerabilities << vuln_hash
    end
  end

  # Loop through the separately reported 'file' findings so we can tag the source (no dep map here)
  result.select { |r| !r['file'].nil? }.each do |file_result|
    JsonPath.on(file_result, '$..component').uniq.each do |comp|
      JsonPath.on(file_result, "$..results[?(@.component == \'#{comp}\')].version").uniq.each do |version|
        source_path = relative_path(file_result['file'], @trigger.path)
        vulnerabilities.select { |v| v[:package] == "#{comp}-#{version}" }.first[:source] = { :scanner => @name, :file => source_path.to_s, :line => nil, :code => nil }
      end
    end
  end
  return vulnerabilities
end
run() click to toggle source
# File lib/pipeline/tasks/retirejs.rb, line 22
def run
  exclude_dirs = ['node_modules','bower_components']
  exclude_dirs = exclude_dirs.concat(@tracker.options[:exclude_dirs]).uniq if @tracker.options[:exclude_dirs]
  directories_with?('package.json', exclude_dirs).each do |dir|
    Pipeline.notify "#{@name} scanning: #{dir}"
    @results << runsystem(false, 'retire', '-c', '--outputformat', 'json', '--path', "#{dir}")
  end
end
supported?() click to toggle source
# File lib/pipeline/tasks/retirejs.rb, line 97
def supported?
  supported=runsystem(false, "retire", "--help")
  if supported =~ /command not found/
    Pipeline.notify "Install RetireJS"
    return false
  else
    return true
  end
end