Skip to content

Commit bc8c269

Browse files
committed
add Deps DataSource
Signed-off-by: Keshav Priyadarshi <git@keshav.space>
1 parent 7bc5575 commit bc8c269

2 files changed

Lines changed: 127 additions & 1 deletion

File tree

vulntotal/datasources/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2323

2424

25-
DATASOURCE_REGISTRY = []
25+
from vulntotal.datasources import deps
26+
27+
DATASOURCE_REGISTRY = [
28+
deps.DepsDataSource,
29+
]
2630

2731
DATASOURCE_REGISTRY = {x.__module__.split(".")[-1]: x for x in DATASOURCE_REGISTRY}

vulntotal/datasources/deps.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# http://nexb.com and https://github.com/nexB/vulnerablecode/
4+
# The VulnTotal software is licensed under the Apache License version 2.0.
5+
# Data generated with VulnTotal require an acknowledgment.
6+
#
7+
# You may not use this software except in compliance with the License.
8+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
9+
# Unless required by applicable law or agreed to in writing, software distributed
10+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
12+
# specific language governing permissions and limitations under the License.
13+
#
14+
# When you publish or redistribute any data created with VulnTotal or any VulnTotal
15+
# derivative work, you must accompany this data with the following acknowledgment:
16+
#
17+
# Generated with VulnTotal and provided on an "AS IS" BASIS, WITHOUT WARRANTIES
18+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
19+
# VulnTotal should be considered or used as legal advice. Consult an Attorney
20+
# for any legal advice.
21+
# VulnTotal is a free software tool from nexB Inc. and others.
22+
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
23+
24+
import json
25+
import logging
26+
from typing import Iterable
27+
from urllib.parse import quote
28+
29+
import requests
30+
from packageurl import PackageURL
31+
32+
from vulntotal.validator import DataSource
33+
from vulntotal.validator import VendorData
34+
35+
logger = logging.getLogger(__name__)
36+
37+
38+
class DepsDataSource(DataSource):
39+
spdx_license_expression = "TODO"
40+
license_url = "TODO"
41+
42+
def fetch_json_response(self, url):
43+
response = requests.get(url)
44+
if not response.status_code == 200 or response.text == "Not Found":
45+
logger.error(f"Error while fetching {url}")
46+
return
47+
return response.json()
48+
49+
def datasource_advisory(self, purl) -> Iterable[VendorData]:
50+
payload = generate_meta_payload(purl)
51+
response = self.fetch_json_response(payload)
52+
if response:
53+
advisories = parse_advisories_from_meta(response)
54+
if advisories:
55+
for advisory in advisories:
56+
advisory_payload = generate_advisory_payload(advisory)
57+
fetched_advisory = self.fetch_json_response(advisory_payload)
58+
self._raw_dump.append(fetched_advisory)
59+
if fetched_advisory:
60+
return parse_advisory(fetched_advisory)
61+
62+
@classmethod
63+
def supported_ecosystem(cls):
64+
return {
65+
"npm": "npm",
66+
"maven": "maven",
67+
"go": "go",
68+
"pypi": "pypi",
69+
"cargo": "cargo",
70+
# Coming soon
71+
# "nuget": "nuget",
72+
}
73+
74+
75+
def parse_advisory(advisory) -> Iterable[VendorData]:
76+
affected_versions = [event["version"] for event in advisory["packages"][0]["versionsAffected"]]
77+
fixed_versions = [event["version"] for event in advisory["packages"][0]["versionsUnaffected"]]
78+
yield VendorData(
79+
aliases=sorted(list(set(advisory["aliases"]))),
80+
affected_versions=sorted(list(set(affected_versions))),
81+
fixed_versions=sorted(list(set(fixed_versions))),
82+
)
83+
84+
85+
def parse_advisories_from_meta(advisories_metadata):
86+
advisories = []
87+
if "dependencies" in advisories_metadata and advisories_metadata["dependencies"]:
88+
for dependency in advisories_metadata["dependencies"]:
89+
if dependency["advisories"]:
90+
advisories.extend(dependency["advisories"])
91+
return advisories
92+
93+
94+
def generate_advisory_payload(advisory_meta):
95+
url_advisory = "https://deps.dev/_/advisory/{source}/{sourceID}"
96+
return url_advisory.format(source=advisory_meta["source"], sourceID=advisory_meta["sourceID"])
97+
98+
99+
def generate_meta_payload(purl):
100+
url_advisories_meta = "https://deps.dev/_/s/{ecosystem}/p/{package}/v/{version}/dependencies"
101+
supported_ecosystem = DepsDataSource.supported_ecosystem()
102+
if purl.type in supported_ecosystem:
103+
purl_version = purl.version
104+
purl_name = purl.name
105+
106+
if purl.type == "maven":
107+
if not purl.namespace:
108+
logger.error(f"Invalid Maven PURL {str(purl)}")
109+
return
110+
purl_name = quote(f"{purl.namespace}:{purl.name}", safe="")
111+
112+
elif purl.type == "go":
113+
if purl.namespace:
114+
purl_name = quote(f"{purl.namespace}/{purl.name}", safe="")
115+
if not purl_version.startswith("v"):
116+
purl_version = f"v{purl_version}"
117+
118+
return url_advisories_meta.format(
119+
ecosystem=supported_ecosystem[purl.type],
120+
package=purl_name,
121+
version=purl_version,
122+
)

0 commit comments

Comments
 (0)