socketsecurity 0.0.98__tar.gz → 0.0.99__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 (23) hide show
  1. {socketsecurity-0.0.98/socketsecurity.egg-info → socketsecurity-0.0.99}/PKG-INFO +1 -1
  2. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity/__init__.py +1 -1
  3. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity/core/__init__.py +126 -111
  4. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity/socketcli.py +21 -10
  5. {socketsecurity-0.0.98 → socketsecurity-0.0.99/socketsecurity.egg-info}/PKG-INFO +1 -1
  6. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/LICENSE +0 -0
  7. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/README.md +0 -0
  8. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/pyproject.toml +0 -0
  9. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/setup.cfg +0 -0
  10. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity/core/classes.py +0 -0
  11. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity/core/exceptions.py +0 -0
  12. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity/core/git_interface.py +0 -0
  13. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity/core/github.py +0 -0
  14. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity/core/gitlab.py +0 -0
  15. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity/core/issues.py +0 -0
  16. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity/core/licenses.py +0 -0
  17. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity/core/messages.py +0 -0
  18. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity/core/scm_comments.py +0 -0
  19. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity.egg-info/SOURCES.txt +0 -0
  20. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity.egg-info/dependency_links.txt +0 -0
  21. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity.egg-info/entry_points.txt +0 -0
  22. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity.egg-info/requires.txt +0 -0
  23. {socketsecurity-0.0.98 → socketsecurity-0.0.99}/socketsecurity.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: socketsecurity
3
- Version: 0.0.98
3
+ Version: 0.0.99
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>
@@ -1,2 +1,2 @@
1
1
  __author__ = 'socket.dev'
2
- __version__ = '0.0.98'
2
+ __version__ = '0.0.99'
@@ -1,4 +1,6 @@
1
1
  import logging
2
+ from pathlib import PurePath
3
+
2
4
  import requests
3
5
  from urllib.parse import urlencode
4
6
  import base64
@@ -46,6 +48,74 @@ security_policy = {}
46
48
  log = logging.getLogger("socketdev")
47
49
  log.addHandler(logging.NullHandler())
48
50
 
51
+ socket_globs = {
52
+ "npm": {
53
+ "package.json": {
54
+ "pattern": "package.json"
55
+ },
56
+ "package-lock.json": {
57
+ "pattern": "package-lock.json"
58
+ },
59
+ "npm-shrinkwrap.json": {
60
+ "pattern": "npm-shrinkwrap.json"
61
+ },
62
+ "yarn.lock": {
63
+ "pattern": "yarn.lock"
64
+ },
65
+ "pnpm-lock.yaml": {
66
+ "pattern": "pnpm-lock.yaml"
67
+ },
68
+ "pnpm-lock.yml": {
69
+ "pattern": "pnpm-lock.yml"
70
+ },
71
+ "pnpm-workspace.yaml": {
72
+ "pattern": "pnpm-workspace.yaml"
73
+ },
74
+ "pnpm-workspace.yml": {
75
+ "pattern": "pnpm-workspace.yml"
76
+ }
77
+ },
78
+ "pypi": {
79
+ "pipfile": {
80
+ "pattern": "pipfile"
81
+ },
82
+ "pyproject.toml": {
83
+ "pattern": "pyproject.toml"
84
+ },
85
+ "requirements.txt": {
86
+ "pattern": "*requirements.txt"
87
+ },
88
+ "requirements": {
89
+ "pattern": "requirements/*.txt"
90
+ },
91
+ "requirements-*.txt": {
92
+ "pattern": "requirements-*.txt"
93
+ },
94
+ "requirements_*.txt": {
95
+ "pattern": "requirements_*.txt"
96
+ },
97
+ "requirements.frozen": {
98
+ "pattern": "requirements.frozen"
99
+ },
100
+ "setup.py": {
101
+ "pattern": "setup.py"
102
+ }
103
+ },
104
+ "golang": {
105
+ "go.mod": {
106
+ "pattern": "go.mod"
107
+ },
108
+ "go.sum": {
109
+ "pattern": "go.sum"
110
+ }
111
+ },
112
+ "java": {
113
+ "pom.xml": {
114
+ "pattern": "pom.xml"
115
+ }
116
+ }
117
+ }
118
+
49
119
 
50
120
  def encode_key(token: str) -> None:
51
121
  """
@@ -287,125 +357,61 @@ class Core:
287
357
  return manifest_files
288
358
 
289
359
  @staticmethod
290
- def create_sbom_output(diff: Diff) -> list:
291
- sbom = []
292
- for package_id in diff.packages:
293
- package: Package
294
- package = diff.packages[package_id]
295
- manifest_files = Core.get_manifest_files(package, diff.packages)
296
- item = {
297
- "id": package.id,
298
- "license": package.license,
299
- "license_text": package.license_text,
300
- "manifestFiles": manifest_files,
301
- "score": package.score,
302
- "size": package.size,
303
- "ecosystem": package.type,
304
- "alerts": package.alerts,
305
- "direct": package.direct,
306
- "name": package.name,
307
- "version": package.version,
308
- "author": package.author,
309
- "url": package.url
310
- }
311
- sbom.append(item)
360
+ def create_sbom_output(diff: Diff) -> dict:
361
+ base_path = f"orgs/{org_slug}/export/cdx"
362
+ path = f"{base_path}/{diff.id}"
363
+ result = do_request(path=path)
364
+ try:
365
+ sbom = result.json()
366
+ except Exception as error:
367
+ log.error(f"Unable to get CycloneDX Output for {diff.id}")
368
+ log.error(error)
369
+ sbom = {}
312
370
  return sbom
313
371
 
314
372
  @staticmethod
315
- def find_files(path: str, new_files: list = None) -> list:
373
+ def match_supported_files(path: str, files: list) -> list:
374
+ matched = []
375
+ for ecosystem in socket_globs:
376
+ patterns = socket_globs[ecosystem]
377
+ for file_name in patterns:
378
+ pattern = patterns[file_name]["pattern"]
379
+ # path_pattern = f"**/{pattern}"
380
+ for file in files:
381
+ if "\\" in file:
382
+ file = file.replace("\\", "/")
383
+ if PurePath(file).match(pattern):
384
+ matched.append(file)
385
+ return matched
386
+
387
+ @staticmethod
388
+ def find_files(path: str, files: list = None) -> list:
316
389
  """
317
390
  Globs the path for supported manifest files.
318
391
  Note: Might move the source to a JSON file
319
392
  :param path: Str - path to where the manifest files are located
320
- :param new_files:
393
+ :param files: override finding the manifest files using the glob matcher
321
394
  :return:
322
395
  """
323
- socket_globs = {
324
- "npm": {
325
- "package.json": {
326
- "pattern": "package.json"
327
- },
328
- "package-lock.json": {
329
- "pattern": "package-lock.json"
330
- },
331
- "npm-shrinkwrap.json": {
332
- "pattern": "npm-shrinkwrap.json"
333
- },
334
- "yarn.lock": {
335
- "pattern": "yarn.lock"
336
- },
337
- "pnpm-lock.yaml": {
338
- "pattern": "pnpm-lock.yaml"
339
- },
340
- "pnpm-lock.yml": {
341
- "pattern": "pnpm-lock.yml"
342
- },
343
- "pnpm-workspace.yaml": {
344
- "pattern": "pnpm-workspace.yaml"
345
- },
346
- "pnpm-workspace.yml": {
347
- "pattern": "pnpm-workspace.yml"
348
- }
349
- },
350
- "pypi": {
351
- "pipfile": {
352
- "pattern": "pipfile"
353
- },
354
- "pyproject.toml": {
355
- "pattern": "pyproject.toml"
356
- },
357
- "requirements.txt": {
358
- "pattern": "*requirements.txt"
359
- },
360
- "requirements": {
361
- "pattern": "requirements/*.txt"
362
- },
363
- "requirements-*.txt": {
364
- "pattern": "requirements-*.txt"
365
- },
366
- "requirements_*.txt": {
367
- "pattern": "requirements_*.txt"
368
- },
369
- "requirements.frozen": {
370
- "pattern": "requirements.frozen"
371
- },
372
- "setup.py": {
373
- "pattern": "setup.py"
374
- }
375
- },
376
- "golang": {
377
- "go.mod": {
378
- "pattern": "go.mod"
379
- },
380
- "go.sum": {
381
- "pattern": "go.sum"
382
- }
383
- },
384
- "java": {
385
- "pom.xml": {
386
- "pattern": "pom.xml"
387
- }
388
- }
389
- }
390
396
  all_files = []
391
397
  for ecosystem in socket_globs:
392
398
  patterns = socket_globs[ecosystem]
393
399
  for file_name in patterns:
394
400
  pattern = patterns[file_name]["pattern"]
395
401
  file_path = f"{path}/**/{pattern}"
396
- files = glob(file_path, recursive=True)
402
+ if files is None or len(files) == 0:
403
+ files = glob(file_path, recursive=True)
404
+ else:
405
+ files = Core.match_supported_files(path, files)
397
406
  for file in files:
398
- if "/" in file:
399
- _, base_name = file.rsplit("/", 1)
400
- else:
401
- base_name = file
402
- if new_files is not None and len(new_files) > 0 and base_name not in new_files:
403
- continue
404
407
  if platform.system() == "Windows":
405
408
  file = file.replace("\\", "/")
409
+ if path not in file:
410
+ file = f"{path}/{file}"
406
411
  found_path, file_name = file.rsplit("/", 1)
407
412
  details = (found_path, file_name)
408
- all_files.append(details)
413
+ if details not in all_files:
414
+ all_files.append(details)
409
415
  return all_files
410
416
 
411
417
  @staticmethod
@@ -480,7 +486,13 @@ class Core:
480
486
  return full_scan
481
487
 
482
488
  @staticmethod
483
- def create_new_diff(path: str, params: FullScanParams, workspace: str, new_files: list = None) -> Diff:
489
+ def create_new_diff(
490
+ path: str,
491
+ params: FullScanParams,
492
+ workspace: str,
493
+ new_files: list = None,
494
+ no_change: bool = False
495
+ ) -> Diff:
484
496
  """
485
497
  1. Get the head full scan. If it isn't present because this repo doesn't exist yet return an Empty full scan.
486
498
  2. Create a new Full scan for the current run
@@ -490,9 +502,14 @@ class Core:
490
502
  :param params: FullScanParams - Query params for the Full Scan endpoint
491
503
  :param workspace: str - Path for workspace
492
504
  :param new_files:
505
+ :param no_change:
493
506
  :return:
494
507
  """
508
+ if no_change:
509
+ return Diff()
495
510
  files = Core.find_files(path, new_files)
511
+ if files is None or len(files) == 0:
512
+ return Diff()
496
513
  try:
497
514
  head_full_scan_id = Core.get_head_scan_for_repo(params.repo)
498
515
  if head_full_scan_id is None or head_full_scan_id == "":
@@ -505,17 +522,15 @@ class Core:
505
522
  log.info(f"Total time to get head full-scan {total_head_time: .2f}")
506
523
  except APIResourceNotFound:
507
524
  head_full_scan = []
508
- if files is not None and len(files) > 0:
509
- new_scan_start = time.time()
510
- new_full_scan = Core.create_full_scan(files, params, workspace)
511
- new_full_scan.packages = Core.create_sbom_dict(new_full_scan.sbom_artifacts)
512
- new_scan_end = time.time()
513
- total_new_time = new_scan_end - new_scan_start
514
- log.info(f"Total time to get new full-scan {total_new_time: .2f}")
515
- diff_report = Core.compare_sboms(new_full_scan.sbom_artifacts, head_full_scan)
516
- diff_report.packages = new_full_scan.packages
517
- else:
518
- diff_report = Diff()
525
+ new_scan_start = time.time()
526
+ new_full_scan = Core.create_full_scan(files, params, workspace)
527
+ new_full_scan.packages = Core.create_sbom_dict(new_full_scan.sbom_artifacts)
528
+ new_scan_end = time.time()
529
+ total_new_time = new_scan_end - new_scan_start
530
+ log.info(f"Total time to get new full-scan {total_new_time: .2f}")
531
+ diff_report = Core.compare_sboms(new_full_scan.sbom_artifacts, head_full_scan)
532
+ diff_report.packages = new_full_scan.packages
533
+ diff_report.id = new_full_scan.id
519
534
  return diff_report
520
535
 
521
536
  @staticmethod
@@ -143,8 +143,9 @@ parser.add_argument(
143
143
  )
144
144
 
145
145
 
146
- def output_console_comments(diff_report) -> None:
146
+ def output_console_comments(diff_report: Diff, sbom_file_name: str = None) -> None:
147
147
  console_security_comment = Messages.create_console_security_alert_table(diff_report)
148
+ save_sbom_file(diff_report, sbom_file_name)
148
149
  if len(diff_report.new_alerts) > 0:
149
150
  log.info("Security issues detected by Socket Security")
150
151
  log.info(console_security_comment)
@@ -153,13 +154,18 @@ def output_console_comments(diff_report) -> None:
153
154
  log.info("No New Security issues detected by Socket Security")
154
155
 
155
156
 
156
- def output_console_json(diff_report) -> None:
157
+ def output_console_json(diff_report: Diff, sbom_file_name: str = None) -> None:
157
158
  console_security_comment = Messages.create_security_comment_json(diff_report)
159
+ save_sbom_file(diff_report, sbom_file_name)
158
160
  print(json.dumps(console_security_comment))
159
161
  if len(diff_report.new_alerts) > 0:
160
162
  sys.exit(1)
161
163
 
162
164
 
165
+ def save_sbom_file(diff_report: Diff, sbom_file_name: str = None):
166
+ if diff_report is not None and sbom_file_name is not None:
167
+ Core.save_file(sbom_file_name, json.dumps(Core.create_sbom_output(diff_report)))
168
+
163
169
  def cli():
164
170
  try:
165
171
  main_code()
@@ -200,6 +206,7 @@ def main_code():
200
206
  api_token = os.getenv("SOCKET_SECURITY_API_KEY") or arguments.api_token
201
207
  try:
202
208
  files = json.loads(files)
209
+ is_repo = True
203
210
  except Exception as error:
204
211
  log.error(f"Unable to parse {files}")
205
212
  log.error(error)
@@ -221,7 +228,9 @@ def main_code():
221
228
  commit_message = git_repo.commit_message
222
229
  if len(files) == 0 and not ignore_commit_files:
223
230
  files = git_repo.changed_files
231
+ is_repo = True
224
232
  except InvalidGitRepositoryError:
233
+ is_repo = False
225
234
  pass
226
235
  # git_repo = None
227
236
  if repo is None:
@@ -241,6 +250,10 @@ def main_code():
241
250
  if scm is not None:
242
251
  default_branch = scm.is_default_branch
243
252
 
253
+ if is_repo and files is not None and len(files) == 0 and not ignore_commit_files:
254
+ no_change = True
255
+ else:
256
+ no_change = False
244
257
  base_api_url = os.getenv("BASE_API_URL") or None
245
258
  core = Core(token=api_token, request_timeout=6000, base_api_url=base_api_url)
246
259
  set_as_pending_head = False
@@ -266,7 +279,7 @@ def main_code():
266
279
  elif scm is not None and scm.check_event_type() != "comment":
267
280
  log.info("Push initiated flow")
268
281
  diff: Diff
269
- diff = core.create_new_diff(target_path, params, workspace=target_path, new_files=files)
282
+ diff = core.create_new_diff(target_path, params, workspace=target_path, new_files=files, no_change=no_change)
270
283
  if scm.check_event_type() == "diff":
271
284
  log.info("Starting comment logic for PR/MR event")
272
285
  log.debug(f"Getting comments for Repo {scm.repository} for PR {scm.pr_number}")
@@ -297,17 +310,17 @@ def main_code():
297
310
  log.info("Not a PR/MR event no comment needed")
298
311
  if enable_json:
299
312
  log.debug("Outputting JSON Results")
300
- output_console_json(diff)
313
+ output_console_json(diff, sbom_file)
301
314
  else:
302
- output_console_comments(diff)
315
+ output_console_comments(diff, sbom_file)
303
316
  else:
304
317
  log.info("API Mode")
305
318
  diff: Diff
306
- diff = core.create_new_diff(target_path, params, workspace=target_path, new_files=files)
319
+ diff = core.create_new_diff(target_path, params, workspace=target_path, new_files=files, no_change=no_change)
307
320
  if enable_json:
308
- output_console_json(diff)
321
+ output_console_json(diff, sbom_file)
309
322
  else:
310
- output_console_comments(diff)
323
+ output_console_comments(diff, sbom_file)
311
324
  if diff is not None and license_mode:
312
325
  all_packages = {}
313
326
  for package_id in diff.packages:
@@ -325,8 +338,6 @@ def main_code():
325
338
  }
326
339
  all_packages[package_id] = output
327
340
  core.save_file(license_file, json.dumps(all_packages))
328
- if diff is not None and sbom_file is not None:
329
- core.save_file(sbom_file, json.dumps(core.create_sbom_output(diff)))
330
341
 
331
342
 
332
343
  if __name__ == '__main__':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: socketsecurity
3
- Version: 0.0.98
3
+ Version: 0.0.99
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>
File without changes