Skip to content

Commit c2deb21

Browse files
mtorpdacoburn
andauthored
Adds the --reach-use-only-pregenerated-sboms flag (#138)
* add flag --reach-use-only-pregenerated-sboms to exclude non-CDX and SPDX files from a Tier 1 reachability scan * prepare for 2.2.44 * ensure only CDX/SPDX manifests and the .socket.facts.json are included in the final scan * Moved the logic for find_sbom_files to find_files to avoid code duplication. Left the new function in place and just called find_files with the optional params * Bumped minimum required SDK version with fix for no version in results * bumped to fixed version of the SDK for no version in results * Upgraded socketdev sdk in lock file --------- Co-authored-by: Douglas Coburn <douglas@dactbc.com>
1 parent d06deb0 commit c2deb21

File tree

7 files changed

+773
-588
lines changed

7 files changed

+773
-588
lines changed

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
66

77
[project]
88
name = "socketsecurity"
9-
version = "2.2.43"
9+
version = "2.2.48"
1010
requires-python = ">= 3.10"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [
@@ -16,7 +16,7 @@ dependencies = [
1616
'GitPython',
1717
'packaging',
1818
'python-dotenv',
19-
'socketdev>=3.0.21,<4.0.0',
19+
'socketdev>=3.0.22,<4.0.0',
2020
"bs4>=0.0.2",
2121
]
2222
readme = "README.md"

socketsecurity/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__author__ = 'socket.dev'
2-
__version__ = '2.2.43'
2+
__version__ = '2.2.48'
33
USER_AGENT = f'SocketPythonCLI/{__version__}'

socketsecurity/config.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class CliConfig:
7777
reach_concurrency: Optional[int] = None
7878
reach_additional_params: Optional[List[str]] = None
7979
only_facts_file: bool = False
80+
reach_use_only_pregenerated_sboms: bool = False
8081

8182
@classmethod
8283
def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
@@ -139,6 +140,7 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
139140
'reach_concurrency': args.reach_concurrency,
140141
'reach_additional_params': args.reach_additional_params,
141142
'only_facts_file': args.only_facts_file,
143+
'reach_use_only_pregenerated_sboms': args.reach_use_only_pregenerated_sboms,
142144
'version': __version__
143145
}
144146
try:
@@ -175,6 +177,11 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
175177
logging.error("--only-facts-file requires --reach to be specified")
176178
exit(1)
177179

180+
# Validate that reach_use_only_pregenerated_sboms requires reach
181+
if args.reach_use_only_pregenerated_sboms and not args.reach:
182+
logging.error("--reach-use-only-pregenerated-sboms requires --reach to be specified")
183+
exit(1)
184+
178185
# Validate reach_concurrency is >= 1 if provided
179186
if args.reach_concurrency is not None and args.reach_concurrency < 1:
180187
logging.error("--reach-concurrency must be >= 1")
@@ -602,6 +609,12 @@ def create_argument_parser() -> argparse.ArgumentParser:
602609
action="store_true",
603610
help="Submit only the .socket.facts.json file when creating full scan (requires --reach)"
604611
)
612+
reachability_group.add_argument(
613+
"--reach-use-only-pregenerated-sboms",
614+
dest="reach_use_only_pregenerated_sboms",
615+
action="store_true",
616+
help="When using this option, the scan is created based only on pre-generated CDX and SPDX files in your project. (requires --reach)"
617+
)
605618

606619
parser.add_argument(
607620
'--version',

socketsecurity/core/__init__.py

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -281,12 +281,13 @@ def format_bytes(bytes_value):
281281
except Exception as e:
282282
log.error(f"Failed to save manifest tar.gz to {output_path}: {e}")
283283

284-
def find_files(self, path: str) -> List[str]:
284+
def find_files(self, path: str, ecosystems: Optional[List[str]] = None) -> List[str]:
285285
"""
286286
Finds supported manifest files in the given path.
287287
288288
Args:
289289
path: Path to search for manifest files.
290+
ecosystems: Optional list of ecosystems to include. If None, all ecosystems are included.
290291
291292
Returns:
292293
List of found manifest file paths.
@@ -299,6 +300,9 @@ def find_files(self, path: str) -> List[str]:
299300
patterns = self.get_supported_patterns()
300301

301302
for ecosystem in patterns:
303+
# If ecosystems filter is provided, only include specified ecosystems
304+
if ecosystems is not None and ecosystem not in ecosystems:
305+
continue
302306
if ecosystem in self.config.excluded_ecosystems:
303307
continue
304308
log.debug(f'Scanning ecosystem: {ecosystem}')
@@ -343,6 +347,23 @@ def find_files(self, path: str) -> List[str]:
343347

344348
return file_list
345349

350+
def find_sbom_files(self, path: str) -> List[str]:
351+
"""
352+
Finds only pre-generated SBOM files (CDX and SPDX) in the given path.
353+
354+
This is used with --reach-use-only-pregenerated-sboms to find only
355+
pre-computed CycloneDX and SPDX manifest files.
356+
357+
Args:
358+
path: Path to search for SBOM files.
359+
360+
Returns:
361+
List of found CDX and SPDX file paths.
362+
"""
363+
log.debug("Starting Find SBOM Files (CDX and SPDX only)")
364+
sbom_ecosystems = ['cdx', 'spdx']
365+
return self.find_files(path, ecosystems=sbom_ecosystems)
366+
346367
def get_supported_patterns(self) -> Dict:
347368
"""
348369
Gets supported file patterns from the Socket API.
@@ -547,7 +568,8 @@ def create_full_scan_with_report_url(
547568
no_change: bool = False,
548569
save_files_list_path: Optional[str] = None,
549570
save_manifest_tar_path: Optional[str] = None,
550-
base_paths: Optional[List[str]] = None
571+
base_paths: Optional[List[str]] = None,
572+
explicit_files: Optional[List[str]] = None
551573
) -> Diff:
552574
"""Create a new full scan and return with html_report_url.
553575
@@ -558,6 +580,7 @@ def create_full_scan_with_report_url(
558580
save_files_list_path: Optional path to save submitted files list for debugging
559581
save_manifest_tar_path: Optional path to save manifest files tar.gz archive
560582
base_paths: List of base paths for the scan (optional)
583+
explicit_files: Optional list of explicit files to use instead of discovering files
561584
562585
Returns:
563586
Dict with full scan data including html_report_url
@@ -571,11 +594,15 @@ def create_full_scan_with_report_url(
571594
if no_change:
572595
return diff
573596

574-
# Find manifest files from all paths
575-
all_files = []
576-
for path in paths:
577-
files = self.find_files(path)
578-
all_files.extend(files)
597+
# Use explicit files if provided, otherwise find manifest files from all paths
598+
if explicit_files is not None:
599+
all_files = explicit_files
600+
log.debug(f"Using {len(all_files)} explicit files instead of discovering files")
601+
else:
602+
all_files = []
603+
for path in paths:
604+
files = self.find_files(path)
605+
all_files.extend(files)
579606

580607
# Save submitted files list if requested
581608
if save_files_list_path and all_files:
@@ -943,7 +970,8 @@ def create_new_diff(
943970
no_change: bool = False,
944971
save_files_list_path: Optional[str] = None,
945972
save_manifest_tar_path: Optional[str] = None,
946-
base_paths: Optional[List[str]] = None
973+
base_paths: Optional[List[str]] = None,
974+
explicit_files: Optional[List[str]] = None
947975
) -> Diff:
948976
"""Create a new diff using the Socket SDK.
949977
@@ -954,16 +982,21 @@ def create_new_diff(
954982
save_files_list_path: Optional path to save submitted files list for debugging
955983
save_manifest_tar_path: Optional path to save manifest files tar.gz archive
956984
base_paths: List of base paths for the scan (optional)
985+
explicit_files: Optional list of explicit files to use instead of discovering files
957986
"""
958987
log.debug(f"starting create_new_diff with no_change: {no_change}")
959988
if no_change:
960989
return Diff(id="NO_DIFF_RAN", diff_url="", report_url="")
961990

962-
# Find manifest files from all paths
963-
all_files = []
964-
for path in paths:
965-
files = self.find_files(path)
966-
all_files.extend(files)
991+
# Use explicit files if provided, otherwise find manifest files from all paths
992+
if explicit_files is not None:
993+
all_files = explicit_files
994+
log.debug(f"Using {len(all_files)} explicit files instead of discovering files")
995+
else:
996+
all_files = []
997+
for path in paths:
998+
files = self.find_files(path)
999+
all_files.extend(files)
9671000

9681001
# Save submitted files list if requested
9691002
if save_files_list_path and all_files:

socketsecurity/core/tools/reachability.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,11 @@ def run_reachability_analysis(
101101
additional_params: Optional[List[str]] = None,
102102
allow_unverified: bool = False,
103103
enable_debug: bool = False,
104+
use_only_pregenerated_sboms: bool = False,
104105
) -> Dict[str, Any]:
105106
"""
106107
Run reachability analysis.
107-
108+
108109
Args:
109110
org_slug: Socket organization slug
110111
target_directory: Directory to analyze
@@ -125,7 +126,8 @@ def run_reachability_analysis(
125126
additional_params: Additional parameters to pass to coana CLI
126127
allow_unverified: Disable SSL certificate verification (sets NODE_TLS_REJECT_UNAUTHORIZED=0)
127128
enable_debug: Enable debug mode (passes -d flag to coana CLI)
128-
129+
use_only_pregenerated_sboms: Use only pre-generated CDX and SPDX files for the scan
130+
129131
Returns:
130132
Dict containing scan_id and report_path
131133
"""
@@ -179,7 +181,10 @@ def run_reachability_analysis(
179181

180182
if enable_debug:
181183
cmd.append("-d")
182-
184+
185+
if use_only_pregenerated_sboms:
186+
cmd.append("--use-only-pregenerated-sboms")
187+
183188
# Add any additional parameters provided by the user
184189
if additional_params:
185190
cmd.extend(additional_params)

socketsecurity/socketcli.py

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ def main_code():
167167

168168
# Variable to track if we need to override files with facts file
169169
facts_file_to_submit = None
170+
# Variable to track SBOM files to submit when using --reach-use-only-pregenerated-sboms
171+
sbom_files_to_submit = None
170172

171173
# Git setup
172174
is_repo = False
@@ -230,12 +232,14 @@ def main_code():
230232
# Run reachability analysis if enabled
231233
if config.reach:
232234
from socketsecurity.core.tools.reachability import ReachabilityAnalyzer
233-
235+
234236
log.info("Starting reachability analysis...")
235-
237+
236238
# Find manifest files in scan paths (excluding .socket.facts.json to avoid circular dependency)
237239
log.info("Finding manifest files for reachability analysis...")
238240
manifest_files = []
241+
242+
# Always find all manifest files for the tar hash upload
239243
for scan_path in scan_paths:
240244
scan_manifests = core.find_files(scan_path)
241245
# Filter out .socket.facts.json files from manifest upload
@@ -289,7 +293,8 @@ def main_code():
289293
concurrency=config.reach_concurrency,
290294
additional_params=config.reach_additional_params,
291295
allow_unverified=config.allow_unverified,
292-
enable_debug=config.enable_debug
296+
enable_debug=config.enable_debug,
297+
use_only_pregenerated_sboms=config.reach_use_only_pregenerated_sboms
293298
)
294299

295300
log.info(f"Reachability analysis completed successfully")
@@ -301,6 +306,17 @@ def main_code():
301306
if config.only_facts_file:
302307
facts_file_to_submit = os.path.abspath(output_path)
303308
log.info(f"Only-facts-file mode: will submit only {facts_file_to_submit}")
309+
310+
# If reach-use-only-pregenerated-sboms mode, submit CDX, SPDX, and facts file
311+
if config.reach_use_only_pregenerated_sboms:
312+
# Find only CDX and SPDX files for the final scan submission
313+
sbom_files_to_submit = []
314+
for scan_path in scan_paths:
315+
sbom_files_to_submit.extend(core.find_sbom_files(scan_path))
316+
# Use relative path for facts file
317+
if os.path.exists(output_path):
318+
sbom_files_to_submit.append(output_path)
319+
log.info(f"Pre-generated SBOMs mode: will submit {len(sbom_files_to_submit)} files (CDX, SPDX, and facts file)")
304320

305321
except Exception as e:
306322
log.error(f"Reachability analysis failed: {str(e)}")
@@ -331,6 +347,12 @@ def main_code():
331347
files_explicitly_specified = True
332348
log.debug(f"Overriding files to only submit facts file: {facts_file_to_submit}")
333349

350+
# Override files if reach-use-only-pregenerated-sboms mode is active
351+
if sbom_files_to_submit:
352+
specified_files = sbom_files_to_submit
353+
files_explicitly_specified = True
354+
log.debug(f"Overriding files to submit only SBOM files (CDX, SPDX, and facts): {sbom_files_to_submit}")
355+
334356
# Determine files to check based on the new logic
335357
files_to_check = []
336358
force_api_mode = False
@@ -452,7 +474,7 @@ def main_code():
452474
log.info("Push initiated flow")
453475
if scm.check_event_type() == "diff":
454476
log.info("Starting comment logic for PR/MR event")
455-
diff = core.create_new_diff(scan_paths, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar, base_paths=base_paths)
477+
diff = core.create_new_diff(scan_paths, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar, base_paths=base_paths, explicit_files=sbom_files_to_submit)
456478
comments = scm.get_comments_for_pr()
457479
log.debug("Removing comment alerts")
458480

@@ -505,14 +527,14 @@ def main_code():
505527
)
506528
else:
507529
log.info("Starting non-PR/MR flow")
508-
diff = core.create_new_diff(scan_paths, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar, base_paths=base_paths)
530+
diff = core.create_new_diff(scan_paths, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar, base_paths=base_paths, explicit_files=sbom_files_to_submit)
509531

510532
output_handler.handle_output(diff)
511-
533+
512534
elif config.enable_diff and not force_api_mode:
513535
# New logic: --enable-diff forces diff mode even with --integration api (no SCM)
514536
log.info("Diff mode enabled without SCM integration")
515-
diff = core.create_new_diff(scan_paths, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar, base_paths=base_paths)
537+
diff = core.create_new_diff(scan_paths, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar, base_paths=base_paths, explicit_files=sbom_files_to_submit)
516538
output_handler.handle_output(diff)
517539

518540
elif config.enable_diff and force_api_mode:
@@ -530,12 +552,13 @@ def main_code():
530552
no_change=should_skip_scan,
531553
save_files_list_path=config.save_submitted_files_list,
532554
save_manifest_tar_path=config.save_manifest_tar,
533-
base_paths=base_paths
555+
base_paths=base_paths,
556+
explicit_files=sbom_files_to_submit
534557
)
535558
log.info(f"Full scan created with ID: {diff.id}")
536559
log.info(f"Full scan report URL: {diff.report_url}")
537560
output_handler.handle_output(diff)
538-
561+
539562
else:
540563
if force_api_mode:
541564
log.info("No Manifest files changed, creating Socket Report")
@@ -550,7 +573,8 @@ def main_code():
550573
no_change=should_skip_scan,
551574
save_files_list_path=config.save_submitted_files_list,
552575
save_manifest_tar_path=config.save_manifest_tar,
553-
base_paths=base_paths
576+
base_paths=base_paths,
577+
explicit_files=sbom_files_to_submit
554578
)
555579
log.info(f"Full scan created with ID: {diff.id}")
556580
log.info(f"Full scan report URL: {diff.report_url}")
@@ -561,7 +585,8 @@ def main_code():
561585
no_change=should_skip_scan,
562586
save_files_list_path=config.save_submitted_files_list,
563587
save_manifest_tar_path=config.save_manifest_tar,
564-
base_paths=base_paths
588+
base_paths=base_paths,
589+
explicit_files=sbom_files_to_submit
565590
)
566591
output_handler.handle_output(diff)
567592

0 commit comments

Comments
 (0)