Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
303efca
add initial config for vulntotal
keshav-space Jun 20, 2022
cfcdfd7
Merge pull request #777 from keshav-space/vulntotal_init
keshav-space Jul 5, 2022
2176cb1
don't include `raw_dump` in `VendorData`
keshav-space Jul 19, 2022
922859f
add supported ecosystem
keshav-space Jul 25, 2022
78dd5ae
drop Validator in favour of DataSource
keshav-space Aug 4, 2022
fe20e49
adopt new CLI
keshav-space Aug 29, 2022
e30131f
add Deps Datasource
keshav-space Sep 5, 2022
3235316
add tests for Deps Datasource
keshav-space Sep 5, 2022
1724d91
add Github Datasource
keshav-space Sep 5, 2022
8d42af1
add vulntotal utils
keshav-space Sep 5, 2022
d1c7d17
add tests for Github Datasource
keshav-space Sep 5, 2022
4a62daa
add OSS-Index Datasource
keshav-space Sep 5, 2022
e639b5e
add tests for OSS-Index Datasource
keshav-space Sep 5, 2022
03fc9b0
add VulnerableCode DataSource
keshav-space Sep 5, 2022
7b1faf8
add Snyk Datasource
keshav-space Sep 5, 2022
13328c7
add vulntotal utils
keshav-space Sep 5, 2022
9a38cf2
add Gitlab Datasource
keshav-space Sep 5, 2022
0378b6c
add vulntotal utils
keshav-space Sep 5, 2022
55fc8a8
test Gitlab Datasource
keshav-space Sep 5, 2022
edf2d61
register available datasources
keshav-space Sep 5, 2022
1fa44ce
add osv DataSource
keshav-space Sep 15, 2022
ab161ff
support get_closest_nuget_package_name
keshav-space Sep 15, 2022
491c318
test OSVDataSource
keshav-space Sep 15, 2022
536dd91
add utilities for vulntotal
keshav-space Sep 15, 2022
1a0744d
Merge branch 'deps-only' into vulntotal-clean
pombredanne Nov 19, 2022
97c2f79
Merge keshav-space:github_validator
pombredanne Nov 19, 2022
b18b2ed
Merge remote-tracking branch 'keshav-space/oss_index_datasource' into…
pombredanne Nov 19, 2022
ec64fc9
test snyk Datasource
keshav-space Sep 5, 2022
edc4047
Merge keshav-space:snyk_datasource branch
pombredanne Nov 19, 2022
3664c3e
Merge keshav-space:gitlab_datasource branch
pombredanne Nov 19, 2022
fb7ef75
test VulnerableCodeDataSource
keshav-space Sep 5, 2022
50d38db
Merge branch 'vulnerablecode_datasource' into vulntotal-clean
pombredanne Nov 19, 2022
cad9501
Merge remote-tracking branch 'keshav-space/vulntotal_cli' into vulnto…
pombredanne Nov 19, 2022
7af1b5d
Merge remote-tracking branch 'keshav-space/datasource_registry' into …
pombredanne Nov 19, 2022
1cb8ead
Use explicit list of datasources
pombredanne Nov 19, 2022
5033af7
Use standard header and streamline imports
pombredanne Nov 19, 2022
9a559bf
Use simpler and smaller test fixtures
pombredanne Nov 19, 2022
557728f
Streamline CLI help
pombredanne Nov 19, 2022
76908fc
Do not use file for small test data list
pombredanne Nov 19, 2022
07553e4
Remove unused file
pombredanne Nov 19, 2022
4980f92
Streamline code
pombredanne Nov 19, 2022
575cc19
Use faster Nuget API
pombredanne Nov 19, 2022
d69b4e5
Add webtest marker to test making network call
pombredanne Nov 19, 2022
e54618d
Remove unused imports
pombredanne Nov 19, 2022
6e77b25
Merge remote-tracking branch 'origin/main' into vulntotal
pombredanne Nov 19, 2022
6d838e6
Format code
pombredanne Nov 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,4 @@ dev =
[options.entry_points]
console_scripts =
vulnerablecode = vulnerablecode:command_line

vulntotal = vulntotal.vulntotal_cli:handler
11 changes: 11 additions & 0 deletions vulnerabilities/tests/util_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ def check_results_against_json(
with open(expected_file) as exp:
expected = json.load(exp)

check_results_against_expected(results, expected)


def check_results_against_expected(
results,
expected,
):
"""
Check the JSON-serializable mapping or sequence ``results`` against the
``expected``.
"""
# NOTE we redump the JSON as a YAML string for easier display of
# the failures comparison/diff
if results != expected:
Expand Down
8 changes: 8 additions & 0 deletions vulntotal/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# VulnerableCode is a trademark of nexB Inc.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
# See https://github.com/nexB/vulnerablecode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#
27 changes: 27 additions & 0 deletions vulntotal/datasources/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# VulnerableCode is a trademark of nexB Inc.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
# See https://github.com/nexB/vulnerablecode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#

from vulntotal.datasources import deps
from vulntotal.datasources import github
from vulntotal.datasources import gitlab
from vulntotal.datasources import oss_index
from vulntotal.datasources import osv
from vulntotal.datasources import snyk
from vulntotal.datasources import vulnerablecode
from vulntotal.validator import DataSource

DATASOURCE_REGISTRY = {
"deps": deps.DepsDataSource,
"github": github.GithubDataSource,
"gitlab": gitlab.GitlabDataSource,
"oss_index": oss_index.OSSDataSource,
"osv": osv.OSVDataSource,
"snyk": snyk.SnykDataSource,
"vulnerablecode": vulnerablecode.VulnerableCodeDataSource,
}
107 changes: 107 additions & 0 deletions vulntotal/datasources/deps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# VulnerableCode is a trademark of nexB Inc.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
# See https://github.com/nexB/vulnerablecode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#

import logging
from typing import Iterable
from urllib.parse import quote

import requests

from vulntotal.validator import DataSource
from vulntotal.validator import VendorData

logger = logging.getLogger(__name__)


class DepsDataSource(DataSource):
spdx_license_expression = "TODO"
license_url = "TODO"

def fetch_json_response(self, url):
response = requests.get(url)
if not response.status_code == 200 or response.text == "Not Found":
logger.error(f"Error while fetching {url}")
return
return response.json()

def datasource_advisory(self, purl) -> Iterable[VendorData]:
payload = generate_meta_payload(purl)
response = self.fetch_json_response(payload)
if response:
advisories = parse_advisories_from_meta(response)
if advisories:
for advisory in advisories:
advisory_payload = generate_advisory_payload(advisory)
fetched_advisory = self.fetch_json_response(advisory_payload)
self._raw_dump.append(fetched_advisory)
if fetched_advisory:
return parse_advisory(fetched_advisory)

@classmethod
def supported_ecosystem(cls):
return {
"npm": "npm",
"maven": "maven",
"golang": "go",
"pypi": "pypi",
"cargo": "cargo",
# Coming soon
# "nuget": "nuget",
}


def parse_advisory(advisory) -> Iterable[VendorData]:
package = advisory["packages"][0]
affected_versions = [event["version"] for event in package["versionsAffected"]]
fixed_versions = [event["version"] for event in package["versionsUnaffected"]]
yield VendorData(
aliases=sorted(set(advisory["aliases"])),
affected_versions=sorted(set(affected_versions)),
fixed_versions=sorted(set(fixed_versions)),
)


def parse_advisories_from_meta(advisories_metadata):
advisories = []
dependencies = advisories_metadata.get("dependencies") or []
for dependency in dependencies:
advs = dependency.get("advisories") or []
advisories.extend(advs)
return advisories


def generate_advisory_payload(advisory_meta):
url_advisory = "https://deps.dev/_/advisory/{source}/{sourceID}"
return url_advisory.format(source=advisory_meta["source"], sourceID=advisory_meta["sourceID"])


def generate_meta_payload(purl):
url_advisories_meta = "https://deps.dev/_/s/{ecosystem}/p/{package}/v/{version}/dependencies"
supported_ecosystem = DepsDataSource.supported_ecosystem()
if purl.type in supported_ecosystem:
purl_version = purl.version
purl_name = purl.name

if purl.type == "maven":
if not purl.namespace:
logger.error(f"Invalid Maven PURL {str(purl)}")
return
purl_name = quote(f"{purl.namespace}:{purl.name}", safe="")

elif purl.type == "golang":
if purl.namespace:
purl_name = quote(f"{purl.namespace}/{purl.name}", safe="")
if not purl_version.startswith("v"):
purl_version = f"v{purl_version}"

return url_advisories_meta.format(
ecosystem=supported_ecosystem[purl.type],
package=purl_name,
version=purl_version,
)
140 changes: 140 additions & 0 deletions vulntotal/datasources/github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# VulnerableCode is a trademark of nexB Inc.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
# See https://github.com/nexB/vulnerablecode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#

import logging
from typing import Iterable

from vulnerabilities import utils
from vulntotal.validator import DataSource
from vulntotal.validator import VendorData
from vulntotal.vulntotal_utils import github_constraints_satisfied

logger = logging.getLogger(__name__)


class GithubDataSource(DataSource):
spdx_license_expression = "TODO"
license_url = "TODO"

def fetch_github(self, graphql_query):
return utils.fetch_github_graphql_query(graphql_query)

def datasource_advisory(self, purl) -> Iterable[VendorData]:
end_cursor = ""
interesting_edges = []
while True:
queryset = generate_graphql_payload(purl, end_cursor)
response = self.fetch_github(queryset)
self._raw_dump.append(response)
security_advisories = response["data"]["securityVulnerabilities"]
interesting_edges.extend(extract_interesting_edge(security_advisories["edges"], purl))
end_cursor = security_advisories["pageInfo"]["endCursor"]
if not security_advisories["pageInfo"]["hasNextPage"]:
break
return parse_advisory(interesting_edges)

@classmethod
def supported_ecosystem(cls):
return {
"maven": "MAVEN",
"nuget": "NUGET",
"composer": "COMPOSER",
"pypi": "PIP",
"gem": "RUBYGEMS",
"golang": "GO",
"rust": "RUST",
"npm": "NPM",
"erlang": "ERLANG",
}


def parse_advisory(interesting_edges) -> Iterable[VendorData]:
for edge in interesting_edges:
node = edge["node"]
aliases = [aliase["value"] for aliase in node["advisory"]["identifiers"]]
affected_versions = node["vulnerableVersionRange"].strip().replace(" ", "").split(",")
fixed_versions = [node["firstPatchedVersion"]["identifier"]]
yield VendorData(
aliases=sorted(list(set(aliases))),
affected_versions=sorted(list(set(affected_versions))),
fixed_versions=sorted(list(set(fixed_versions))),
)


def extract_interesting_edge(edges, purl):
interesting_edges = []
for edge in edges:
if github_constraints_satisfied(edge["node"]["vulnerableVersionRange"], purl.version):
interesting_edges.append(edge)
return interesting_edges


def generate_graphql_payload(purl, end_cursor):
GRAPHQL_QUERY_TEMPLATE = """
query{
securityVulnerabilities(first: 100, ecosystem: %s, package: "%s", %s){
edges {
node {
advisory {
identifiers {
type
value
}
summary
references {
url
}
severity
publishedAt
}
firstPatchedVersion{
identifier
}
package {
name
}
vulnerableVersionRange
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
"""

supported_ecosystem = GithubDataSource.supported_ecosystem()

if purl.type not in supported_ecosystem:
return

end_cursor_exp = ""
ecosystem = supported_ecosystem[purl.type]
package_name = purl.name

if end_cursor:
end_cursor_exp = f'after: "{end_cursor}"'

if purl.type == "maven":
if not purl.namespace:
logger.error(f"Invalid Maven PURL {str(purl)}")
return
package_name = f"{purl.namespace}:{purl.name}"

elif purl.type == "composer":
if not purl.namespace:
logger.error(f"Invalid Composer PURL {str(purl)}")
return
package_name = f"{purl.namespace}/{purl.name}"

elif purl.type == "golang" and purl.namespace:
package_name = f"{purl.namespace}/{purl.name}"

return {"query": GRAPHQL_QUERY_TEMPLATE % (ecosystem, package_name, end_cursor_exp)}
Loading