Skip to content

Commit 2d0ce73

Browse files
committed
add deps validator
Signed-off-by: Keshav Priyadarshi <git@keshav.space>
1 parent cfcdfd7 commit 2d0ce73

2 files changed

Lines changed: 123 additions & 1 deletion

File tree

vulntotal/validators/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
# VulnTotal is a free software tool from nexB Inc. and others.
2222
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2323

24+
from vulntotal.validators import deps
2425

25-
VALIDATORS_REGISTRY = []
26+
VALIDATORS_REGISTRY = [
27+
deps.DepsValidator,
28+
]
2629

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

vulntotal/validators/deps.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 Validator
33+
from vulntotal.validator import VendorData
34+
35+
logger = logging.getLogger(__name__)
36+
37+
38+
class DepsValidator(Validator):
39+
spdx_license_expression = "TODO"
40+
license_url = "TODO"
41+
_raw_dump = []
42+
43+
def fetch_json_response(self, url):
44+
response = requests.get(url)
45+
if not response.status_code == 200 or response.text == "Not Found":
46+
logger.error(f"Error while fetching {url}")
47+
return
48+
return json.loads(response.text)
49+
50+
def validator_advisory(self, purl) -> Iterable[VendorData]:
51+
payload = generate_meta_payload(purl)
52+
response = self.fetch_json_response(payload)
53+
if response:
54+
advisories = parse_advisories_from_meta(response)
55+
if advisories:
56+
for advisory in advisories:
57+
advisory_payload = generate_advisory_payload(advisory)
58+
fetched_advisory = self.fetch_json_response(advisory_payload)
59+
self._raw_dump.append(fetched_advisory)
60+
if fetched_advisory:
61+
return parse_advisory(fetched_advisory)
62+
63+
64+
def parse_advisory(advisory) -> Iterable[VendorData]:
65+
affected_versions = [event["version"] for event in advisory["packages"][0]["versionsAffected"]]
66+
fixed_versions = [event["version"] for event in advisory["packages"][0]["versionsUnaffected"]]
67+
yield VendorData(
68+
aliases=sorted(list(set(advisory["aliases"]))),
69+
affected_versions=sorted(list(set(affected_versions))),
70+
fixed_versions=sorted(list(set(fixed_versions))),
71+
)
72+
73+
74+
def parse_advisories_from_meta(advisories_metadata):
75+
advisories = []
76+
if "dependencies" in advisories_metadata and advisories_metadata["dependencies"]:
77+
for dependency in advisories_metadata["dependencies"]:
78+
if dependency["advisories"]:
79+
advisories.extend(dependency["advisories"])
80+
return advisories
81+
82+
83+
def generate_advisory_payload(advisory_meta):
84+
url_advisory = "https://deps.dev/_/advisory/{source}/{sourceID}"
85+
return url_advisory.format(source=advisory_meta["source"], sourceID=advisory_meta["sourceID"])
86+
87+
88+
def generate_meta_payload(purl):
89+
url_advisories_meta = "https://deps.dev/_/s/{ecosystem}/p/{package}/v/{version}/dependencies"
90+
supported_ecosystem = {
91+
"npm": "npm",
92+
"maven": "maven",
93+
"go": "go",
94+
"pypi": "pypi",
95+
"cargo": "cargo",
96+
# Coming soon
97+
# "nuget": "nuget",
98+
}
99+
if purl.type in supported_ecosystem:
100+
purl_version = purl.version
101+
purl_name = purl.name
102+
103+
if purl.type == "maven":
104+
if not purl.namespace:
105+
logger.error(f"Invalid Maven PURL {str(purl)}")
106+
return
107+
purl_name = quote(f"{purl.namespace}:{purl.name}", safe="")
108+
109+
elif purl.type == "go":
110+
if purl.namespace:
111+
purl_name = quote(f"{purl.namespace}/{purl.name}", safe="")
112+
if not purl_version.startswith("v"):
113+
purl_version = f"v{purl_version}"
114+
115+
return url_advisories_meta.format(
116+
ecosystem=supported_ecosystem[purl.type],
117+
package=purl_name,
118+
version=purl_version,
119+
)

0 commit comments

Comments
 (0)