socketsecurity 0.0.42__tar.gz → 0.0.44__tar.gz
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.
- socketsecurity-0.0.44/PKG-INFO +6 -0
- socketsecurity-0.0.44/setup.py +17 -0
- socketsecurity-0.0.44/socketsecurity/__init__.py +2 -0
- {socketsecurity-0.0.42 → socketsecurity-0.0.44}/socketsecurity/core/__init__.py +95 -26
- {socketsecurity-0.0.42 → socketsecurity-0.0.44}/socketsecurity/core/classes.py +26 -4
- socketsecurity-0.0.44/socketsecurity/core/github.py +327 -0
- socketsecurity-0.0.44/socketsecurity/core/glitlab.py +327 -0
- socketsecurity-0.0.44/socketsecurity/core/issues.py +2079 -0
- socketsecurity-0.0.44/socketsecurity/core/licenses.py +21574 -0
- {socketsecurity-0.0.42 → socketsecurity-0.0.44}/socketsecurity/core/messages.py +17 -11
- socketsecurity-0.0.44/socketsecurity/socketcli.py +176 -0
- socketsecurity-0.0.44/socketsecurity.egg-info/PKG-INFO +6 -0
- {socketsecurity-0.0.42 → socketsecurity-0.0.44}/socketsecurity.egg-info/SOURCES.txt +4 -0
- socketsecurity-0.0.44/socketsecurity.egg-info/entry_points.txt +2 -0
- socketsecurity-0.0.44/socketsecurity.egg-info/requires.txt +3 -0
- socketsecurity-0.0.42/PKG-INFO +0 -7
- socketsecurity-0.0.42/setup.py +0 -16
- socketsecurity-0.0.42/socketsecurity/__init__.py +0 -1
- socketsecurity-0.0.42/socketsecurity/socketcli.py +0 -50
- socketsecurity-0.0.42/socketsecurity.egg-info/PKG-INFO +0 -7
- socketsecurity-0.0.42/socketsecurity.egg-info/entry_points.txt +0 -2
- socketsecurity-0.0.42/socketsecurity.egg-info/requires.txt +0 -4
- {socketsecurity-0.0.42 → socketsecurity-0.0.44}/setup.cfg +0 -0
- {socketsecurity-0.0.42 → socketsecurity-0.0.44}/socketsecurity/core/exceptions.py +0 -0
- {socketsecurity-0.0.42 → socketsecurity-0.0.44}/socketsecurity.egg-info/dependency_links.txt +0 -0
- {socketsecurity-0.0.42 → socketsecurity-0.0.44}/socketsecurity.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name='socketsecurity',
|
|
5
|
+
version='0.0.44',
|
|
6
|
+
packages=find_packages(),
|
|
7
|
+
install_requires=[
|
|
8
|
+
'requests',
|
|
9
|
+
'mdutils'
|
|
10
|
+
'prettytable',
|
|
11
|
+
'argparse'
|
|
12
|
+
],
|
|
13
|
+
entry_points='''
|
|
14
|
+
[console_scripts]
|
|
15
|
+
socketcli=socketsecurity.socketcli:main
|
|
16
|
+
''',
|
|
17
|
+
)
|
|
@@ -4,6 +4,8 @@ from urllib.parse import urlencode
|
|
|
4
4
|
import base64
|
|
5
5
|
import json
|
|
6
6
|
from core.exceptions import APIFailure, APIKeyMissing, APIAccessDenied, APIInsufficientQuota, APIResourceNotFound
|
|
7
|
+
from core.licenses import Licenses
|
|
8
|
+
from core.issues import AllIssues
|
|
7
9
|
from core.classes import (
|
|
8
10
|
Report,
|
|
9
11
|
Issue,
|
|
@@ -21,7 +23,7 @@ import time
|
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
__author__ = 'socket.dev'
|
|
24
|
-
__version__ = '0.0.
|
|
26
|
+
__version__ = '0.0.44'
|
|
25
27
|
__all__ = [
|
|
26
28
|
"Core",
|
|
27
29
|
"log"
|
|
@@ -34,31 +36,16 @@ api_url = "https://api.socket.dev/v0"
|
|
|
34
36
|
timeout = 30
|
|
35
37
|
full_scan_path = ""
|
|
36
38
|
repository_path = ""
|
|
39
|
+
all_issues = AllIssues()
|
|
37
40
|
org_id = None
|
|
38
41
|
org_slug = None
|
|
39
42
|
all_new_alerts = False
|
|
40
43
|
issue_file = "core/issues.json"
|
|
41
|
-
issue_text = {}
|
|
42
44
|
security_policy = {}
|
|
43
45
|
log = logging.getLogger("socketdev")
|
|
44
46
|
log.addHandler(logging.NullHandler())
|
|
45
47
|
|
|
46
48
|
|
|
47
|
-
def load_issue_data() -> dict:
|
|
48
|
-
"""
|
|
49
|
-
Loads the issue data json and returns it as a dictionary. This data is used in the alert messages.
|
|
50
|
-
:return:
|
|
51
|
-
"""
|
|
52
|
-
try:
|
|
53
|
-
file = open(issue_file, 'r')
|
|
54
|
-
data = json.load(file)
|
|
55
|
-
return data
|
|
56
|
-
except Exception as error:
|
|
57
|
-
print("Unable to load issue data")
|
|
58
|
-
print(error)
|
|
59
|
-
exit(1)
|
|
60
|
-
|
|
61
|
-
|
|
62
49
|
def encode_key(token: str) -> None:
|
|
63
50
|
"""
|
|
64
51
|
encode_key takes passed token string and does a base64 encoding. It sets this as a global variable
|
|
@@ -140,8 +127,6 @@ class Core:
|
|
|
140
127
|
def __init__(self, token: str, base_api_url=None, request_timeout=None, enable_all_alerts=False):
|
|
141
128
|
self.token = token + ":"
|
|
142
129
|
encode_key(self.token)
|
|
143
|
-
global issue_text
|
|
144
|
-
issue_text = load_issue_data()
|
|
145
130
|
self.socket_date_format = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
146
131
|
self.base_api_url = base_api_url
|
|
147
132
|
if self.base_api_url is not None:
|
|
@@ -262,7 +247,6 @@ class Core:
|
|
|
262
247
|
Globs the path for supported manifest files.
|
|
263
248
|
Note: Might move the source to a JSON file
|
|
264
249
|
:param path: Str - path to where the manifest files are located
|
|
265
|
-
:param workspace: str - workspace path to be stripped from the name
|
|
266
250
|
:return:
|
|
267
251
|
"""
|
|
268
252
|
socket_globs = {
|
|
@@ -387,6 +371,16 @@ class Core:
|
|
|
387
371
|
full_scan.sbom_artifacts = Core.get_sbom_data(full_scan.id)
|
|
388
372
|
return full_scan
|
|
389
373
|
|
|
374
|
+
@staticmethod
|
|
375
|
+
def get_license_details(package: Package) -> Package:
|
|
376
|
+
license_raw = package.license
|
|
377
|
+
all_licenses = Licenses()
|
|
378
|
+
license_str = Licenses.make_python_safe(license_raw)
|
|
379
|
+
if license_str is not None and hasattr(all_licenses, license_str):
|
|
380
|
+
license_obj = getattr(all_licenses, license_str)
|
|
381
|
+
package.license_text = license_obj.licenseText
|
|
382
|
+
return package
|
|
383
|
+
|
|
390
384
|
@staticmethod
|
|
391
385
|
def get_head_scan_for_repo(repo_slug: str):
|
|
392
386
|
"""
|
|
@@ -442,10 +436,12 @@ class Core:
|
|
|
442
436
|
if files is not None and len(files) > 0:
|
|
443
437
|
new_scan_start = time.time()
|
|
444
438
|
new_full_scan = Core.create_full_scan(files, params, workspace)
|
|
439
|
+
new_full_scan.packages = Core.create_sbom_dict(new_full_scan.sbom_artifacts)
|
|
445
440
|
new_scan_end = time.time()
|
|
446
441
|
total_new_time = new_scan_end - new_scan_start
|
|
447
442
|
print(f"Total time to get new sbom {total_new_time: .2f}")
|
|
448
443
|
diff_report = Core.compare_sboms(new_full_scan.sbom_artifacts, head_full_scan)
|
|
444
|
+
diff_report.packages = new_full_scan.packages
|
|
449
445
|
else:
|
|
450
446
|
diff_report = Diff()
|
|
451
447
|
return diff_report
|
|
@@ -477,8 +473,69 @@ class Core:
|
|
|
477
473
|
diff.removed_packages.append(purl)
|
|
478
474
|
head_scan_alerts = Core.create_issue_alerts(package, head_scan_alerts, head_packages)
|
|
479
475
|
diff.new_alerts = Core.compare_issue_alerts(new_scan_alerts, head_scan_alerts, diff.new_alerts)
|
|
476
|
+
diff.new_capabilities = Core.compare_capabilities(new_packages, head_packages)
|
|
477
|
+
diff = Core.add_capabilities_to_purl(diff)
|
|
478
|
+
return diff
|
|
479
|
+
|
|
480
|
+
@staticmethod
|
|
481
|
+
def add_capabilities_to_purl(diff: Diff) -> Diff:
|
|
482
|
+
new_packages = []
|
|
483
|
+
for purl in diff.new_packages:
|
|
484
|
+
purl: Purl
|
|
485
|
+
if purl.id in diff.new_capabilities:
|
|
486
|
+
capabilities = diff.new_capabilities[purl.id]
|
|
487
|
+
if len(capabilities) > 0:
|
|
488
|
+
purl.capabilities = capabilities
|
|
489
|
+
new_packages.append(purl)
|
|
490
|
+
else:
|
|
491
|
+
new_packages.append(purl)
|
|
492
|
+
diff.new_packages = new_packages
|
|
480
493
|
return diff
|
|
481
494
|
|
|
495
|
+
@staticmethod
|
|
496
|
+
def compare_capabilities(new_packages: dict, head_packages: dict) -> dict:
|
|
497
|
+
capabilities = {}
|
|
498
|
+
for package_id in new_packages:
|
|
499
|
+
package: Package
|
|
500
|
+
head_package: Package
|
|
501
|
+
package = new_packages[package_id]
|
|
502
|
+
if package_id in head_packages:
|
|
503
|
+
head_package = head_packages[package_id]
|
|
504
|
+
for alert in package.alerts:
|
|
505
|
+
if alert not in head_package.alerts:
|
|
506
|
+
capabilities = Core.check_alert_capabilities(package, capabilities, package_id, head_package)
|
|
507
|
+
else:
|
|
508
|
+
capabilities = Core.check_alert_capabilities(package, capabilities, package_id)
|
|
509
|
+
|
|
510
|
+
return capabilities
|
|
511
|
+
|
|
512
|
+
@staticmethod
|
|
513
|
+
def check_alert_capabilities(
|
|
514
|
+
package: Package,
|
|
515
|
+
capabilities: dict,
|
|
516
|
+
package_id: str,
|
|
517
|
+
head_package: Package = None
|
|
518
|
+
) -> dict:
|
|
519
|
+
alert_types = {
|
|
520
|
+
"envVars": "Environment",
|
|
521
|
+
"networkAccess": "Network",
|
|
522
|
+
"filesystemAccess": "File System",
|
|
523
|
+
"shellAccess": "Shell"
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
for alert in package.alerts:
|
|
527
|
+
new_alert = True
|
|
528
|
+
if head_package is not None and alert in head_package.alerts:
|
|
529
|
+
new_alert = False
|
|
530
|
+
if alert["type"] in alert_types and new_alert:
|
|
531
|
+
value = alert_types[alert["type"]]
|
|
532
|
+
if package_id not in capabilities:
|
|
533
|
+
capabilities[package_id] = [value]
|
|
534
|
+
else:
|
|
535
|
+
if value not in capabilities[package_id]:
|
|
536
|
+
capabilities[package_id].append(value)
|
|
537
|
+
return capabilities
|
|
538
|
+
|
|
482
539
|
@staticmethod
|
|
483
540
|
def compare_issue_alerts(new_scan_alerts: dict, head_scan_alerts: dict, alerts: list) -> list:
|
|
484
541
|
"""
|
|
@@ -528,12 +585,12 @@ class Core:
|
|
|
528
585
|
"""
|
|
529
586
|
for item in package.alerts:
|
|
530
587
|
alert = Alert(**item)
|
|
531
|
-
props =
|
|
588
|
+
props = getattr(all_issues, alert.type)
|
|
532
589
|
if props is not None:
|
|
533
|
-
description = props.
|
|
534
|
-
title = props.
|
|
535
|
-
suggestion = props.
|
|
536
|
-
next_step_title = props.
|
|
590
|
+
description = props.description
|
|
591
|
+
title = props.title
|
|
592
|
+
suggestion = props.suggestion
|
|
593
|
+
next_step_title = props.nextStepTitle
|
|
537
594
|
else:
|
|
538
595
|
description = ""
|
|
539
596
|
title = ""
|
|
@@ -611,7 +668,7 @@ class Core:
|
|
|
611
668
|
ecosystem=package.type,
|
|
612
669
|
direct=package.direct,
|
|
613
670
|
introduced_by=introduced_by,
|
|
614
|
-
author=package.author or
|
|
671
|
+
author=package.author or [],
|
|
615
672
|
size=package.size,
|
|
616
673
|
transitives=package.transitives
|
|
617
674
|
)
|
|
@@ -631,6 +688,7 @@ class Core:
|
|
|
631
688
|
if package.id in packages:
|
|
632
689
|
print("Duplicate package?")
|
|
633
690
|
else:
|
|
691
|
+
package = Core.get_license_details(package)
|
|
634
692
|
packages[package.id] = package
|
|
635
693
|
for top_id in package.topLevelAncestors:
|
|
636
694
|
if top_id not in top_level_count:
|
|
@@ -642,3 +700,14 @@ class Core:
|
|
|
642
700
|
packages[package_id].transitives = top_level_count[package_id]
|
|
643
701
|
return packages
|
|
644
702
|
|
|
703
|
+
@staticmethod
|
|
704
|
+
def save_file(file_name: str, content: str) -> None:
|
|
705
|
+
file = open(file_name, "w")
|
|
706
|
+
file.write(content)
|
|
707
|
+
file.close()
|
|
708
|
+
|
|
709
|
+
# @staticmethod
|
|
710
|
+
# def create_license_file(diff: Diff) -> None:
|
|
711
|
+
# output = []
|
|
712
|
+
# for package_id in diff.packages:
|
|
713
|
+
# purl = Core.create_purl(package_id, diff.packages)
|
|
@@ -84,6 +84,8 @@ class Package:
|
|
|
84
84
|
topLevelAncestors: list
|
|
85
85
|
url: str
|
|
86
86
|
transitives: int
|
|
87
|
+
license: str
|
|
88
|
+
license_text: str
|
|
87
89
|
|
|
88
90
|
def __init__(self, **kwargs):
|
|
89
91
|
if kwargs:
|
|
@@ -99,16 +101,16 @@ class Package:
|
|
|
99
101
|
self.scores = Score(**self.score)
|
|
100
102
|
if not hasattr(self, "alerts"):
|
|
101
103
|
self.alerts = []
|
|
102
|
-
if not hasattr(self, "author"):
|
|
103
|
-
self.author = []
|
|
104
|
-
if not hasattr(self, "size"):
|
|
105
|
-
self.size = 0
|
|
106
104
|
if not hasattr(self, "topLevelAncestors"):
|
|
107
105
|
self.topLevelAncestors = []
|
|
108
106
|
if not hasattr(self, "manifestFiles"):
|
|
109
107
|
self.manifestFiles = []
|
|
110
108
|
if not hasattr(self, "transitives"):
|
|
111
109
|
self.transitives = 0
|
|
110
|
+
if not hasattr(self, "author"):
|
|
111
|
+
self.author = []
|
|
112
|
+
if not hasattr(self, "size"):
|
|
113
|
+
self.size = 0
|
|
112
114
|
self.alert_counts = {
|
|
113
115
|
"critical": 0,
|
|
114
116
|
"high": 0,
|
|
@@ -116,6 +118,10 @@ class Package:
|
|
|
116
118
|
"low": 0
|
|
117
119
|
}
|
|
118
120
|
self.error_alerts = []
|
|
121
|
+
if not hasattr(self, "license"):
|
|
122
|
+
self.license = "NoLicenseFound"
|
|
123
|
+
if not hasattr(self, "license_text"):
|
|
124
|
+
self.license_text = ""
|
|
119
125
|
|
|
120
126
|
def __str__(self):
|
|
121
127
|
return json.dumps(self.__dict__)
|
|
@@ -229,6 +235,7 @@ class FullScan:
|
|
|
229
235
|
commit_hash: str
|
|
230
236
|
pull_request: int
|
|
231
237
|
sbom_artifacts: list
|
|
238
|
+
packages: dict
|
|
232
239
|
|
|
233
240
|
def __init__(self, **kwargs):
|
|
234
241
|
if kwargs:
|
|
@@ -283,10 +290,12 @@ class FullScanParams:
|
|
|
283
290
|
|
|
284
291
|
class Diff:
|
|
285
292
|
new_packages: list
|
|
293
|
+
new_capabilities: dict
|
|
286
294
|
removed_packages: list
|
|
287
295
|
new_alerts: list
|
|
288
296
|
id: str
|
|
289
297
|
sbom: str
|
|
298
|
+
packages: dict
|
|
290
299
|
|
|
291
300
|
def __init__(self, **kwargs):
|
|
292
301
|
if kwargs:
|
|
@@ -298,6 +307,8 @@ class Diff:
|
|
|
298
307
|
self.removed_packages = []
|
|
299
308
|
if not hasattr(self, "new_alerts"):
|
|
300
309
|
self.new_alerts = []
|
|
310
|
+
if not hasattr(self, "new_capabilities"):
|
|
311
|
+
self.new_capabilities = {}
|
|
301
312
|
|
|
302
313
|
def __str__(self):
|
|
303
314
|
return json.dumps(self.__dict__)
|
|
@@ -313,6 +324,8 @@ class Purl:
|
|
|
313
324
|
size: int
|
|
314
325
|
transitives: int
|
|
315
326
|
introduced_by: list
|
|
327
|
+
capabilities: dict
|
|
328
|
+
is_new: bool
|
|
316
329
|
|
|
317
330
|
def __init__(self, **kwargs):
|
|
318
331
|
if kwargs:
|
|
@@ -320,6 +333,10 @@ class Purl:
|
|
|
320
333
|
setattr(self, key, value)
|
|
321
334
|
if not hasattr(self, "introduced_by"):
|
|
322
335
|
self.new_packages = []
|
|
336
|
+
if not hasattr(self, "capabilities"):
|
|
337
|
+
self.capabilities = {}
|
|
338
|
+
if not hasattr(self, "is_new"):
|
|
339
|
+
self.is_new = False
|
|
323
340
|
|
|
324
341
|
def __str__(self):
|
|
325
342
|
return json.dumps(self.__dict__)
|
|
@@ -349,3 +366,8 @@ class GithubComment:
|
|
|
349
366
|
|
|
350
367
|
def __str__(self):
|
|
351
368
|
return json.dumps(self.__dict__)
|
|
369
|
+
|
|
370
|
+
class LicenseOutput(Purl):
|
|
371
|
+
licenses: list
|
|
372
|
+
license
|
|
373
|
+
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from core import log
|
|
4
|
+
import requests
|
|
5
|
+
from core.exceptions import *
|
|
6
|
+
from core.classes import GithubComment, Diff, Issue
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
global github_sha
|
|
11
|
+
global github_api_url
|
|
12
|
+
global github_ref_type
|
|
13
|
+
global github_event_name
|
|
14
|
+
global github_workspace
|
|
15
|
+
global github_repository
|
|
16
|
+
global github_ref_name
|
|
17
|
+
global github_actor
|
|
18
|
+
global default_branch
|
|
19
|
+
global github_env
|
|
20
|
+
global pr_number
|
|
21
|
+
global pr_name
|
|
22
|
+
global is_default_branch
|
|
23
|
+
global commit_message
|
|
24
|
+
global committer
|
|
25
|
+
global gh_api_token
|
|
26
|
+
global github_repository_owner
|
|
27
|
+
|
|
28
|
+
github_variables = [
|
|
29
|
+
"GITHUB_SHA",
|
|
30
|
+
"GITHUB_API_URL",
|
|
31
|
+
"GITHUB_REF_TYPE",
|
|
32
|
+
"GITHUB_EVENT_NAME",
|
|
33
|
+
"GITHUB_WORKSPACE",
|
|
34
|
+
"GITHUB_REPOSITORY",
|
|
35
|
+
"GITHUB_REF_NAME",
|
|
36
|
+
"DEFAULT_BRANCH",
|
|
37
|
+
"PR_NUMBER",
|
|
38
|
+
"PR_NAME",
|
|
39
|
+
"COMMIT_MESSAGE",
|
|
40
|
+
"GITHUB_ACTOR",
|
|
41
|
+
"GITHUB_ENV",
|
|
42
|
+
"GH_API_TOKEN",
|
|
43
|
+
"GITHUB_REPOSITORY_OWNER"
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
for env in github_variables:
|
|
47
|
+
var_name = env.lower()
|
|
48
|
+
globals()[var_name] = os.getenv(env) or None
|
|
49
|
+
if var_name == "default_branch":
|
|
50
|
+
global is_default_branch
|
|
51
|
+
if default_branch is None or default_branch.lower() == "false":
|
|
52
|
+
is_default_branch = False
|
|
53
|
+
else:
|
|
54
|
+
is_default_branch = True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def do_request(
|
|
58
|
+
path: str,
|
|
59
|
+
headers: dict = None,
|
|
60
|
+
payload: [dict, str] = None,
|
|
61
|
+
files: list = None,
|
|
62
|
+
method: str = "GET",
|
|
63
|
+
) -> dict:
|
|
64
|
+
"""
|
|
65
|
+
do_requests is the shared function for making HTTP calls
|
|
66
|
+
|
|
67
|
+
:param path: Required path for the request
|
|
68
|
+
:param headers: Optional dictionary of headers. If not set will use a default set
|
|
69
|
+
:param payload: Optional dictionary or string of the payload to pass
|
|
70
|
+
:param files: Optional list of files to upload
|
|
71
|
+
:param method: Optional method to use, defaults to GET
|
|
72
|
+
:return:
|
|
73
|
+
"""
|
|
74
|
+
if gh_api_token is None or gh_api_token == "":
|
|
75
|
+
raise APIKeyMissing
|
|
76
|
+
|
|
77
|
+
if headers is None:
|
|
78
|
+
headers = {
|
|
79
|
+
'Authorization': f"Bearer {gh_api_token}",
|
|
80
|
+
'User-Agent': 'SocketPythonScript/0.0.1',
|
|
81
|
+
"accept": "application/json"
|
|
82
|
+
}
|
|
83
|
+
url = f"{github_api_url}/{path}"
|
|
84
|
+
response = requests.request(
|
|
85
|
+
method.upper(),
|
|
86
|
+
url,
|
|
87
|
+
headers=headers,
|
|
88
|
+
data=payload,
|
|
89
|
+
files=files
|
|
90
|
+
)
|
|
91
|
+
if response.status_code <= 399:
|
|
92
|
+
try:
|
|
93
|
+
return response.json()
|
|
94
|
+
except Exception as error:
|
|
95
|
+
response = {
|
|
96
|
+
"error": error,
|
|
97
|
+
"response": response.text
|
|
98
|
+
}
|
|
99
|
+
return response
|
|
100
|
+
else:
|
|
101
|
+
msg = {
|
|
102
|
+
"status_code": response.status_code,
|
|
103
|
+
"UnexpectedError": "There was an unexpected error using the API",
|
|
104
|
+
"error": response.text
|
|
105
|
+
}
|
|
106
|
+
raise APIFailure(msg)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class Github:
|
|
110
|
+
commit_sha: str
|
|
111
|
+
api_url: str
|
|
112
|
+
ref_type: str
|
|
113
|
+
event_name: str
|
|
114
|
+
workspace: str
|
|
115
|
+
repository: str
|
|
116
|
+
ref_name: str
|
|
117
|
+
default_branch: str
|
|
118
|
+
is_default_branch: bool
|
|
119
|
+
pr_number: int
|
|
120
|
+
pr_name: str
|
|
121
|
+
commit_message: str
|
|
122
|
+
committer: str
|
|
123
|
+
github_env: str
|
|
124
|
+
api_token: str
|
|
125
|
+
|
|
126
|
+
def __init__(self):
|
|
127
|
+
self.commit_sha = github_sha
|
|
128
|
+
self.api_url = github_api_url
|
|
129
|
+
self.ref_type = github_ref_type
|
|
130
|
+
self.event_name = github_event_name
|
|
131
|
+
self.workspace = github_workspace
|
|
132
|
+
self.repository = github_repository
|
|
133
|
+
if "/" in self.repository:
|
|
134
|
+
self.repository = self.repository.rsplit("/")[1]
|
|
135
|
+
self.ref_name = github_ref_name
|
|
136
|
+
self.default_branch = default_branch
|
|
137
|
+
self.is_default_branch = is_default_branch
|
|
138
|
+
self.pr_number = pr_number
|
|
139
|
+
self.pr_name = pr_name
|
|
140
|
+
self.commit_message = commit_message
|
|
141
|
+
self.committer = github_actor
|
|
142
|
+
self.github_env = github_env
|
|
143
|
+
self.api_token = gh_api_token
|
|
144
|
+
if self.api_token is None:
|
|
145
|
+
print("Unable to get Github API Token from GH_API_TOKEN")
|
|
146
|
+
sys.exit(2)
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def check_event_type() -> str:
|
|
150
|
+
if github_event_name.lower() == "push":
|
|
151
|
+
if pr_number is None or pr_number == "":
|
|
152
|
+
event_type = "main"
|
|
153
|
+
else:
|
|
154
|
+
event_type = "diff"
|
|
155
|
+
elif github_event_name.lower() == "issue_comment":
|
|
156
|
+
event_type = "comment"
|
|
157
|
+
else:
|
|
158
|
+
event_type = None
|
|
159
|
+
log.error(f"Unknown event type {github_event_name}")
|
|
160
|
+
sys.exit(1)
|
|
161
|
+
return event_type
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
def add_socket_comments(security_comment: str, overview_comment: str, comments: dict) -> None:
|
|
165
|
+
existing_overview_comment = comments.get("overview")
|
|
166
|
+
existing_security_comment = comments.get("security")
|
|
167
|
+
if existing_overview_comment is not None:
|
|
168
|
+
existing_overview_comment: GithubComment
|
|
169
|
+
Github.update_comment(overview_comment, str(existing_overview_comment.id))
|
|
170
|
+
else:
|
|
171
|
+
Github.post_comment(overview_comment)
|
|
172
|
+
if existing_security_comment is not None:
|
|
173
|
+
existing_security_comment: GithubComment
|
|
174
|
+
Github.update_comment(security_comment, str(existing_security_comment.id))
|
|
175
|
+
else:
|
|
176
|
+
Github.post_comment(security_comment)
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def post_comment(body: str) -> None:
|
|
180
|
+
repo = github_repository.rsplit("/", 1)[1]
|
|
181
|
+
path = f"repos/{github_repository_owner}/{repo}/issues/{pr_number}/comments"
|
|
182
|
+
payload = {
|
|
183
|
+
"body": body
|
|
184
|
+
}
|
|
185
|
+
payload = json.dumps(payload)
|
|
186
|
+
do_request(path, payload=payload, method="POST")
|
|
187
|
+
|
|
188
|
+
@staticmethod
|
|
189
|
+
def update_comment(body: str, comment_id: str) -> None:
|
|
190
|
+
repo = github_repository.rsplit("/", 1)[1]
|
|
191
|
+
path = f"repos/{github_repository_owner}/{repo}/issues/comments/{comment_id}"
|
|
192
|
+
payload = {
|
|
193
|
+
"body": body
|
|
194
|
+
}
|
|
195
|
+
payload = json.dumps(payload)
|
|
196
|
+
do_request(path, payload=payload, method="PATCH")
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def write_new_env(name: str, content: str) -> None:
|
|
200
|
+
file = open(github_env, "a")
|
|
201
|
+
new_content = content.replace("\n", "\\n")
|
|
202
|
+
env_output = f"{name}={new_content}"
|
|
203
|
+
file.write(env_output)
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def get_comments_for_pr(repo: str, pr: str) -> dict:
|
|
207
|
+
path = f"repos/{github_repository_owner}/{repo}/issues/{pr}/comments"
|
|
208
|
+
raw_comments = do_request(path)
|
|
209
|
+
comments = {}
|
|
210
|
+
if "error" not in raw_comments:
|
|
211
|
+
for item in raw_comments:
|
|
212
|
+
comment = GithubComment(**item)
|
|
213
|
+
comments[comment.id] = comment
|
|
214
|
+
for line in comment.body.split("\n"):
|
|
215
|
+
comment.body_list.append(line)
|
|
216
|
+
else:
|
|
217
|
+
log.error(raw_comments)
|
|
218
|
+
socket_comments = Github.check_for_socket_comments(comments)
|
|
219
|
+
return socket_comments
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def check_for_socket_comments(comments: dict):
|
|
223
|
+
socket_comments = {}
|
|
224
|
+
for comment_id in comments:
|
|
225
|
+
comment = comments[comment_id]
|
|
226
|
+
comment: GithubComment
|
|
227
|
+
if "socket-security-comment-actions" in comment.body:
|
|
228
|
+
socket_comments["security"] = comment
|
|
229
|
+
elif "socket-overview-comment-actions" in comment.body:
|
|
230
|
+
socket_comments["overview"] = comment
|
|
231
|
+
elif "SocketSecurity ignore" in comment.body:
|
|
232
|
+
if "ignore" not in socket_comments:
|
|
233
|
+
socket_comments["ignore"] = []
|
|
234
|
+
socket_comments["ignore"].append(comment)
|
|
235
|
+
return socket_comments
|
|
236
|
+
|
|
237
|
+
@staticmethod
|
|
238
|
+
def remove_alerts(comments: dict, new_alerts: list) -> list:
|
|
239
|
+
alerts = []
|
|
240
|
+
if "ignore" not in comments:
|
|
241
|
+
return new_alerts
|
|
242
|
+
ignore_all, ignore_commands = Github.get_ignore_options(comments)
|
|
243
|
+
for alert in new_alerts:
|
|
244
|
+
alert: Issue
|
|
245
|
+
if ignore_all:
|
|
246
|
+
break
|
|
247
|
+
else:
|
|
248
|
+
purl = f"{alert.pkg_name}, {alert.pkg_version}"
|
|
249
|
+
purl_star = f"{alert.pkg_name}, *"
|
|
250
|
+
if purl in ignore_commands or purl_star in ignore_commands:
|
|
251
|
+
log.debug(f"Alerts for {alert.pkg_name}@{alert.pkg_version} ignored")
|
|
252
|
+
else:
|
|
253
|
+
log.debug(f"Adding alert {alert.type} for {alert.pkg_name}@{alert.pkg_version}")
|
|
254
|
+
alerts.append(alert)
|
|
255
|
+
return alerts
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def get_ignore_options(comments: dict) -> [bool, list]:
|
|
259
|
+
ignore_commands = []
|
|
260
|
+
ignore_all = False
|
|
261
|
+
|
|
262
|
+
for comment in comments["ignore"]:
|
|
263
|
+
comment: GithubComment
|
|
264
|
+
first_line = comment.body_list[0]
|
|
265
|
+
if not ignore_all and "SocketSecurity ignore" in first_line:
|
|
266
|
+
first_line = first_line.lstrip("@")
|
|
267
|
+
_, command = first_line.split("SocketSecurity ")
|
|
268
|
+
command = command.strip()
|
|
269
|
+
if command == "ignore-all":
|
|
270
|
+
ignore_all = True
|
|
271
|
+
else:
|
|
272
|
+
command = command.lstrip("ignore").strip()
|
|
273
|
+
name, version = command.split("@")
|
|
274
|
+
data = f"{name}, {version}"
|
|
275
|
+
ignore_commands.append(data)
|
|
276
|
+
return ignore_all, ignore_commands
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
def is_ignore(pkg_name: str, pkg_version: str, name: str, version: str) -> bool:
|
|
280
|
+
result = False
|
|
281
|
+
if pkg_name == name and (pkg_version == version or version == "*"):
|
|
282
|
+
result = True
|
|
283
|
+
return result
|
|
284
|
+
|
|
285
|
+
@staticmethod
|
|
286
|
+
def remove_comment_alerts(comments: dict):
|
|
287
|
+
security_alert = comments.get("security")
|
|
288
|
+
if security_alert is not None:
|
|
289
|
+
security_alert: GithubComment
|
|
290
|
+
new_body = Github.process_security_comment(security_alert, comments)
|
|
291
|
+
Github.update_comment(new_body, str(security_alert.id))
|
|
292
|
+
|
|
293
|
+
@staticmethod
|
|
294
|
+
def is_heading_line(line) -> bool:
|
|
295
|
+
is_heading_line = True
|
|
296
|
+
if line != "|Alert|Package|Introduced by|Manifest File|" and ":---" not in line:
|
|
297
|
+
is_heading_line = False
|
|
298
|
+
return is_heading_line
|
|
299
|
+
|
|
300
|
+
@staticmethod
|
|
301
|
+
def process_security_comment(comment: GithubComment, comments) -> str:
|
|
302
|
+
lines = []
|
|
303
|
+
start = False
|
|
304
|
+
ignore_all, ignore_commands = Github.get_ignore_options(comments)
|
|
305
|
+
for line in comment.body_list:
|
|
306
|
+
line = line.strip()
|
|
307
|
+
if "start-socket-alerts-table" in line:
|
|
308
|
+
start = True
|
|
309
|
+
elif start and "end-socket-alerts-table" not in line and not Github.is_heading_line(line) and line != '':
|
|
310
|
+
title, package, introduced_by, manifest = line.lstrip("|").rstrip("|").split("|")
|
|
311
|
+
details, _ = package.split("](")
|
|
312
|
+
ecosystem, details = details.split("/", 1)
|
|
313
|
+
pkg_name, pkg_version = details.split("@")
|
|
314
|
+
ignore = False
|
|
315
|
+
for name, version in ignore_commands:
|
|
316
|
+
if ignore_all or Github.is_ignore(pkg_name, pkg_version, name, version):
|
|
317
|
+
ignore = True
|
|
318
|
+
if not ignore:
|
|
319
|
+
lines.append(line)
|
|
320
|
+
elif "end-socket-alerts-table" in line:
|
|
321
|
+
start = False
|
|
322
|
+
lines.append(line)
|
|
323
|
+
else:
|
|
324
|
+
lines.append(line)
|
|
325
|
+
new_body = "\n".join(lines)
|
|
326
|
+
return new_body
|
|
327
|
+
|