Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "hatchling.build"

[project]
name = "socketsecurity"
version = "2.2.43"
version = "2.2.48"
requires-python = ">= 3.10"
license = {"file" = "LICENSE"}
dependencies = [
Expand All @@ -16,7 +16,7 @@ dependencies = [
'GitPython',
'packaging',
'python-dotenv',
'socketdev>=3.0.21,<4.0.0',
'socketdev>=3.0.22,<4.0.0',
"bs4>=0.0.2",
]
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion socketsecurity/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__author__ = 'socket.dev'
__version__ = '2.2.43'
__version__ = '2.2.48'
USER_AGENT = f'SocketPythonCLI/{__version__}'
13 changes: 13 additions & 0 deletions socketsecurity/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class CliConfig:
reach_concurrency: Optional[int] = None
reach_additional_params: Optional[List[str]] = None
only_facts_file: bool = False
reach_use_only_pregenerated_sboms: bool = False

@classmethod
def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
Expand Down Expand Up @@ -139,6 +140,7 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
'reach_concurrency': args.reach_concurrency,
'reach_additional_params': args.reach_additional_params,
'only_facts_file': args.only_facts_file,
'reach_use_only_pregenerated_sboms': args.reach_use_only_pregenerated_sboms,
'version': __version__
}
try:
Expand Down Expand Up @@ -175,6 +177,11 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
logging.error("--only-facts-file requires --reach to be specified")
exit(1)

# Validate that reach_use_only_pregenerated_sboms requires reach
if args.reach_use_only_pregenerated_sboms and not args.reach:
logging.error("--reach-use-only-pregenerated-sboms requires --reach to be specified")
exit(1)

# Validate reach_concurrency is >= 1 if provided
if args.reach_concurrency is not None and args.reach_concurrency < 1:
logging.error("--reach-concurrency must be >= 1")
Expand Down Expand Up @@ -602,6 +609,12 @@ def create_argument_parser() -> argparse.ArgumentParser:
action="store_true",
help="Submit only the .socket.facts.json file when creating full scan (requires --reach)"
)
reachability_group.add_argument(
"--reach-use-only-pregenerated-sboms",
dest="reach_use_only_pregenerated_sboms",
action="store_true",
help="When using this option, the scan is created based only on pre-generated CDX and SPDX files in your project. (requires --reach)"
)

parser.add_argument(
'--version',
Expand Down
59 changes: 46 additions & 13 deletions socketsecurity/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,12 +281,13 @@ def format_bytes(bytes_value):
except Exception as e:
log.error(f"Failed to save manifest tar.gz to {output_path}: {e}")

def find_files(self, path: str) -> List[str]:
def find_files(self, path: str, ecosystems: Optional[List[str]] = None) -> List[str]:
"""
Finds supported manifest files in the given path.

Args:
path: Path to search for manifest files.
ecosystems: Optional list of ecosystems to include. If None, all ecosystems are included.

Returns:
List of found manifest file paths.
Expand All @@ -299,6 +300,9 @@ def find_files(self, path: str) -> List[str]:
patterns = self.get_supported_patterns()

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

return file_list

def find_sbom_files(self, path: str) -> List[str]:
"""
Finds only pre-generated SBOM files (CDX and SPDX) in the given path.

This is used with --reach-use-only-pregenerated-sboms to find only
pre-computed CycloneDX and SPDX manifest files.

Args:
path: Path to search for SBOM files.

Returns:
List of found CDX and SPDX file paths.
"""
log.debug("Starting Find SBOM Files (CDX and SPDX only)")
sbom_ecosystems = ['cdx', 'spdx']
return self.find_files(path, ecosystems=sbom_ecosystems)

def get_supported_patterns(self) -> Dict:
"""
Gets supported file patterns from the Socket API.
Expand Down Expand Up @@ -547,7 +568,8 @@ def create_full_scan_with_report_url(
no_change: bool = False,
save_files_list_path: Optional[str] = None,
save_manifest_tar_path: Optional[str] = None,
base_paths: Optional[List[str]] = None
base_paths: Optional[List[str]] = None,
explicit_files: Optional[List[str]] = None
) -> Diff:
"""Create a new full scan and return with html_report_url.

Expand All @@ -558,6 +580,7 @@ def create_full_scan_with_report_url(
save_files_list_path: Optional path to save submitted files list for debugging
save_manifest_tar_path: Optional path to save manifest files tar.gz archive
base_paths: List of base paths for the scan (optional)
explicit_files: Optional list of explicit files to use instead of discovering files

Returns:
Dict with full scan data including html_report_url
Expand All @@ -571,11 +594,15 @@ def create_full_scan_with_report_url(
if no_change:
return diff

# Find manifest files from all paths
all_files = []
for path in paths:
files = self.find_files(path)
all_files.extend(files)
# Use explicit files if provided, otherwise find manifest files from all paths
if explicit_files is not None:
all_files = explicit_files
log.debug(f"Using {len(all_files)} explicit files instead of discovering files")
else:
all_files = []
for path in paths:
files = self.find_files(path)
all_files.extend(files)

# Save submitted files list if requested
if save_files_list_path and all_files:
Expand Down Expand Up @@ -943,7 +970,8 @@ def create_new_diff(
no_change: bool = False,
save_files_list_path: Optional[str] = None,
save_manifest_tar_path: Optional[str] = None,
base_paths: Optional[List[str]] = None
base_paths: Optional[List[str]] = None,
explicit_files: Optional[List[str]] = None
) -> Diff:
"""Create a new diff using the Socket SDK.

Expand All @@ -954,16 +982,21 @@ def create_new_diff(
save_files_list_path: Optional path to save submitted files list for debugging
save_manifest_tar_path: Optional path to save manifest files tar.gz archive
base_paths: List of base paths for the scan (optional)
explicit_files: Optional list of explicit files to use instead of discovering files
"""
log.debug(f"starting create_new_diff with no_change: {no_change}")
if no_change:
return Diff(id="NO_DIFF_RAN", diff_url="", report_url="")

# Find manifest files from all paths
all_files = []
for path in paths:
files = self.find_files(path)
all_files.extend(files)
# Use explicit files if provided, otherwise find manifest files from all paths
if explicit_files is not None:
all_files = explicit_files
log.debug(f"Using {len(all_files)} explicit files instead of discovering files")
else:
all_files = []
for path in paths:
files = self.find_files(path)
all_files.extend(files)

# Save submitted files list if requested
if save_files_list_path and all_files:
Expand Down
11 changes: 8 additions & 3 deletions socketsecurity/core/tools/reachability.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,11 @@ def run_reachability_analysis(
additional_params: Optional[List[str]] = None,
allow_unverified: bool = False,
enable_debug: bool = False,
use_only_pregenerated_sboms: bool = False,
) -> Dict[str, Any]:
"""
Run reachability analysis.

Args:
org_slug: Socket organization slug
target_directory: Directory to analyze
Expand All @@ -125,7 +126,8 @@ def run_reachability_analysis(
additional_params: Additional parameters to pass to coana CLI
allow_unverified: Disable SSL certificate verification (sets NODE_TLS_REJECT_UNAUTHORIZED=0)
enable_debug: Enable debug mode (passes -d flag to coana CLI)

use_only_pregenerated_sboms: Use only pre-generated CDX and SPDX files for the scan

Returns:
Dict containing scan_id and report_path
"""
Expand Down Expand Up @@ -179,7 +181,10 @@ def run_reachability_analysis(

if enable_debug:
cmd.append("-d")


if use_only_pregenerated_sboms:
cmd.append("--use-only-pregenerated-sboms")

# Add any additional parameters provided by the user
if additional_params:
cmd.extend(additional_params)
Expand Down
47 changes: 36 additions & 11 deletions socketsecurity/socketcli.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ def main_code():

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

# Git setup
is_repo = False
Expand Down Expand Up @@ -230,12 +232,14 @@ def main_code():
# Run reachability analysis if enabled
if config.reach:
from socketsecurity.core.tools.reachability import ReachabilityAnalyzer

log.info("Starting reachability analysis...")

# Find manifest files in scan paths (excluding .socket.facts.json to avoid circular dependency)
log.info("Finding manifest files for reachability analysis...")
manifest_files = []

# Always find all manifest files for the tar hash upload
for scan_path in scan_paths:
scan_manifests = core.find_files(scan_path)
# Filter out .socket.facts.json files from manifest upload
Expand Down Expand Up @@ -289,7 +293,8 @@ def main_code():
concurrency=config.reach_concurrency,
additional_params=config.reach_additional_params,
allow_unverified=config.allow_unverified,
enable_debug=config.enable_debug
enable_debug=config.enable_debug,
use_only_pregenerated_sboms=config.reach_use_only_pregenerated_sboms
)

log.info(f"Reachability analysis completed successfully")
Expand All @@ -301,6 +306,17 @@ def main_code():
if config.only_facts_file:
facts_file_to_submit = os.path.abspath(output_path)
log.info(f"Only-facts-file mode: will submit only {facts_file_to_submit}")

# If reach-use-only-pregenerated-sboms mode, submit CDX, SPDX, and facts file
if config.reach_use_only_pregenerated_sboms:
# Find only CDX and SPDX files for the final scan submission
sbom_files_to_submit = []
for scan_path in scan_paths:
sbom_files_to_submit.extend(core.find_sbom_files(scan_path))
# Use relative path for facts file
if os.path.exists(output_path):
sbom_files_to_submit.append(output_path)
log.info(f"Pre-generated SBOMs mode: will submit {len(sbom_files_to_submit)} files (CDX, SPDX, and facts file)")

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

# Override files if reach-use-only-pregenerated-sboms mode is active
if sbom_files_to_submit:
specified_files = sbom_files_to_submit
files_explicitly_specified = True
log.debug(f"Overriding files to submit only SBOM files (CDX, SPDX, and facts): {sbom_files_to_submit}")

# Determine files to check based on the new logic
files_to_check = []
force_api_mode = False
Expand Down Expand Up @@ -452,7 +474,7 @@ def main_code():
log.info("Push initiated flow")
if scm.check_event_type() == "diff":
log.info("Starting comment logic for PR/MR event")
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)
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)
comments = scm.get_comments_for_pr()
log.debug("Removing comment alerts")

Expand Down Expand Up @@ -505,14 +527,14 @@ def main_code():
)
else:
log.info("Starting non-PR/MR flow")
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)
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)

output_handler.handle_output(diff)

elif config.enable_diff and not force_api_mode:
# New logic: --enable-diff forces diff mode even with --integration api (no SCM)
log.info("Diff mode enabled without SCM integration")
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)
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)
output_handler.handle_output(diff)

elif config.enable_diff and force_api_mode:
Expand All @@ -530,12 +552,13 @@ def main_code():
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
base_paths=base_paths,
explicit_files=sbom_files_to_submit
)
log.info(f"Full scan created with ID: {diff.id}")
log.info(f"Full scan report URL: {diff.report_url}")
output_handler.handle_output(diff)

else:
if force_api_mode:
log.info("No Manifest files changed, creating Socket Report")
Expand All @@ -550,7 +573,8 @@ def main_code():
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
base_paths=base_paths,
explicit_files=sbom_files_to_submit
)
log.info(f"Full scan created with ID: {diff.id}")
log.info(f"Full scan report URL: {diff.report_url}")
Expand All @@ -561,7 +585,8 @@ def main_code():
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
base_paths=base_paths,
explicit_files=sbom_files_to_submit
)
output_handler.handle_output(diff)

Expand Down
Loading
Loading