Skip to content

Commit 97c2f79

Browse files
committed
Merge keshav-space:github_validator
Signed-off-by: Philippe Ombredanne <[email protected]>
2 parents 1a0744d + d1c7d17 commit 97c2f79

File tree

8 files changed

+1132
-0
lines changed

8 files changed

+1132
-0
lines changed

vulntotal/datasources/github.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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+
25+
import logging
26+
from typing import Iterable
27+
28+
from packageurl import PackageURL
29+
30+
from vulnerabilities import utils
31+
from vulntotal.validator import DataSource
32+
from vulntotal.validator import VendorData
33+
from vulntotal.vulntotal_utils import github_constraints_satisfied
34+
35+
logger = logging.getLogger(__name__)
36+
37+
38+
class GithubDataSource(DataSource):
39+
spdx_license_expression = "TODO"
40+
license_url = "TODO"
41+
42+
def fetch_github(self, graphql_query):
43+
return utils.fetch_github_graphql_query(graphql_query)
44+
45+
def datasource_advisory(self, purl) -> Iterable[VendorData]:
46+
end_cursor = ""
47+
interesting_edges = []
48+
while True:
49+
queryset = generate_graphql_payload(purl, end_cursor)
50+
response = self.fetch_github(queryset)
51+
self._raw_dump.append(response)
52+
security_advisories = response["data"]["securityVulnerabilities"]
53+
interesting_edges.extend(extract_interesting_edge(security_advisories["edges"], purl))
54+
end_cursor = security_advisories["pageInfo"]["endCursor"]
55+
if not security_advisories["pageInfo"]["hasNextPage"]:
56+
break
57+
return parse_advisory(interesting_edges)
58+
59+
@classmethod
60+
def supported_ecosystem(cls):
61+
return {
62+
"maven": "MAVEN",
63+
"nuget": "NUGET",
64+
"composer": "COMPOSER",
65+
"pypi": "PIP",
66+
"gem": "RUBYGEMS",
67+
"golang": "GO",
68+
"rust": "RUST",
69+
"npm": "NPM",
70+
"erlang": "ERLANG",
71+
}
72+
73+
74+
def parse_advisory(interesting_edges) -> Iterable[VendorData]:
75+
for edge in interesting_edges:
76+
aliases = [aliase["value"] for aliase in edge["node"]["advisory"]["identifiers"]]
77+
affected_versions = (
78+
edge["node"]["vulnerableVersionRange"].strip().replace(" ", "").split(",")
79+
)
80+
fixed_versions = [edge["node"]["firstPatchedVersion"]["identifier"]]
81+
yield VendorData(
82+
aliases=sorted(list(set(aliases))),
83+
affected_versions=sorted(list(set(affected_versions))),
84+
fixed_versions=sorted(list(set(fixed_versions))),
85+
)
86+
87+
88+
def extract_interesting_edge(edges, purl):
89+
interesting_edges = []
90+
for edge in edges:
91+
if github_constraints_satisfied(edge["node"]["vulnerableVersionRange"], purl.version):
92+
interesting_edges.append(edge)
93+
return interesting_edges
94+
95+
96+
def generate_graphql_payload(purl, end_cursor):
97+
GRAPHQL_QUERY_TEMPLATE = """
98+
query{
99+
securityVulnerabilities(first: 100, ecosystem: %s, package: "%s", %s){
100+
edges {
101+
node {
102+
advisory {
103+
identifiers {
104+
type
105+
value
106+
}
107+
summary
108+
references {
109+
url
110+
}
111+
severity
112+
publishedAt
113+
}
114+
firstPatchedVersion{
115+
identifier
116+
}
117+
package {
118+
name
119+
}
120+
vulnerableVersionRange
121+
}
122+
}
123+
pageInfo {
124+
hasNextPage
125+
endCursor
126+
}
127+
}
128+
}
129+
"""
130+
131+
supported_ecosystem = GithubDataSource.supported_ecosystem()
132+
133+
if purl.type not in supported_ecosystem:
134+
return
135+
136+
end_cursor_exp = ""
137+
ecosystem = supported_ecosystem[purl.type]
138+
package_name = purl.name
139+
140+
if end_cursor:
141+
end_cursor_exp = f'after: "{end_cursor}"'
142+
143+
if purl.type == "maven":
144+
if not purl.namespace:
145+
logger.error(f"Invalid Maven PURL {str(purl)}")
146+
return
147+
package_name = f"{purl.namespace}:{purl.name}"
148+
149+
elif purl.type == "composer":
150+
if not purl.namespace:
151+
logger.error(f"Invalid Composer PURL {str(purl)}")
152+
return
153+
package_name = f"{purl.namespace}/{purl.name}"
154+
155+
elif purl.type == "golang" and purl.namespace:
156+
package_name = f"{purl.namespace}/{purl.name}"
157+
158+
return {"query": GRAPHQL_QUERY_TEMPLATE % (ecosystem, package_name, end_cursor_exp)}

0 commit comments

Comments
 (0)