scanoss 1.19.6__py3-none-any.whl → 1.20.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- protoc_gen_swagger/__init__.py +13 -13
- protoc_gen_swagger/options/__init__.py +13 -13
- protoc_gen_swagger/options/annotations_pb2.py +12 -9
- protoc_gen_swagger/options/annotations_pb2_grpc.py +1 -1
- protoc_gen_swagger/options/openapiv2_pb2.py +98 -96
- protoc_gen_swagger/options/openapiv2_pb2_grpc.py +1 -1
- scanoss/__init__.py +18 -18
- scanoss/api/__init__.py +17 -17
- scanoss/api/common/__init__.py +17 -17
- scanoss/api/common/v2/__init__.py +17 -17
- scanoss/api/common/v2/scanoss_common_pb2.py +18 -18
- scanoss/api/common/v2/scanoss_common_pb2_grpc.py +1 -1
- scanoss/api/components/__init__.py +17 -17
- scanoss/api/components/v2/__init__.py +17 -17
- scanoss/api/components/v2/scanoss_components_pb2.py +48 -38
- scanoss/api/components/v2/scanoss_components_pb2_grpc.py +142 -96
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +22 -16
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +75 -49
- scanoss/api/dependencies/__init__.py +17 -17
- scanoss/api/dependencies/v2/__init__.py +17 -17
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +30 -24
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +75 -49
- scanoss/api/provenance/__init__.py +23 -0
- scanoss/api/provenance/v2/__init__.py +23 -0
- scanoss/api/provenance/v2/scanoss_provenance_pb2.py +42 -0
- scanoss/api/provenance/v2/scanoss_provenance_pb2_grpc.py +108 -0
- scanoss/api/scanning/__init__.py +17 -17
- scanoss/api/scanning/v2/__init__.py +17 -17
- scanoss/api/scanning/v2/scanoss_scanning_pb2.py +10 -8
- scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +40 -32
- scanoss/api/semgrep/__init__.py +17 -17
- scanoss/api/semgrep/v2/__init__.py +17 -17
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +22 -18
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +71 -49
- scanoss/api/vulnerabilities/__init__.py +17 -17
- scanoss/api/vulnerabilities/v2/__init__.py +17 -17
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +37 -27
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +109 -72
- scanoss/cli.py +578 -264
- scanoss/components.py +99 -48
- scanoss/csvoutput.py +83 -56
- scanoss/cyclonedx.py +48 -46
- scanoss/data/build_date.txt +1 -1
- scanoss/file_filters.py +13 -15
- scanoss/filecount.py +43 -36
- scanoss/inspection/__init__.py +17 -17
- scanoss/inspection/copyleft.py +71 -58
- scanoss/inspection/policy_check.py +76 -53
- scanoss/inspection/undeclared_component.py +98 -75
- scanoss/inspection/utils/license_utils.py +66 -44
- scanoss/results.py +51 -60
- scanoss/scancodedeps.py +61 -38
- scanoss/scanner.py +203 -135
- scanoss/scanoss_settings.py +5 -3
- scanoss/scanossapi.py +98 -69
- scanoss/scanossbase.py +19 -19
- scanoss/scanossgrpc.py +107 -51
- scanoss/scanpostprocessor.py +9 -6
- scanoss/scantype.py +22 -21
- scanoss/spdxlite.py +265 -171
- scanoss/threadeddependencies.py +91 -61
- scanoss/threadedscanning.py +37 -31
- scanoss/utils/file.py +4 -4
- scanoss/winnowing.py +111 -47
- {scanoss-1.19.6.dist-info → scanoss-1.20.1.dist-info}/METADATA +1 -1
- scanoss-1.20.1.dist-info/RECORD +74 -0
- scanoss-1.19.6.dist-info/RECORD +0 -70
- {scanoss-1.19.6.dist-info → scanoss-1.20.1.dist-info}/LICENSE +0 -0
- {scanoss-1.19.6.dist-info → scanoss-1.20.1.dist-info}/WHEEL +0 -0
- {scanoss-1.19.6.dist-info → scanoss-1.20.1.dist-info}/entry_points.txt +0 -0
- {scanoss-1.19.6.dist-info → scanoss-1.20.1.dist-info}/top_level.txt +0 -0
scanoss/components.py
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2023, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
|
+
|
|
24
25
|
import json
|
|
25
26
|
import os
|
|
26
27
|
import sys
|
|
27
|
-
from typing import TextIO
|
|
28
|
+
from typing import TextIO, Optional, List
|
|
28
29
|
|
|
29
30
|
from pypac.parser import PACFile
|
|
30
31
|
|
|
@@ -38,10 +39,19 @@ class Components(ScanossBase):
|
|
|
38
39
|
Class for Component functionality
|
|
39
40
|
"""
|
|
40
41
|
|
|
41
|
-
def __init__(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
debug: bool = False,
|
|
45
|
+
trace: bool = False,
|
|
46
|
+
quiet: bool = False,
|
|
47
|
+
grpc_url: str = None,
|
|
48
|
+
api_key: str = None,
|
|
49
|
+
timeout: int = 600,
|
|
50
|
+
proxy: str = None,
|
|
51
|
+
grpc_proxy: str = None,
|
|
52
|
+
ca_cert: str = None,
|
|
53
|
+
pac: PACFile = None,
|
|
54
|
+
):
|
|
45
55
|
"""
|
|
46
56
|
Handle all component style requests
|
|
47
57
|
|
|
@@ -58,17 +68,27 @@ class Components(ScanossBase):
|
|
|
58
68
|
"""
|
|
59
69
|
super().__init__(debug, trace, quiet)
|
|
60
70
|
ver_details = Scanner.version_details()
|
|
61
|
-
self.grpc_api = ScanossGrpc(
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
self.grpc_api = ScanossGrpc(
|
|
72
|
+
url=grpc_url,
|
|
73
|
+
debug=debug,
|
|
74
|
+
quiet=quiet,
|
|
75
|
+
trace=trace,
|
|
76
|
+
api_key=api_key,
|
|
77
|
+
ver_details=ver_details,
|
|
78
|
+
ca_cert=ca_cert,
|
|
79
|
+
proxy=proxy,
|
|
80
|
+
pac=pac,
|
|
81
|
+
grpc_proxy=grpc_proxy,
|
|
82
|
+
timeout=timeout,
|
|
83
|
+
)
|
|
64
84
|
|
|
65
|
-
def load_purls(self, json_file: str = None, purls: [] = None) -> dict:
|
|
85
|
+
def load_purls(self, json_file: Optional[str] = None, purls: Optional[List[str]] = None) -> Optional[dict]:
|
|
66
86
|
"""
|
|
67
87
|
Load the specified purls and return a dictionary
|
|
68
88
|
|
|
69
89
|
:param json_file: JSON PURL file (optional)
|
|
70
90
|
:param purls: list of PURLs (optional)
|
|
71
|
-
:return: PURL Request dictionary
|
|
91
|
+
:return: PURL Request dictionary or None
|
|
72
92
|
"""
|
|
73
93
|
if json_file:
|
|
74
94
|
if not os.path.isfile(json_file) or not os.access(json_file, os.R_OK):
|
|
@@ -81,6 +101,9 @@ class Components(ScanossBase):
|
|
|
81
101
|
self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
|
|
82
102
|
return None
|
|
83
103
|
elif purls:
|
|
104
|
+
if not all(isinstance(purl, str) for purl in purls):
|
|
105
|
+
self.print_stderr('ERROR: PURLs must be a list of strings.')
|
|
106
|
+
return None
|
|
84
107
|
parsed_purls = []
|
|
85
108
|
for p in purls:
|
|
86
109
|
parsed_purls.append({'purl': p})
|
|
@@ -219,9 +242,17 @@ class Components(ScanossBase):
|
|
|
219
242
|
self._close_file(output_file, file)
|
|
220
243
|
return success
|
|
221
244
|
|
|
222
|
-
def search_components(
|
|
223
|
-
|
|
224
|
-
|
|
245
|
+
def search_components(
|
|
246
|
+
self,
|
|
247
|
+
output_file: str = None,
|
|
248
|
+
json_file: str = None,
|
|
249
|
+
search: str = None,
|
|
250
|
+
vendor: str = None,
|
|
251
|
+
comp: str = None,
|
|
252
|
+
package: str = None,
|
|
253
|
+
limit: int = None,
|
|
254
|
+
offset: int = None,
|
|
255
|
+
) -> bool:
|
|
225
256
|
"""
|
|
226
257
|
Search for a component based on the given search criteria
|
|
227
258
|
|
|
@@ -242,16 +273,11 @@ class Components(ScanossBase):
|
|
|
242
273
|
if request is None:
|
|
243
274
|
return False
|
|
244
275
|
else: # Construct a query dictionary from parameters
|
|
245
|
-
request = {
|
|
246
|
-
"search": search,
|
|
247
|
-
"vendor": vendor,
|
|
248
|
-
"component": comp,
|
|
249
|
-
"package": package
|
|
250
|
-
}
|
|
276
|
+
request = {'search': search, 'vendor': vendor, 'component': comp, 'package': package}
|
|
251
277
|
if limit is not None and limit > 0:
|
|
252
|
-
request[
|
|
278
|
+
request['limit'] = limit
|
|
253
279
|
if offset is not None and offset > 0:
|
|
254
|
-
request[
|
|
280
|
+
request['offset'] = offset
|
|
255
281
|
|
|
256
282
|
file = self._open_file_or_sdtout(output_file)
|
|
257
283
|
if file is None:
|
|
@@ -266,8 +292,9 @@ class Components(ScanossBase):
|
|
|
266
292
|
self._close_file(output_file, file)
|
|
267
293
|
return success
|
|
268
294
|
|
|
269
|
-
def get_component_versions(
|
|
270
|
-
|
|
295
|
+
def get_component_versions(
|
|
296
|
+
self, output_file: str = None, json_file: str = None, purl: str = None, limit: int = None
|
|
297
|
+
) -> bool:
|
|
271
298
|
"""
|
|
272
299
|
Search for a component versions based on the given search criteria
|
|
273
300
|
|
|
@@ -284,11 +311,9 @@ class Components(ScanossBase):
|
|
|
284
311
|
if request is None:
|
|
285
312
|
return False
|
|
286
313
|
else: # Construct a query dictionary from parameters
|
|
287
|
-
request = {
|
|
288
|
-
"purl": purl
|
|
289
|
-
}
|
|
314
|
+
request = {'purl': purl}
|
|
290
315
|
if limit is not None and limit > 0:
|
|
291
|
-
request[
|
|
316
|
+
request['limit'] = limit
|
|
292
317
|
|
|
293
318
|
file = self._open_file_or_sdtout(output_file)
|
|
294
319
|
if file is None:
|
|
@@ -302,3 +327,29 @@ class Components(ScanossBase):
|
|
|
302
327
|
self.print_msg(f'Results written to: {output_file}')
|
|
303
328
|
self._close_file(output_file, file)
|
|
304
329
|
return success
|
|
330
|
+
|
|
331
|
+
def get_provenance_details(self, json_file: str = None, purls: [] = None, output_file: str = None) -> bool:
|
|
332
|
+
"""
|
|
333
|
+
Retrieve the semgrep details for the supplied PURLs
|
|
334
|
+
|
|
335
|
+
:param json_file: PURL JSON request file (optional)
|
|
336
|
+
:param purls: PURL request array (optional)
|
|
337
|
+
:param output_file: output filename (optional). Default: STDOUT
|
|
338
|
+
:return: True on success, False otherwise
|
|
339
|
+
"""
|
|
340
|
+
success = False
|
|
341
|
+
purls_request = self.load_purls(json_file, purls)
|
|
342
|
+
if purls_request is None or len(purls_request) == 0:
|
|
343
|
+
return False
|
|
344
|
+
file = self._open_file_or_sdtout(output_file)
|
|
345
|
+
if file is None:
|
|
346
|
+
return False
|
|
347
|
+
self.print_msg('Sending PURLs to Provenance API for decoration...')
|
|
348
|
+
response = self.grpc_api.get_provenance_json(purls_request)
|
|
349
|
+
if response:
|
|
350
|
+
print(json.dumps(response, indent=2, sort_keys=True), file=file)
|
|
351
|
+
success = True
|
|
352
|
+
if output_file:
|
|
353
|
+
self.print_msg(f'Results written to: {output_file}')
|
|
354
|
+
self._close_file(output_file, file)
|
|
355
|
+
return success
|
scanoss/csvoutput.py
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2022, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
|
+
|
|
24
25
|
import json
|
|
25
26
|
import os.path
|
|
26
27
|
import sys
|
|
@@ -59,21 +60,21 @@ class CsvOutput(ScanossBase):
|
|
|
59
60
|
file_details = data.get(f)
|
|
60
61
|
# print(f'File: {f}: {file_details}')
|
|
61
62
|
for d in file_details:
|
|
62
|
-
id_details = d.get(
|
|
63
|
+
id_details = d.get('id')
|
|
63
64
|
if not id_details or id_details == 'none':
|
|
64
65
|
continue
|
|
65
|
-
matched = d.get(
|
|
66
|
-
lines = d.get(
|
|
67
|
-
oss_lines = d.get(
|
|
66
|
+
matched = d.get('matched', '')
|
|
67
|
+
lines = d.get('lines', '').replace(',', ';') # swap comma with semicolon to help basic parsers
|
|
68
|
+
oss_lines = d.get('oss_lines', '').replace(',', ';')
|
|
68
69
|
detected = {}
|
|
69
70
|
if id_details == 'dependency':
|
|
70
|
-
dependencies = d.get(
|
|
71
|
+
dependencies = d.get('dependencies')
|
|
71
72
|
if not dependencies:
|
|
72
73
|
self.print_stderr(f'Warning: No Dependencies found for {f}: {file_details}')
|
|
73
74
|
continue
|
|
74
75
|
for deps in dependencies:
|
|
75
76
|
detected = {}
|
|
76
|
-
purl = deps.get(
|
|
77
|
+
purl = deps.get('purl')
|
|
77
78
|
if not purl:
|
|
78
79
|
self.print_stderr(f'Warning: No PURL found for {f}: {deps}')
|
|
79
80
|
continue
|
|
@@ -84,7 +85,7 @@ class CsvOutput(ScanossBase):
|
|
|
84
85
|
dc = []
|
|
85
86
|
if licenses:
|
|
86
87
|
for lic in licenses:
|
|
87
|
-
name = lic.get(
|
|
88
|
+
name = lic.get('name')
|
|
88
89
|
if name and name not in dc: # Only save the license name once
|
|
89
90
|
dc.append(name)
|
|
90
91
|
if not dc or len(dc) == 0:
|
|
@@ -92,17 +93,23 @@ class CsvOutput(ScanossBase):
|
|
|
92
93
|
else:
|
|
93
94
|
detected['licenses'] = ';'.join(dc)
|
|
94
95
|
# inventory_id,path,usage,detected_component,detected_license,detected_version,detected_latest,purl
|
|
95
|
-
csv_dict.append(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
csv_dict.append(
|
|
97
|
+
{
|
|
98
|
+
'inventory_id': row_id,
|
|
99
|
+
'path': f,
|
|
100
|
+
'detected_usage': id_details,
|
|
101
|
+
'detected_component': detected.get('component'),
|
|
102
|
+
'detected_license': detected.get('licenses'),
|
|
103
|
+
'detected_version': detected.get('version'),
|
|
104
|
+
'detected_latest': detected.get('latest'),
|
|
105
|
+
'detected_purls': detected.get('purls'),
|
|
106
|
+
'detected_url': detected.get('url'),
|
|
107
|
+
'detected_path': detected.get('file', ''),
|
|
108
|
+
'detected_match': matched,
|
|
109
|
+
'detected_lines': lines,
|
|
110
|
+
'detected_oss_lines': oss_lines,
|
|
111
|
+
}
|
|
112
|
+
)
|
|
106
113
|
row_id = row_id + 1
|
|
107
114
|
else:
|
|
108
115
|
purls = d.get('purl')
|
|
@@ -123,25 +130,31 @@ class CsvOutput(ScanossBase):
|
|
|
123
130
|
dc = []
|
|
124
131
|
if licenses:
|
|
125
132
|
for lic in licenses:
|
|
126
|
-
name = lic.get(
|
|
133
|
+
name = lic.get('name')
|
|
127
134
|
if name and name not in dc: # Only save the license name once
|
|
128
|
-
dc.append(lic.get(
|
|
135
|
+
dc.append(lic.get('name'))
|
|
129
136
|
if not dc or len(dc) == 0:
|
|
130
137
|
detected['licenses'] = ''
|
|
131
138
|
else:
|
|
132
139
|
detected['licenses'] = ';'.join(dc)
|
|
133
140
|
# inventory_id,path,usage,detected_component,detected_license,detected_version,detected_latest,purl
|
|
134
|
-
csv_dict.append(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
csv_dict.append(
|
|
142
|
+
{
|
|
143
|
+
'inventory_id': row_id,
|
|
144
|
+
'path': f,
|
|
145
|
+
'detected_usage': id_details,
|
|
146
|
+
'detected_component': detected.get('component'),
|
|
147
|
+
'detected_license': detected.get('licenses'),
|
|
148
|
+
'detected_version': detected.get('version'),
|
|
149
|
+
'detected_latest': detected.get('latest'),
|
|
150
|
+
'detected_purls': detected.get('purls'),
|
|
151
|
+
'detected_url': detected.get('url'),
|
|
152
|
+
'detected_path': detected.get('file', ''),
|
|
153
|
+
'detected_match': matched,
|
|
154
|
+
'detected_lines': lines,
|
|
155
|
+
'detected_oss_lines': oss_lines,
|
|
156
|
+
}
|
|
157
|
+
)
|
|
145
158
|
row_id = row_id + 1
|
|
146
159
|
return csv_dict
|
|
147
160
|
|
|
@@ -174,16 +187,28 @@ class CsvOutput(ScanossBase):
|
|
|
174
187
|
self.print_stderr('ERROR: No CSV data returned for the JSON string provided.')
|
|
175
188
|
return False
|
|
176
189
|
# Header row/column details
|
|
177
|
-
fields = [
|
|
178
|
-
|
|
179
|
-
|
|
190
|
+
fields = [
|
|
191
|
+
'inventory_id',
|
|
192
|
+
'path',
|
|
193
|
+
'detected_usage',
|
|
194
|
+
'detected_component',
|
|
195
|
+
'detected_license',
|
|
196
|
+
'detected_version',
|
|
197
|
+
'detected_latest',
|
|
198
|
+
'detected_purls',
|
|
199
|
+
'detected_url',
|
|
200
|
+
'detected_match',
|
|
201
|
+
'detected_lines',
|
|
202
|
+
'detected_oss_lines',
|
|
203
|
+
'detected_path',
|
|
204
|
+
]
|
|
180
205
|
file = sys.stdout
|
|
181
206
|
if not output_file and self.output_file:
|
|
182
207
|
output_file = self.output_file
|
|
183
208
|
if output_file:
|
|
184
209
|
file = open(output_file, 'w')
|
|
185
210
|
writer = csv.DictWriter(file, fieldnames=fields)
|
|
186
|
-
writer.writeheader()
|
|
211
|
+
writer.writeheader() # writing headers (field names)
|
|
187
212
|
writer.writerows(csv_data) # writing data rows
|
|
188
213
|
if output_file:
|
|
189
214
|
file.close()
|
|
@@ -206,6 +231,8 @@ class CsvOutput(ScanossBase):
|
|
|
206
231
|
self.print_stderr(f'ERROR: Problem parsing input JSON: {e}')
|
|
207
232
|
return False
|
|
208
233
|
return self.produce_from_json(data, output_file)
|
|
234
|
+
|
|
235
|
+
|
|
209
236
|
#
|
|
210
237
|
# End of CsvOutput Class
|
|
211
238
|
#
|
scanoss/cyclonedx.py
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
Copyright (c) 2021, SCANOSS
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
|
+
|
|
24
25
|
import json
|
|
25
26
|
import os.path
|
|
26
27
|
import sys
|
|
@@ -64,17 +65,17 @@ class CycloneDx(ScanossBase):
|
|
|
64
65
|
file_details = data.get(f)
|
|
65
66
|
# print(f'File: {f}: {file_details}')
|
|
66
67
|
for d in file_details:
|
|
67
|
-
id_details = d.get(
|
|
68
|
+
id_details = d.get('id')
|
|
68
69
|
if not id_details or id_details == 'none':
|
|
69
70
|
continue
|
|
70
71
|
purl = None
|
|
71
72
|
if id_details == 'dependency':
|
|
72
|
-
dependencies = d.get(
|
|
73
|
+
dependencies = d.get('dependencies')
|
|
73
74
|
if not dependencies:
|
|
74
75
|
self.print_stderr(f'Warning: No Dependencies found for {f}: {file_details}')
|
|
75
76
|
continue
|
|
76
77
|
for deps in dependencies:
|
|
77
|
-
purl = deps.get(
|
|
78
|
+
purl = deps.get('purl')
|
|
78
79
|
if not purl:
|
|
79
80
|
self.print_stderr(f'Warning: No PURL found for {f}: {deps}')
|
|
80
81
|
continue
|
|
@@ -89,7 +90,7 @@ class CycloneDx(ScanossBase):
|
|
|
89
90
|
if licenses:
|
|
90
91
|
dc = []
|
|
91
92
|
for lic in licenses:
|
|
92
|
-
name = lic.get(
|
|
93
|
+
name = lic.get('name')
|
|
93
94
|
if name not in dc: # Only save the license name once
|
|
94
95
|
fdl.append({'id': name})
|
|
95
96
|
dc.append(name)
|
|
@@ -108,30 +109,33 @@ class CycloneDx(ScanossBase):
|
|
|
108
109
|
self.print_stderr(f'Warning: No PURL found for {f}: {file_details}')
|
|
109
110
|
continue
|
|
110
111
|
fd = {}
|
|
111
|
-
vulnerabilities = d.get(
|
|
112
|
+
vulnerabilities = d.get('vulnerabilities')
|
|
112
113
|
if vulnerabilities:
|
|
113
114
|
for vuln in vulnerabilities:
|
|
114
|
-
vuln_id = vuln.get(
|
|
115
|
+
vuln_id = vuln.get('ID')
|
|
115
116
|
if vuln_id == '':
|
|
116
|
-
vuln_id = vuln.get(
|
|
117
|
+
vuln_id = vuln.get('id')
|
|
117
118
|
if not vuln_id or vuln_id == '': # Skip empty ids
|
|
118
119
|
continue
|
|
119
|
-
vuln_cve = vuln.get(
|
|
120
|
+
vuln_cve = vuln.get('CVE', '')
|
|
120
121
|
if vuln_cve == '':
|
|
121
|
-
vuln_cve = vuln.get(
|
|
122
|
-
if vuln_id.upper().startswith(
|
|
123
|
-
fd['cpe'] = vuln_id
|
|
122
|
+
vuln_cve = vuln.get('cve', '')
|
|
123
|
+
if vuln_id.upper().startswith('CPE:'):
|
|
124
|
+
fd['cpe'] = vuln_id # Save the component CPE if we have one
|
|
124
125
|
if vuln_cve != '':
|
|
125
126
|
vuln_id = vuln_cve
|
|
126
127
|
vd = vdx.get(vuln_id) # Check if we've already encountered this vulnerability
|
|
127
128
|
if not vd:
|
|
128
129
|
vuln_source = vuln.get('source', '').lower()
|
|
129
|
-
vd = {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
vd = {
|
|
131
|
+
'cve': vuln_cve,
|
|
132
|
+
'source': 'NVD' if vuln_source == 'nvd' else 'GitHub Advisories',
|
|
133
|
+
'url': f'https://nvd.nist.gov/vuln/detail/{vuln_cve}'
|
|
134
|
+
if vuln_source == 'nvd'
|
|
135
|
+
else f'https://github.com/advisories/{vuln_id}',
|
|
136
|
+
'severity': self._sev_lookup(vuln.get('severity', 'unknown').lower()),
|
|
137
|
+
'affects': set(),
|
|
138
|
+
}
|
|
135
139
|
vd.get('affects').add(purl)
|
|
136
140
|
vdx[vuln_id] = vd
|
|
137
141
|
if cdx.get(purl):
|
|
@@ -143,7 +147,7 @@ class CycloneDx(ScanossBase):
|
|
|
143
147
|
fdl = []
|
|
144
148
|
if licenses:
|
|
145
149
|
for lic in licenses:
|
|
146
|
-
fdl.append({'id': lic.get(
|
|
150
|
+
fdl.append({'id': lic.get('name')})
|
|
147
151
|
fd['licenses'] = fdl
|
|
148
152
|
cdx[purl] = fd
|
|
149
153
|
# self.print_stderr(f'VD: {vdx}')
|
|
@@ -190,7 +194,7 @@ class CycloneDx(ScanossBase):
|
|
|
190
194
|
'serialNumber': f'urn:uuid:{uuid.uuid4()}',
|
|
191
195
|
'version': 1,
|
|
192
196
|
'metadata': {
|
|
193
|
-
'timestamp': datetime.datetime.now(datetime.timezone.utc).strftime(
|
|
197
|
+
'timestamp': datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
194
198
|
'tools': [
|
|
195
199
|
{
|
|
196
200
|
'vendor': 'SCANOSS',
|
|
@@ -198,14 +202,10 @@ class CycloneDx(ScanossBase):
|
|
|
198
202
|
'version': __version__,
|
|
199
203
|
}
|
|
200
204
|
],
|
|
201
|
-
'component': {
|
|
202
|
-
'type': 'application',
|
|
203
|
-
'name': 'NOASSERTION',
|
|
204
|
-
'version': 'NOASSERTION'
|
|
205
|
-
}
|
|
205
|
+
'component': {'type': 'application', 'name': 'NOASSERTION', 'version': 'NOASSERTION'},
|
|
206
206
|
},
|
|
207
207
|
'components': [],
|
|
208
|
-
'vulnerabilities': []
|
|
208
|
+
'vulnerabilities': [],
|
|
209
209
|
}
|
|
210
210
|
for purl in cdx:
|
|
211
211
|
comp = cdx.get(purl)
|
|
@@ -230,7 +230,7 @@ class CycloneDx(ScanossBase):
|
|
|
230
230
|
'version': comp.get('version'),
|
|
231
231
|
'purl': purl,
|
|
232
232
|
'bom-ref': purl,
|
|
233
|
-
'licenses': lic_text
|
|
233
|
+
'licenses': lic_text,
|
|
234
234
|
}
|
|
235
235
|
cpe = comp.get('cpe', '')
|
|
236
236
|
if cpe and cpe != '':
|
|
@@ -250,7 +250,7 @@ class CycloneDx(ScanossBase):
|
|
|
250
250
|
'id': vuln_id,
|
|
251
251
|
'source': {'name': v_source, 'url': vulns.get('url')},
|
|
252
252
|
'ratings': [{'severity': vulns.get('severity', 'unknown')}],
|
|
253
|
-
'affects': affects
|
|
253
|
+
'affects': affects,
|
|
254
254
|
}
|
|
255
255
|
data['vulnerabilities'].append(vd)
|
|
256
256
|
# End for loop
|
|
@@ -298,8 +298,10 @@ class CycloneDx(ScanossBase):
|
|
|
298
298
|
'low': 'low',
|
|
299
299
|
'info': 'info',
|
|
300
300
|
'none': 'none',
|
|
301
|
-
'unknown': 'unknown'
|
|
302
|
-
|
|
301
|
+
'unknown': 'unknown',
|
|
302
|
+
}.get(value, 'unknown')
|
|
303
|
+
|
|
304
|
+
|
|
303
305
|
#
|
|
304
306
|
# End of CycloneDX Class
|
|
305
307
|
#
|
scanoss/data/build_date.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
date:
|
|
1
|
+
date: 20250219134430, utime: 1739972670
|