Dependency List shows only vulnerabilities from Gemnasium analyzer instead of all Dependency Scanning reports

Summary

The dependency list doesn't show all found vulnerabilities. If some analyzer (e.g. Gemnasium) reports dependencies, and some other analyzer (e.g. Retire.JS) report vulnerabilities for these, those vulnerabilities will be visible on the Project Security Dashboard, but not on the Dependency List page.

This happens because of how we process reports for the Dependency List page. We are doing it each by each. Our primal source of dependencies is dependency_files section of Dependency Scanning report. However, only Gemnasium analyzer is capable of producing this section. And since we're looping through dependency_files and applying found vulnerabilities in this particular artifact, only all vulnerabilities found by Gemnasium are added to the Dependency List payload.

Steps to reproduce

  1. Open a project that contains JavaScript packages
  2. Run dependency_scanning job on master branch
  3. Observe logs of this job and make sure that there are vulnerabilities reported by not Gemnasium analyzer. (for example, Retire.JS)
  4. Go to Security Dashboard, check a number of Dependency Scanning reported vulnerabilities.
  5. Go to the Dependency List page.

Example Project

https://gitlab.com/gitlab-org/security-products/tests/js-yarn/-/dependencies

What is the current bug behavior?

Vulnerabilities reported by Retire.JS ain't visible from the Dependency list

What is the expected correct behavior?

Vulnerabilities reported by Retire.JS should be visible from the Dependency list

Possible fixes

We can change the way how we collect vulnerabilities for Dependency List and add a dependency to the report.

We should loop through the vulnerabilities section of the report as well, and add or update the dependency in the report. This also solves an issue with possible duplicates in the list of dependencies.

class Gitlab::Ci::Parsers::Security::DependencyList

  def parse!(json_data, report)
  
    json_data['vulnerabilities'].each do |vulnerability|
      dependency = extract_dependency_from_location(vulnerability['location'])
      found_dependency = report.first_or_add(formatted(dependency))
      found_dependency.add_vulnerability(formatted(vulnerability))
    end

    json_data['dependency_files'].each do |file|
      file['dependencies'].each do |dependency|
        report.first_or_add(formatted(dependency))
      end 
    end 
  end
end

/cc @fcatteau

Edited by Olivier Gonzalez