socketsecurity 1.0.31__tar.gz → 1.0.34__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.
Files changed (25) hide show
  1. {socketsecurity-1.0.31/socketsecurity.egg-info → socketsecurity-1.0.34}/PKG-INFO +9 -5
  2. socketsecurity-1.0.34/pyproject.toml +130 -0
  3. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity/__init__.py +1 -1
  4. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity/core/__init__.py +131 -116
  5. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity/core/github.py +25 -28
  6. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity/socketcli.py +18 -7
  7. {socketsecurity-1.0.31 → socketsecurity-1.0.34/socketsecurity.egg-info}/PKG-INFO +9 -5
  8. socketsecurity-1.0.34/socketsecurity.egg-info/requires.txt +11 -0
  9. socketsecurity-1.0.31/pyproject.toml +0 -48
  10. socketsecurity-1.0.31/socketsecurity.egg-info/requires.txt +0 -6
  11. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/LICENSE +0 -0
  12. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/README.md +0 -0
  13. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/setup.cfg +0 -0
  14. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity/core/classes.py +0 -0
  15. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity/core/exceptions.py +0 -0
  16. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity/core/git_interface.py +0 -0
  17. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity/core/gitlab.py +0 -0
  18. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity/core/issues.py +0 -0
  19. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity/core/licenses.py +0 -0
  20. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity/core/messages.py +0 -0
  21. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity/core/scm_comments.py +0 -0
  22. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity.egg-info/SOURCES.txt +0 -0
  23. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity.egg-info/dependency_links.txt +0 -0
  24. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity.egg-info/entry_points.txt +0 -0
  25. {socketsecurity-1.0.31 → socketsecurity-1.0.34}/socketsecurity.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: socketsecurity
3
- Version: 1.0.31
3
+ Version: 1.0.34
4
4
  Summary: Socket Security CLI for CI/CD
5
5
  Author-email: Douglas Coburn <douglas@socket.dev>
6
6
  Maintainer-email: Douglas Coburn <douglas@socket.dev>
@@ -15,12 +15,16 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Requires-Python: >=3.9
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: requests
19
- Requires-Dist: mdutils
18
+ Requires-Dist: requests>=2.32.0
19
+ Requires-Dist: mdutils~=1.6.0
20
20
  Requires-Dist: prettytable
21
21
  Requires-Dist: argparse
22
- Requires-Dist: GitPython
23
- Requires-Dist: packaging
22
+ Requires-Dist: GitPython>=3.1.43
23
+ Requires-Dist: packaging>=24.1
24
+ Requires-Dist: python-dotenv>=1.0.1
25
+ Requires-Dist: socket-sdk-python>=1.0.14
26
+ Provides-Extra: dev
27
+ Requires-Dist: ruff>=0.3.0; extra == "dev"
24
28
 
25
29
  # Socket Security CLI
26
30
 
@@ -0,0 +1,130 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "socketsecurity"
7
+ dynamic = ["version"]
8
+ requires-python = ">= 3.9"
9
+ dependencies = [
10
+ 'requests >= 2.32.0',
11
+ 'mdutils ~= 1.6.0',
12
+ 'prettytable',
13
+ 'argparse',
14
+ 'GitPython >= 3.1.43',
15
+ 'packaging >= 24.1',
16
+ 'python-dotenv >= 1.0.1',
17
+ 'socket-sdk-python >= 1.0.14'
18
+ ]
19
+
20
+
21
+ readme = "README.md"
22
+ description = "Socket Security CLI for CI/CD"
23
+ keywords = ["socketsecurity", "socket.dev", "sca", "oss", "security"]
24
+ authors = [
25
+ {name = "Douglas Coburn", email = "douglas@socket.dev"}
26
+ ]
27
+ maintainers = [
28
+ {name = "Douglas Coburn", email = "douglas@socket.dev"}
29
+ ]
30
+ classifiers = [
31
+ "Development Status :: 4 - Beta",
32
+ "Intended Audience :: Developers",
33
+ "Programming Language :: Python :: 3.9",
34
+ "Programming Language :: Python :: 3.10",
35
+ "Programming Language :: Python :: 3.11",
36
+ "Programming Language :: Python :: 3.12",
37
+ ]
38
+
39
+ # modern, faster linter and language server. install with `pip install -e ".[dev]"`
40
+ [project.optional-dependencies]
41
+ dev = [
42
+ "ruff>=0.3.0",
43
+ ]
44
+
45
+ [project.scripts]
46
+ socketcli = "socketsecurity.socketcli:cli"
47
+
48
+ [project.urls]
49
+ Homepage = "https://socket.dev"
50
+
51
+ [tool.setuptools.packages.find]
52
+ include = [
53
+ "socketsecurity",
54
+ "socketsecurity.core"
55
+ ]
56
+
57
+ [tool.setuptools.dynamic]
58
+ version = {attr = "socketsecurity.__version__"}
59
+
60
+ [tool.ruff]
61
+ # Exclude a variety of commonly ignored directories.
62
+ exclude = [
63
+ ".bzr",
64
+ ".direnv",
65
+ ".eggs",
66
+ ".git",
67
+ ".git-rewrite",
68
+ ".hg",
69
+ ".ipynb_checkpoints",
70
+ ".mypy_cache",
71
+ ".nox",
72
+ ".pants.d",
73
+ ".pyenv",
74
+ ".pytest_cache",
75
+ ".pytype",
76
+ ".ruff_cache",
77
+ ".svn",
78
+ ".tox",
79
+ ".venv",
80
+ ".vscode",
81
+ "__pypackages__",
82
+ "_build",
83
+ "buck-out",
84
+ "build",
85
+ "dist",
86
+ "node_modules",
87
+ "site-packages",
88
+ "venv",
89
+ ]
90
+
91
+ [tool.ruff.lint]
92
+ # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
93
+ # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
94
+ # McCabe complexity (`C901`) by default.
95
+ select = ["E4", "E7", "E9", "F"]
96
+ ignore = []
97
+
98
+ # Allow fix for all enabled rules (when `--fix`) is provided.
99
+ fixable = ["ALL"]
100
+ unfixable = []
101
+
102
+ # Allow unused variables when underscore-prefixed.
103
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
104
+
105
+ [tool.ruff.format]
106
+ # Like Black, use double quotes for strings.
107
+ quote-style = "double"
108
+
109
+ # Like Black, indent with spaces, rather than tabs.
110
+ indent-style = "space"
111
+
112
+ # Like Black, respect magic trailing commas.
113
+ skip-magic-trailing-comma = false
114
+
115
+ # Like Black, automatically detect the appropriate line ending.
116
+ line-ending = "auto"
117
+
118
+ # Enable auto-formatting of code examples in docstrings. Markdown,
119
+ # reStructuredText code/literal blocks and doctests are all supported.
120
+ #
121
+ # This is currently disabled by default, but it is planned for this
122
+ # to be opt-out in the future.
123
+ docstring-code-format = false
124
+
125
+ # Set the line length limit used when formatting code snippets in
126
+ # docstrings.
127
+ #
128
+ # This only has an effect when the `docstring-code-format` setting is
129
+ # enabled.
130
+ docstring-code-line-length = "dynamic"
@@ -1,2 +1,2 @@
1
1
  __author__ = 'socket.dev'
2
- __version__ = '1.0.31'
2
+ __version__ = '1.0.34'
@@ -2,9 +2,9 @@ import logging
2
2
  from pathlib import PurePath
3
3
 
4
4
  import requests
5
- from urllib.parse import urlencode
6
5
  import base64
7
6
  import json
7
+ from socketdev import socketdev
8
8
  from socketsecurity.core.exceptions import (
9
9
  APIFailure, APIKeyMissing, APIAccessDenied, APIInsufficientQuota, APIResourceNotFound, APICloudflareError
10
10
  )
@@ -12,7 +12,6 @@ from socketsecurity import __version__
12
12
  from socketsecurity.core.licenses import Licenses
13
13
  from socketsecurity.core.issues import AllIssues
14
14
  from socketsecurity.core.classes import (
15
- Report,
16
15
  Issue,
17
16
  Package,
18
17
  Alert,
@@ -50,6 +49,19 @@ log = logging.getLogger("socketdev")
50
49
  log.addHandler(logging.NullHandler())
51
50
 
52
51
  socket_globs = {
52
+ "spdx": {
53
+ "spdx.json": {
54
+ "pattern": "*[-.]spdx.json"
55
+ }
56
+ },
57
+ "cdx": {
58
+ "cyclonedx.json": {
59
+ "pattern": "{bom,*[-.]c{yclone,}dx}.json"
60
+ },
61
+ "xml": {
62
+ "pattern": "{bom,*[-.]c{yclone,}dx}.xml"
63
+ }
64
+ },
53
65
  "npm": {
54
66
  "package.json": {
55
67
  "pattern": "package.json"
@@ -213,34 +225,53 @@ def do_request(
213
225
 
214
226
 
215
227
  class Core:
216
- token: str
217
- base_api_url: str
218
- request_timeout: int
219
- reports: list
228
+ _sdk = None
229
+ _initialized = False
220
230
 
221
- def __init__(
222
- self,
231
+ def __init__(self):
232
+ raise NotImplementedError("Use Core.initialize() instead of instantiation")
233
+
234
+ @classmethod
235
+ def initialize(
236
+ cls,
223
237
  token: str,
224
238
  base_api_url: str = None,
225
239
  request_timeout: int = None,
226
240
  enable_all_alerts: bool = False,
227
241
  allow_unverified: bool = False
228
- ):
229
- global allow_unverified_ssl
242
+ ) -> None:
243
+ """Initialize the Core class and set up global configuration"""
244
+ if cls._initialized:
245
+ return
246
+
247
+ global allow_unverified_ssl, all_new_alerts
248
+
230
249
  allow_unverified_ssl = allow_unverified
231
- self.token = token + ":"
232
- encode_key(self.token)
233
- self.socket_date_format = "%Y-%m-%dT%H:%M:%S.%fZ"
234
- self.base_api_url = base_api_url
235
- if self.base_api_url is not None:
236
- Core.set_api_url(self.base_api_url)
237
- self.request_timeout = request_timeout
238
- if self.request_timeout is not None:
239
- Core.set_timeout(self.request_timeout)
250
+ cls._initialize_sdk(token)
251
+ encode_key(token + ":")
252
+
253
+ if base_api_url is not None:
254
+ cls.set_api_url(base_api_url)
255
+
256
+ if request_timeout is not None:
257
+ cls.set_timeout(request_timeout)
258
+
240
259
  if enable_all_alerts:
241
- global all_new_alerts
242
260
  all_new_alerts = True
243
- Core.set_org_vars()
261
+
262
+ cls._initialized = True
263
+ cls.set_org_vars()
264
+
265
+ @classmethod
266
+ def _initialize_sdk(cls, token: str) -> None:
267
+ if cls._sdk is None:
268
+ cls._sdk = socketdev(token=token)
269
+
270
+ @classmethod
271
+ def get_sdk(cls) -> socketdev:
272
+ if not cls._initialized:
273
+ raise RuntimeError("Core not initialized - call Core.initialize() first")
274
+ return cls._sdk
244
275
 
245
276
  @staticmethod
246
277
  def enable_debug_log(level: int):
@@ -284,39 +315,50 @@ class Core:
284
315
  def get_org_id_slug() -> (str, str):
285
316
  """
286
317
  Gets the Org ID and Org Slug for the API Token
287
- :return:
318
+ :return: Tuple of Org ID and Org Slug
288
319
  """
289
- path = "organizations"
290
- response = do_request(path)
291
- data = response.json()
292
- organizations = data.get("organizations")
293
320
  new_org_id = None
294
321
  new_org_slug = None
322
+
323
+ sdk = Core.get_sdk()
324
+ data = sdk.org.get()
325
+ organizations = data.get("organizations")
326
+
295
327
  if len(organizations) == 1:
296
328
  for key in organizations:
297
329
  new_org_id = key
298
330
  new_org_slug = organizations[key].get('slug')
331
+
299
332
  return new_org_id, new_org_slug
300
333
 
301
334
  @staticmethod
302
- def get_sbom_data(full_scan_id: str) -> list:
303
- path = f"orgs/{org_slug}/full-scans/{full_scan_id}"
304
- response = do_request(path)
305
- results = []
306
- try:
307
- data = response.json()
308
- results = data.get("sbom_artifacts") or []
309
- except Exception as error:
310
- log.debug("Failed with old style full-scan API using new format")
311
- log.debug(error)
312
- data = response.text
335
+ def get_sbom_data(full_scan_id: str) -> dict:
336
+ sdk = Core.get_sdk()
337
+ response_dict = sdk.fullscans.stream(org_slug, full_scan_id)
338
+
339
+ if not response_dict.get("success"):
340
+ results = []
341
+ data = response_dict.get("message")
313
342
  data.strip('"')
314
343
  data.strip()
344
+
315
345
  for line in data.split("\n"):
316
346
  if line != '"' and line != "" and line is not None:
317
347
  item = json.loads(line)
318
348
  results.append(item)
319
- return results
349
+
350
+ return results
351
+
352
+ keys_to_remove = ["success", "status"]
353
+ for key in keys_to_remove:
354
+ response_dict.pop(key, None)
355
+
356
+ for key in response_dict:
357
+ value = response_dict.get(key)
358
+ response_dict[key] = Package(**value)
359
+
360
+ return response_dict
361
+
320
362
 
321
363
  @staticmethod
322
364
  def get_security_policy() -> dict:
@@ -324,23 +366,21 @@ class Core:
324
366
  Get the Security policy and determine the effective Org security policy
325
367
  :return:
326
368
  """
327
- path = "settings"
328
- payload = [
329
- {
330
- "organization": org_id
331
- }
332
- ]
333
- response = do_request(path, payload=json.dumps(payload), method="POST")
334
- data = response.json()
369
+
370
+ sdk = Core.get_sdk()
371
+ data = sdk.settings.get(org_id)
372
+
335
373
  defaults = data.get("defaults")
336
374
  default_rules = defaults.get("issueRules")
337
375
  entries = data.get("entries")
338
376
  org_rules = {}
377
+
339
378
  for org_set in entries:
340
379
  settings = org_set.get("settings")
341
380
  if settings is not None:
342
381
  org_details = settings.get("organization")
343
382
  org_rules = org_details.get("issueRules")
383
+
344
384
  for default in default_rules:
345
385
  if default not in org_rules:
346
386
  action = default_rules[default]["action"]
@@ -349,10 +389,6 @@ class Core:
349
389
  }
350
390
  return org_rules
351
391
 
352
- # @staticmethod
353
- # def get_supported_file_types() -> dict:
354
- # path = "report/supported"
355
-
356
392
  @staticmethod
357
393
  def get_manifest_files(package: Package, packages: dict) -> str:
358
394
  if package.direct:
@@ -375,15 +411,16 @@ class Core:
375
411
 
376
412
  @staticmethod
377
413
  def create_sbom_output(diff: Diff) -> dict:
378
- base_path = f"orgs/{org_slug}/export/cdx"
379
- path = f"{base_path}/{diff.id}"
380
- result = do_request(path=path)
381
- try:
382
- sbom = result.json()
383
- except Exception as error:
414
+ sdk = Core.get_sdk()
415
+ sbom = sdk.export.cdx_bom(org_slug, diff.id)
416
+
417
+ if not sbom.get("success"):
384
418
  log.error(f"Unable to get CycloneDX Output for {diff.id}")
385
- log.error(error)
386
- sbom = {}
419
+ log.error(sbom.get("message"))
420
+ return {}
421
+
422
+ sbom.pop("success", None)
423
+
387
424
  return sbom
388
425
 
389
426
  @staticmethod
@@ -394,7 +431,7 @@ class Core:
394
431
  patterns = socket_globs[ecosystem]
395
432
  for file_name in patterns:
396
433
  pattern = patterns[file_name]["pattern"]
397
- # path_pattern = f"**/{pattern}"
434
+
398
435
  for file in files:
399
436
  if "\\" in file:
400
437
  file = file.replace("\\", "/")
@@ -412,11 +449,12 @@ class Core:
412
449
  :param path: Str - path to where the manifest files are located
413
450
  :return:
414
451
  """
415
- log.debug("Starting Find Files")
452
+
416
453
  start_time = time.time()
417
454
  files = set()
418
455
  for ecosystem in socket_globs:
419
456
  patterns = socket_globs[ecosystem]
457
+
420
458
  for file_name in patterns:
421
459
  pattern = patterns[file_name]["pattern"]
422
460
  file_path = f"{path}/**/{pattern}"
@@ -424,14 +462,15 @@ class Core:
424
462
  log.debug(f"Globbing {file_path}")
425
463
  glob_start = time.time()
426
464
  glob_files = glob(file_path, recursive=True)
465
+
427
466
  for glob_file in glob_files:
428
467
  if glob_file not in files:
429
468
  files.add(glob_file)
469
+
430
470
  glob_end = time.time()
431
471
  glob_total_time = glob_end - glob_start
432
472
  log.debug(f"Glob for pattern {file_path} took {glob_total_time:.2f} seconds")
433
473
 
434
- log.debug("Finished Find Files")
435
474
  end_time = time.time()
436
475
  total_time = end_time - start_time
437
476
  log.info(f"Found {len(files)} in {total_time:.2f} seconds")
@@ -448,7 +487,7 @@ class Core:
448
487
  """
449
488
  send_files = []
450
489
  create_full_start = time.time()
451
- log.debug("Creating new full scan")
490
+
452
491
  for file in files:
453
492
  if platform.system() == "Windows":
454
493
  file = file.replace("\\", "/")
@@ -472,10 +511,12 @@ class Core:
472
511
  )
473
512
  )
474
513
  send_files.append(payload)
475
- query_params = urlencode(params.__dict__)
476
- full_uri = f"{full_scan_path}?{query_params}"
477
- response = do_request(full_uri, method="POST", files=send_files)
478
- results = response.json()
514
+
515
+ sdk = Core.get_sdk()
516
+ params_dict = params.__dict__
517
+ params_dict["org_slug"] = org_slug
518
+ results = sdk.fullscans.post(files=files, params=params_dict)
519
+
479
520
  full_scan = FullScan(**results)
480
521
  full_scan.sbom_artifacts = Core.get_sbom_data(full_scan.id)
481
522
  create_full_end = time.time()
@@ -500,25 +541,12 @@ class Core:
500
541
  :param repo_slug: Str - Repo slug for the repository that is being diffed
501
542
  :return:
502
543
  """
503
- repo_path = f"{repository_path}/{repo_slug}"
504
- response = do_request(repo_path)
505
- results = response.json()
544
+
545
+ sdk = Core.get_sdk()
546
+ results = sdk.repos.repo(org_slug, repo_slug)
506
547
  repository = Repository(**results)
507
- return repository.head_full_scan_id
508
548
 
509
- @staticmethod
510
- def get_full_scan(full_scan_id: str) -> FullScan:
511
- """
512
- Get the specified full scan and return a FullScan object
513
- :param full_scan_id: str - ID of the full scan to pull
514
- :return:
515
- """
516
- full_scan_url = f"{full_scan_path}/{full_scan_id}"
517
- response = do_request(full_scan_url)
518
- results = response.json()
519
- full_scan = FullScan(**results)
520
- full_scan.sbom_artifacts = Core.get_sbom_data(full_scan.id)
521
- return full_scan
549
+ return repository.head_full_scan_id
522
550
 
523
551
  @staticmethod
524
552
  def create_new_diff(
@@ -542,11 +570,13 @@ class Core:
542
570
  diff = Diff()
543
571
  diff.id = "no_diff_id"
544
572
  return diff
573
+
545
574
  files = Core.find_files(path)
546
575
  if files is None or len(files) == 0:
547
576
  diff = Diff()
548
577
  diff.id = "no_diff_id"
549
578
  return diff
579
+
550
580
  try:
551
581
  head_full_scan_id = Core.get_head_scan_for_repo(params.repo)
552
582
  if head_full_scan_id is None or head_full_scan_id == "":
@@ -557,29 +587,35 @@ class Core:
557
587
  head_end = time.time()
558
588
  total_head_time = head_end - head_start
559
589
  log.info(f"Total time to get head full-scan {total_head_time: .2f}")
590
+
560
591
  except APIResourceNotFound:
561
592
  head_full_scan_id = None
562
593
  head_full_scan = []
594
+
563
595
  new_scan_start = time.time()
564
596
  new_full_scan = Core.create_full_scan(files, params, workspace)
565
- new_full_scan.packages = Core.create_sbom_dict(new_full_scan.sbom_artifacts)
597
+ new_full_scan.packages = new_full_scan.sbom_artifacts
566
598
  new_scan_end = time.time()
599
+
567
600
  total_new_time = new_scan_end - new_scan_start
568
601
  log.info(f"Total time to get new full-scan {total_new_time: .2f}")
602
+
569
603
  diff_report = Core.compare_sboms(new_full_scan.sbom_artifacts, head_full_scan)
570
604
  diff_report.packages = new_full_scan.packages
571
- # Set the diff ID and URLs
605
+
572
606
  base_socket = "https://socket.dev/dashboard/org"
573
607
  diff_report.id = new_full_scan.id
574
608
  diff_report.report_url = f"{base_socket}/{org_slug}/sbom/{diff_report.id}"
609
+
575
610
  if head_full_scan_id is not None:
576
611
  diff_report.diff_url = f"{base_socket}/{org_slug}/diff/{diff_report.id}/{head_full_scan_id}"
577
612
  else:
578
613
  diff_report.diff_url = diff_report.report_url
614
+
579
615
  return diff_report
580
616
 
581
617
  @staticmethod
582
- def compare_sboms(new_scan: list, head_scan: list) -> Diff:
618
+ def compare_sboms(new_scan: dict, head_scan: dict) -> Diff:
583
619
  """
584
620
  compare the SBOMs of the new full Scan and the head full scan. Return a Diff report with new packages,
585
621
  removed packages, and new alerts for the new full scan compared to the head.
@@ -587,28 +623,33 @@ class Core:
587
623
  :param head_scan: FullScan - Current head FullScan for the repository
588
624
  :return:
589
625
  """
590
- diff: Diff
591
- diff = Diff()
592
- new_packages = Core.create_sbom_dict(new_scan)
593
- head_packages = Core.create_sbom_dict(head_scan)
626
+ diff: Diff = Diff()
627
+ new_packages = new_scan
628
+ head_packages = head_scan
629
+
594
630
  new_scan_alerts = {}
595
631
  head_scan_alerts = {}
596
632
  consolidated = set()
633
+
597
634
  for package_id in new_packages:
598
635
  purl, package = Core.create_purl(package_id, new_packages)
599
636
  base_purl = f"{purl.ecosystem}/{purl.name}@{purl.version}"
637
+
600
638
  if package_id not in head_packages and package.direct and base_purl not in consolidated:
601
639
  diff.new_packages.append(purl)
602
640
  consolidated.add(base_purl)
603
641
  new_scan_alerts = Core.create_issue_alerts(package, new_scan_alerts, new_packages)
642
+
604
643
  for package_id in head_packages:
605
644
  purl, package = Core.create_purl(package_id, head_packages)
606
645
  if package_id not in new_packages and package.direct:
607
646
  diff.removed_packages.append(purl)
608
647
  head_scan_alerts = Core.create_issue_alerts(package, head_scan_alerts, head_packages)
648
+
609
649
  diff.new_alerts = Core.compare_issue_alerts(new_scan_alerts, head_scan_alerts, diff.new_alerts)
610
650
  diff.new_capabilities = Core.compare_capabilities(new_packages, head_packages)
611
651
  diff = Core.add_capabilities_to_purl(diff)
652
+
612
653
  return diff
613
654
 
614
655
  @staticmethod
@@ -814,32 +855,6 @@ class Core:
814
855
  )
815
856
  return purl, package
816
857
 
817
- @staticmethod
818
- def create_sbom_dict(sbom: list) -> dict:
819
- """
820
- Converts the SBOM Artifacts from the FulLScan into a Dictionary for parsing
821
- :param sbom: list - Raw artifacts for the SBOM
822
- :return:
823
- """
824
- packages = {}
825
- top_level_count = {}
826
- for item in sbom:
827
- package = Package(**item)
828
- if package.id in packages:
829
- print("Duplicate package?")
830
- else:
831
- package = Core.get_license_details(package)
832
- packages[package.id] = package
833
- for top_id in package.topLevelAncestors:
834
- if top_id not in top_level_count:
835
- top_level_count[top_id] = 1
836
- else:
837
- top_level_count[top_id] += 1
838
- if len(top_level_count) > 0:
839
- for package_id in top_level_count:
840
- packages[package_id].transitives = top_level_count[package_id]
841
- return packages
842
-
843
858
  @staticmethod
844
859
  def save_file(file_name: str, content: str) -> None:
845
860
  file = open(file_name, "w")
@@ -1,30 +1,29 @@
1
1
  import json
2
2
  import os
3
3
  from socketsecurity.core import log, do_request
4
- import requests
5
4
  from socketsecurity.core.classes import Comment
6
5
  from socketsecurity.core.scm_comments import Comments
7
6
  import sys
8
7
 
9
8
 
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
- global event_action
9
+ github_sha = None
10
+ github_api_url = None
11
+ github_ref_type = None
12
+ github_event_name = None
13
+ github_workspace = None
14
+ github_repository = None
15
+ github_ref_name = None
16
+ github_actor = None
17
+ default_branch = None
18
+ github_env = None
19
+ pr_number = None
20
+ pr_name = None
21
+ is_default_branch = False
22
+ commit_message = None
23
+ committer = None
24
+ gh_api_token = None
25
+ github_repository_owner = None
26
+ event_action = None
28
27
 
29
28
  github_variables = [
30
29
  "GITHUB_SHA",
@@ -47,16 +46,14 @@ github_variables = [
47
46
 
48
47
  for env in github_variables:
49
48
  var_name = env.lower()
50
- globals()[var_name] = os.getenv(env) or None
49
+ value = os.getenv(env)
50
+ globals()[var_name] = value
51
+
51
52
  if var_name == "default_branch":
52
- global is_default_branch
53
- if default_branch is None or default_branch.lower() == "false":
54
- is_default_branch = False
55
- else:
56
- is_default_branch = True
57
- if var_name != "gh_api_token":
58
- value = globals()[var_name] = os.getenv(env) or None
59
- log.debug(f"{env}={value}")
53
+ is_default_branch = bool(value and value.lower() != "false")
54
+
55
+ if var_name != "gh_api_token":
56
+ log.debug(f"{env}={value}")
60
57
 
61
58
  headers = {
62
59
  'Authorization': f"Bearer {gh_api_token}",
@@ -167,12 +167,15 @@ def output_console_comments(diff_report: Diff, sbom_file_name: str = None) -> No
167
167
  console_security_comment = Messages.create_console_security_alert_table(diff_report)
168
168
  save_sbom_file(diff_report, sbom_file_name)
169
169
  log.info(f"Socket Full Scan ID: {diff_report.id}")
170
+
170
171
  if len(diff_report.new_alerts) > 0:
171
172
  log.info("Security issues detected by Socket Security")
172
173
  msg = f"\n{console_security_comment}"
173
174
  log.info(msg)
175
+
174
176
  if not report_pass(diff_report) and not blocking_disabled:
175
177
  sys.exit(1)
178
+
176
179
  else:
177
180
  # Means only warning alerts with no blocked
178
181
  if not blocking_disabled:
@@ -188,7 +191,7 @@ def output_console_json(diff_report: Diff, sbom_file_name: str = None) -> None:
188
191
  print(json.dumps(console_security_comment))
189
192
  if not report_pass(diff_report) and not blocking_disabled:
190
193
  sys.exit(1)
191
- elif len(diff_report.new_alerts) > 0 and not blocking_disabled:
194
+ if len(diff_report.new_alerts) > 0 and not blocking_disabled:
192
195
  # Means only warning alerts with no blocked
193
196
  sys.exit(5)
194
197
 
@@ -252,6 +255,7 @@ def main_code():
252
255
  ignore_commit_files = arguments.ignore_commit_files
253
256
  disable_blocking = arguments.disable_blocking
254
257
  allow_unverified = arguments.allow_unverified
258
+
255
259
  if disable_blocking:
256
260
  global blocking_disabled
257
261
  blocking_disabled = True
@@ -308,13 +312,20 @@ def main_code():
308
312
  default_branch = scm.is_default_branch
309
313
 
310
314
  base_api_url = os.getenv("BASE_API_URL") or None
311
- core = Core(token=api_token, request_timeout=1200, base_api_url=base_api_url, allow_unverified=allow_unverified)
315
+
316
+ Core.initialize(
317
+ token=api_token,
318
+ request_timeout=1200,
319
+ base_api_url=base_api_url,
320
+ allow_unverified=allow_unverified
321
+ )
322
+
312
323
  no_change = True
313
324
  if ignore_commit_files:
314
325
  no_change = False
315
326
  elif is_repo and files is not None and len(files) > 0:
316
327
  log.info(files)
317
- no_change = core.match_supported_files(files)
328
+ no_change = Core.match_supported_files(files)
318
329
 
319
330
  set_as_pending_head = False
320
331
  if default_branch:
@@ -344,7 +355,7 @@ def main_code():
344
355
  log.info("No manifest files changes, skipping scan")
345
356
  # log.info("No dependency changes")
346
357
  elif scm.check_event_type() == "diff":
347
- diff = core.create_new_diff(target_path, params, workspace=target_path, no_change=no_change)
358
+ diff = Core.create_new_diff(target_path, params, workspace=target_path, no_change=no_change)
348
359
  log.info("Starting comment logic for PR/MR event")
349
360
  log.debug(f"Getting comments for Repo {scm.repository} for PR {scm.pr_number}")
350
361
  comments = scm.get_comments_for_pr(repo, str(pr_number))
@@ -388,7 +399,7 @@ def main_code():
388
399
  )
389
400
  else:
390
401
  log.info("Starting non-PR/MR flow")
391
- diff = core.create_new_diff(target_path, params, workspace=target_path, no_change=no_change)
402
+ diff = Core.create_new_diff(target_path, params, workspace=target_path, no_change=no_change)
392
403
  if enable_json:
393
404
  log.debug("Outputting JSON Results")
394
405
  output_console_json(diff, sbom_file)
@@ -397,7 +408,7 @@ def main_code():
397
408
  else:
398
409
  log.info("API Mode")
399
410
  diff: Diff
400
- diff = core.create_new_diff(target_path, params, workspace=target_path, no_change=no_change)
411
+ diff = Core.create_new_diff(target_path, params, workspace=target_path, no_change=no_change)
401
412
  if enable_json:
402
413
  output_console_json(diff, sbom_file)
403
414
  else:
@@ -418,7 +429,7 @@ def main_code():
418
429
  "license_text": package.license_text
419
430
  }
420
431
  all_packages[package_id] = output
421
- core.save_file(license_file, json.dumps(all_packages))
432
+ Core.save_file(license_file, json.dumps(all_packages))
422
433
 
423
434
 
424
435
  if __name__ == '__main__':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: socketsecurity
3
- Version: 1.0.31
3
+ Version: 1.0.34
4
4
  Summary: Socket Security CLI for CI/CD
5
5
  Author-email: Douglas Coburn <douglas@socket.dev>
6
6
  Maintainer-email: Douglas Coburn <douglas@socket.dev>
@@ -15,12 +15,16 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Requires-Python: >=3.9
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: requests
19
- Requires-Dist: mdutils
18
+ Requires-Dist: requests>=2.32.0
19
+ Requires-Dist: mdutils~=1.6.0
20
20
  Requires-Dist: prettytable
21
21
  Requires-Dist: argparse
22
- Requires-Dist: GitPython
23
- Requires-Dist: packaging
22
+ Requires-Dist: GitPython>=3.1.43
23
+ Requires-Dist: packaging>=24.1
24
+ Requires-Dist: python-dotenv>=1.0.1
25
+ Requires-Dist: socket-sdk-python>=1.0.14
26
+ Provides-Extra: dev
27
+ Requires-Dist: ruff>=0.3.0; extra == "dev"
24
28
 
25
29
  # Socket Security CLI
26
30
 
@@ -0,0 +1,11 @@
1
+ requests>=2.32.0
2
+ mdutils~=1.6.0
3
+ prettytable
4
+ argparse
5
+ GitPython>=3.1.43
6
+ packaging>=24.1
7
+ python-dotenv>=1.0.1
8
+ socket-sdk-python>=1.0.14
9
+
10
+ [dev]
11
+ ruff>=0.3.0
@@ -1,48 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools >= 61.0"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "socketsecurity"
7
- dynamic = ["version"]
8
- requires-python = ">= 3.9"
9
- dependencies = [
10
- 'requests',
11
- 'mdutils',
12
- 'prettytable',
13
- 'argparse',
14
- 'GitPython',
15
- 'packaging'
16
- ]
17
- readme = "README.md"
18
- description = "Socket Security CLI for CI/CD"
19
- keywords = ["socketsecurity", "socket.dev", "sca", "oss", "security"]
20
- authors = [
21
- {name = "Douglas Coburn", email = "douglas@socket.dev"}
22
- ]
23
- maintainers = [
24
- {name = "Douglas Coburn", email = "douglas@socket.dev"}
25
- ]
26
- classifiers = [
27
- "Development Status :: 4 - Beta",
28
- "Intended Audience :: Developers",
29
- "Programming Language :: Python :: 3.9",
30
- "Programming Language :: Python :: 3.10",
31
- "Programming Language :: Python :: 3.11",
32
- "Programming Language :: Python :: 3.12",
33
- ]
34
-
35
- [project.scripts]
36
- socketcli = "socketsecurity.socketcli:cli"
37
-
38
- [project.urls]
39
- Homepage = "https://socket.dev"
40
-
41
- [tool.setuptools.packages.find]
42
- include = [
43
- "socketsecurity",
44
- "socketsecurity.core"
45
- ]
46
-
47
- [tool.setuptools.dynamic]
48
- version = {attr = "socketsecurity.__version__"}
@@ -1,6 +0,0 @@
1
- requests
2
- mdutils
3
- prettytable
4
- argparse
5
- GitPython
6
- packaging
File without changes