socketsecurity 2.2.43__tar.gz → 2.2.55__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-2.2.43 → socketsecurity-2.2.55}/Dockerfile +1 -1
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/PKG-INFO +2 -2
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/pyproject.toml +3 -2
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/__init__.py +1 -1
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/config.py +28 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/__init__.py +100 -85
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/tools/reachability.py +8 -3
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/socketcli.py +63 -25
- socketsecurity-2.2.55/uv.lock +1541 -0
- socketsecurity-2.2.43/uv.lock +0 -1432
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.github/CODEOWNERS +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.github/workflows/docker-stable.yml +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.github/workflows/pr-preview.yml +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.github/workflows/release.yml +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.github/workflows/version-check.yml +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.gitignore +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.hooks/sync_version.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.pre-commit-config.yaml +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/.python-version +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/LICENSE +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/Makefile +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/README.md +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/docs/README.md +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/pytest.ini +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/scripts/build_container.sh +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/scripts/build_container_flexible.sh +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/scripts/deploy-test-docker.sh +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/scripts/deploy-test-pypi.sh +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/scripts/docker-entrypoint.sh +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/scripts/run.sh +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/classes.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/cli_client.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/exceptions.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/git_interface.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/helper/__init__.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/lazy_file_loader.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/logging.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/messages.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/resource_utils.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/scm/__init__.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/scm/base.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/scm/client.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/scm/github.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/scm/gitlab.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/scm_comments.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/socket_config.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/core/utils.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/output.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/plugins/__init__.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/plugins/base.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/plugins/jira.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/plugins/manager.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/plugins/slack.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/plugins/teams.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/socketsecurity/plugins/webhook.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/__init__.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/core/conftest.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/core/create_diff_input.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/core/test_diff_generation.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/core/test_package_and_alerts.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/core/test_sdk_methods.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/core/test_supporting_methods.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/data/fullscans/create_response.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/data/fullscans/diff/stream_diff.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/data/fullscans/head_scan/metadata.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/data/fullscans/new_scan/metadata.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/data/repos/repo_info_error.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/data/repos/repo_info_no_head.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/data/repos/repo_info_success.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/data/settings/security-policy.json +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/unit/__init__.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/unit/test_cli_config.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/unit/test_client.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/unit/test_config.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/unit/test_gitlab_auth.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/unit/test_gitlab_auth_fallback.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/tests/unit/test_output.py +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/workflows/bitbucket-pipelines.yml +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/workflows/github-actions.yml +0 -0
- {socketsecurity-2.2.43 → socketsecurity-2.2.55}/workflows/gitlab-ci.yml +0 -0
|
@@ -57,7 +57,7 @@ RUN if [ "$DOTNET_VERSION" = "6" ]; then \
|
|
|
57
57
|
fi
|
|
58
58
|
|
|
59
59
|
# Install additional tools
|
|
60
|
-
RUN npm install @coana-tech/cli -g && \
|
|
60
|
+
RUN npm install @coana-tech/cli socket -g && \
|
|
61
61
|
gem install bundler && \
|
|
62
62
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \
|
|
63
63
|
. ~/.cargo/env && \
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: socketsecurity
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.55
|
|
4
4
|
Summary: Socket Security CLI for CI/CD
|
|
5
5
|
Project-URL: Homepage, https://socket.dev
|
|
6
6
|
Author-email: Douglas Coburn <douglas@socket.dev>
|
|
@@ -40,7 +40,7 @@ Requires-Dist: packaging
|
|
|
40
40
|
Requires-Dist: prettytable
|
|
41
41
|
Requires-Dist: python-dotenv
|
|
42
42
|
Requires-Dist: requests
|
|
43
|
-
Requires-Dist: socketdev<4.0.0,>=3.0.
|
|
43
|
+
Requires-Dist: socketdev<4.0.0,>=3.0.22
|
|
44
44
|
Provides-Extra: dev
|
|
45
45
|
Requires-Dist: hatch; extra == 'dev'
|
|
46
46
|
Requires-Dist: pre-commit; extra == 'dev'
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "socketsecurity"
|
|
9
|
-
version = "2.2.
|
|
9
|
+
version = "2.2.55"
|
|
10
10
|
requires-python = ">= 3.10"
|
|
11
11
|
license = {"file" = "LICENSE"}
|
|
12
12
|
dependencies = [
|
|
@@ -16,7 +16,7 @@ dependencies = [
|
|
|
16
16
|
'GitPython',
|
|
17
17
|
'packaging',
|
|
18
18
|
'python-dotenv',
|
|
19
|
-
'socketdev>=3.0.
|
|
19
|
+
'socketdev>=3.0.22,<4.0.0',
|
|
20
20
|
"bs4>=0.0.2",
|
|
21
21
|
]
|
|
22
22
|
readme = "README.md"
|
|
@@ -165,3 +165,4 @@ include = ["socketsecurity", "LICENSE"]
|
|
|
165
165
|
dev = [
|
|
166
166
|
"pre-commit>=4.3.0",
|
|
167
167
|
]
|
|
168
|
+
|
|
@@ -77,6 +77,8 @@ class CliConfig:
|
|
|
77
77
|
reach_concurrency: Optional[int] = None
|
|
78
78
|
reach_additional_params: Optional[List[str]] = None
|
|
79
79
|
only_facts_file: bool = False
|
|
80
|
+
reach_use_only_pregenerated_sboms: bool = False
|
|
81
|
+
max_purl_batch_size: int = 5000
|
|
80
82
|
|
|
81
83
|
@classmethod
|
|
82
84
|
def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
|
|
@@ -105,6 +107,7 @@ class CliConfig:
|
|
|
105
107
|
'commit_sha': args.commit_sha,
|
|
106
108
|
'generate_license': args.generate_license,
|
|
107
109
|
'enable_debug': args.enable_debug,
|
|
110
|
+
'enable_diff': args.enable_diff,
|
|
108
111
|
'allow_unverified': args.allow_unverified,
|
|
109
112
|
'enable_json': args.enable_json,
|
|
110
113
|
'enable_sarif': args.enable_sarif,
|
|
@@ -139,6 +142,8 @@ class CliConfig:
|
|
|
139
142
|
'reach_concurrency': args.reach_concurrency,
|
|
140
143
|
'reach_additional_params': args.reach_additional_params,
|
|
141
144
|
'only_facts_file': args.only_facts_file,
|
|
145
|
+
'reach_use_only_pregenerated_sboms': args.reach_use_only_pregenerated_sboms,
|
|
146
|
+
'max_purl_batch_size': args.max_purl_batch_size,
|
|
142
147
|
'version': __version__
|
|
143
148
|
}
|
|
144
149
|
try:
|
|
@@ -175,11 +180,21 @@ class CliConfig:
|
|
|
175
180
|
logging.error("--only-facts-file requires --reach to be specified")
|
|
176
181
|
exit(1)
|
|
177
182
|
|
|
183
|
+
# Validate that reach_use_only_pregenerated_sboms requires reach
|
|
184
|
+
if args.reach_use_only_pregenerated_sboms and not args.reach:
|
|
185
|
+
logging.error("--reach-use-only-pregenerated-sboms requires --reach to be specified")
|
|
186
|
+
exit(1)
|
|
187
|
+
|
|
178
188
|
# Validate reach_concurrency is >= 1 if provided
|
|
179
189
|
if args.reach_concurrency is not None and args.reach_concurrency < 1:
|
|
180
190
|
logging.error("--reach-concurrency must be >= 1")
|
|
181
191
|
exit(1)
|
|
182
192
|
|
|
193
|
+
# Validate max_purl_batch_size is within allowed range
|
|
194
|
+
if args.max_purl_batch_size < 1 or args.max_purl_batch_size > 9999:
|
|
195
|
+
logging.error("--max-purl-batch-size must be between 1 and 9999")
|
|
196
|
+
exit(1)
|
|
197
|
+
|
|
183
198
|
return cls(**config_args)
|
|
184
199
|
|
|
185
200
|
def to_dict(self) -> dict:
|
|
@@ -439,6 +454,13 @@ def create_argument_parser() -> argparse.ArgumentParser:
|
|
|
439
454
|
action="store_true",
|
|
440
455
|
help="Exclude license details from the diff report (boosts performance for large repos)"
|
|
441
456
|
)
|
|
457
|
+
output_group.add_argument(
|
|
458
|
+
"--max-purl-batch-size",
|
|
459
|
+
dest="max_purl_batch_size",
|
|
460
|
+
type=int,
|
|
461
|
+
default=5000,
|
|
462
|
+
help="Maximum batch size for PURL endpoint calls when generating license info (default: 5000, min: 1, max: 9999)"
|
|
463
|
+
)
|
|
442
464
|
|
|
443
465
|
output_group.add_argument(
|
|
444
466
|
"--disable-security-issue",
|
|
@@ -602,6 +624,12 @@ def create_argument_parser() -> argparse.ArgumentParser:
|
|
|
602
624
|
action="store_true",
|
|
603
625
|
help="Submit only the .socket.facts.json file when creating full scan (requires --reach)"
|
|
604
626
|
)
|
|
627
|
+
reachability_group.add_argument(
|
|
628
|
+
"--reach-use-only-pregenerated-sboms",
|
|
629
|
+
dest="reach_use_only_pregenerated_sboms",
|
|
630
|
+
action="store_true",
|
|
631
|
+
help="When using this option, the scan is created based only on pre-generated CDX and SPDX files in your project. (requires --reach)"
|
|
632
|
+
)
|
|
605
633
|
|
|
606
634
|
parser.add_argument(
|
|
607
635
|
'--version',
|
|
@@ -281,12 +281,13 @@ class Core:
|
|
|
281
281
|
except Exception as e:
|
|
282
282
|
log.error(f"Failed to save manifest tar.gz to {output_path}: {e}")
|
|
283
283
|
|
|
284
|
-
def find_files(self, path: str) -> List[str]:
|
|
284
|
+
def find_files(self, path: str, ecosystems: Optional[List[str]] = None) -> List[str]:
|
|
285
285
|
"""
|
|
286
286
|
Finds supported manifest files in the given path.
|
|
287
287
|
|
|
288
288
|
Args:
|
|
289
289
|
path: Path to search for manifest files.
|
|
290
|
+
ecosystems: Optional list of ecosystems to include. If None, all ecosystems are included.
|
|
290
291
|
|
|
291
292
|
Returns:
|
|
292
293
|
List of found manifest file paths.
|
|
@@ -299,6 +300,9 @@ class Core:
|
|
|
299
300
|
patterns = self.get_supported_patterns()
|
|
300
301
|
|
|
301
302
|
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
|
|
302
306
|
if ecosystem in self.config.excluded_ecosystems:
|
|
303
307
|
continue
|
|
304
308
|
log.debug(f'Scanning ecosystem: {ecosystem}')
|
|
@@ -343,6 +347,23 @@ class Core:
|
|
|
343
347
|
|
|
344
348
|
return file_list
|
|
345
349
|
|
|
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
|
+
|
|
346
367
|
def get_supported_patterns(self) -> Dict:
|
|
347
368
|
"""
|
|
348
369
|
Gets supported file patterns from the Socket API.
|
|
@@ -547,7 +568,8 @@ class Core:
|
|
|
547
568
|
no_change: bool = False,
|
|
548
569
|
save_files_list_path: Optional[str] = None,
|
|
549
570
|
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
|
|
551
573
|
) -> Diff:
|
|
552
574
|
"""Create a new full scan and return with html_report_url.
|
|
553
575
|
|
|
@@ -558,6 +580,7 @@ class Core:
|
|
|
558
580
|
save_files_list_path: Optional path to save submitted files list for debugging
|
|
559
581
|
save_manifest_tar_path: Optional path to save manifest files tar.gz archive
|
|
560
582
|
base_paths: List of base paths for the scan (optional)
|
|
583
|
+
explicit_files: Optional list of explicit files to use instead of discovering files
|
|
561
584
|
|
|
562
585
|
Returns:
|
|
563
586
|
Dict with full scan data including html_report_url
|
|
@@ -571,11 +594,15 @@ class Core:
|
|
|
571
594
|
if no_change:
|
|
572
595
|
return diff
|
|
573
596
|
|
|
574
|
-
#
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
files
|
|
578
|
-
|
|
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)
|
|
579
606
|
|
|
580
607
|
# Save submitted files list if requested
|
|
581
608
|
if save_files_list_path and all_files:
|
|
@@ -632,54 +659,6 @@ class Core:
|
|
|
632
659
|
# Return result in the format expected by the user
|
|
633
660
|
return diff
|
|
634
661
|
|
|
635
|
-
def check_full_scans_status(self, head_full_scan_id: str, new_full_scan_id: str) -> bool:
|
|
636
|
-
is_ready = False
|
|
637
|
-
current_timeout = self.config.timeout
|
|
638
|
-
self.sdk.set_timeout(0.5)
|
|
639
|
-
try:
|
|
640
|
-
self.sdk.fullscans.stream(self.config.org_slug, head_full_scan_id)
|
|
641
|
-
except Exception:
|
|
642
|
-
log.debug(f"Queued up full scan for processing ({head_full_scan_id})")
|
|
643
|
-
|
|
644
|
-
try:
|
|
645
|
-
self.sdk.fullscans.stream(self.config.org_slug, new_full_scan_id)
|
|
646
|
-
except Exception:
|
|
647
|
-
log.debug(f"Queued up full scan for processing ({new_full_scan_id})")
|
|
648
|
-
self.sdk.set_timeout(current_timeout)
|
|
649
|
-
start_check = time.time()
|
|
650
|
-
head_is_ready = False
|
|
651
|
-
new_is_ready = False
|
|
652
|
-
while not is_ready:
|
|
653
|
-
head_full_scan_metadata = self.sdk.fullscans.metadata(self.config.org_slug, head_full_scan_id)
|
|
654
|
-
if head_full_scan_metadata:
|
|
655
|
-
head_state = head_full_scan_metadata.get("scan_state")
|
|
656
|
-
else:
|
|
657
|
-
head_state = None
|
|
658
|
-
new_full_scan_metadata = self.sdk.fullscans.metadata(self.config.org_slug, new_full_scan_id)
|
|
659
|
-
if new_full_scan_metadata:
|
|
660
|
-
new_state = new_full_scan_metadata.get("scan_state")
|
|
661
|
-
else:
|
|
662
|
-
new_state = None
|
|
663
|
-
if head_state and head_state == "resolve":
|
|
664
|
-
head_is_ready = True
|
|
665
|
-
if new_state and new_state == "resolve":
|
|
666
|
-
new_is_ready = True
|
|
667
|
-
if head_is_ready and new_is_ready:
|
|
668
|
-
is_ready = True
|
|
669
|
-
current_time = time.time()
|
|
670
|
-
if current_time - start_check >= self.config.timeout:
|
|
671
|
-
log.debug(
|
|
672
|
-
f"Timeout reached while waiting for full scans to be ready "
|
|
673
|
-
f"({head_full_scan_id}, {new_full_scan_id})"
|
|
674
|
-
)
|
|
675
|
-
break
|
|
676
|
-
total_time = time.time() - start_check
|
|
677
|
-
if is_ready:
|
|
678
|
-
log.info(f"Full scans are ready in {total_time:.2f} seconds")
|
|
679
|
-
else:
|
|
680
|
-
log.warning(f"Full scans are not ready yet ({head_full_scan_id}, {new_full_scan_id})")
|
|
681
|
-
return is_ready
|
|
682
|
-
|
|
683
662
|
def get_full_scan(self, full_scan_id: str) -> FullScan:
|
|
684
663
|
"""
|
|
685
664
|
Get a FullScan object for an existing full scan including sbom_artifacts and packages.
|
|
@@ -819,28 +798,54 @@ class Core:
|
|
|
819
798
|
pkg.url += f"/{pkg.name}/overview/{pkg.version}"
|
|
820
799
|
return pkg
|
|
821
800
|
|
|
822
|
-
def get_license_text_via_purl(self, packages: dict[str, Package]) -> dict:
|
|
823
|
-
|
|
801
|
+
def get_license_text_via_purl(self, packages: dict[str, Package], batch_size: int = 5000) -> dict:
|
|
802
|
+
"""Get license attribution and details via PURL endpoint in batches.
|
|
803
|
+
|
|
804
|
+
Args:
|
|
805
|
+
packages: Dictionary of packages to get license info for
|
|
806
|
+
batch_size: Maximum number of packages to process per API call (1-9999)
|
|
807
|
+
|
|
808
|
+
Returns:
|
|
809
|
+
Updated packages dictionary with licenseAttrib and licenseDetails populated
|
|
810
|
+
"""
|
|
811
|
+
# Validate batch size
|
|
812
|
+
batch_size = max(1, min(9999, batch_size))
|
|
813
|
+
|
|
814
|
+
# Build list of all components
|
|
815
|
+
all_components = []
|
|
824
816
|
for purl in packages:
|
|
825
817
|
full_purl = f"pkg:/{purl}"
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
)
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
818
|
+
all_components.append({"purl": full_purl})
|
|
819
|
+
|
|
820
|
+
# Process in batches
|
|
821
|
+
total_components = len(all_components)
|
|
822
|
+
log.debug(f"Processing {total_components} packages in batches of {batch_size}")
|
|
823
|
+
|
|
824
|
+
for i in range(0, total_components, batch_size):
|
|
825
|
+
batch_components = all_components[i:i + batch_size]
|
|
826
|
+
batch_num = (i // batch_size) + 1
|
|
827
|
+
total_batches = (total_components + batch_size - 1) // batch_size
|
|
828
|
+
log.debug(f"Processing batch {batch_num}/{total_batches} ({len(batch_components)} packages)")
|
|
829
|
+
|
|
830
|
+
results = self.sdk.purl.post(
|
|
831
|
+
license=True,
|
|
832
|
+
components=batch_components,
|
|
833
|
+
licenseattrib=True,
|
|
834
|
+
licensedetails=True
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
purl_packages = []
|
|
838
|
+
for result in results:
|
|
839
|
+
ecosystem = result["type"]
|
|
840
|
+
name = result["name"]
|
|
841
|
+
package_version = result["version"]
|
|
842
|
+
licenseDetails = result.get("licenseDetails")
|
|
843
|
+
licenseAttrib = result.get("licenseAttrib")
|
|
844
|
+
purl = f"{ecosystem}/{name}@{package_version}"
|
|
845
|
+
if purl not in purl_packages and purl in packages:
|
|
846
|
+
packages[purl].licenseAttrib = licenseAttrib
|
|
847
|
+
packages[purl].licenseDetails = licenseDetails
|
|
848
|
+
|
|
844
849
|
return packages
|
|
845
850
|
|
|
846
851
|
def get_added_and_removed_packages(
|
|
@@ -933,7 +938,14 @@ class Core:
|
|
|
933
938
|
log.error(f"Artifact details - name: {artifact.name}, version: {artifact.version}")
|
|
934
939
|
log.error("No matching packages found in head_full_scan")
|
|
935
940
|
|
|
936
|
-
|
|
941
|
+
# Only fetch license details if generate_license is enabled
|
|
942
|
+
if self.cli_config and self.cli_config.generate_license:
|
|
943
|
+
log.debug("Fetching license details via PURL endpoint")
|
|
944
|
+
batch_size = self.cli_config.max_purl_batch_size if self.cli_config else 5000
|
|
945
|
+
packages = self.get_license_text_via_purl(packages, batch_size=batch_size)
|
|
946
|
+
else:
|
|
947
|
+
log.debug("Skipping PURL endpoint call (--generate-license not set)")
|
|
948
|
+
|
|
937
949
|
return added_packages, removed_packages, packages
|
|
938
950
|
|
|
939
951
|
def create_new_diff(
|
|
@@ -943,7 +955,8 @@ class Core:
|
|
|
943
955
|
no_change: bool = False,
|
|
944
956
|
save_files_list_path: Optional[str] = None,
|
|
945
957
|
save_manifest_tar_path: Optional[str] = None,
|
|
946
|
-
base_paths: Optional[List[str]] = None
|
|
958
|
+
base_paths: Optional[List[str]] = None,
|
|
959
|
+
explicit_files: Optional[List[str]] = None
|
|
947
960
|
) -> Diff:
|
|
948
961
|
"""Create a new diff using the Socket SDK.
|
|
949
962
|
|
|
@@ -954,16 +967,21 @@ class Core:
|
|
|
954
967
|
save_files_list_path: Optional path to save submitted files list for debugging
|
|
955
968
|
save_manifest_tar_path: Optional path to save manifest files tar.gz archive
|
|
956
969
|
base_paths: List of base paths for the scan (optional)
|
|
970
|
+
explicit_files: Optional list of explicit files to use instead of discovering files
|
|
957
971
|
"""
|
|
958
972
|
log.debug(f"starting create_new_diff with no_change: {no_change}")
|
|
959
973
|
if no_change:
|
|
960
974
|
return Diff(id="NO_DIFF_RAN", diff_url="", report_url="")
|
|
961
975
|
|
|
962
|
-
#
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
files
|
|
966
|
-
|
|
976
|
+
# Use explicit files if provided, otherwise find manifest files from all paths
|
|
977
|
+
if explicit_files is not None:
|
|
978
|
+
all_files = explicit_files
|
|
979
|
+
log.debug(f"Using {len(all_files)} explicit files instead of discovering files")
|
|
980
|
+
else:
|
|
981
|
+
all_files = []
|
|
982
|
+
for path in paths:
|
|
983
|
+
files = self.find_files(path)
|
|
984
|
+
all_files.extend(files)
|
|
967
985
|
|
|
968
986
|
# Save submitted files list if requested
|
|
969
987
|
if save_files_list_path and all_files:
|
|
@@ -1059,9 +1077,6 @@ class Core:
|
|
|
1059
1077
|
log.warning(f"Failed to clean up temporary file {temp_file}: {e}")
|
|
1060
1078
|
|
|
1061
1079
|
# Handle diff generation - now we always have both scans
|
|
1062
|
-
scans_ready = self.check_full_scans_status(head_full_scan_id, new_full_scan.id)
|
|
1063
|
-
if scans_ready is False:
|
|
1064
|
-
log.error(f"Full scans did not complete within {self.config.timeout} seconds")
|
|
1065
1080
|
(
|
|
1066
1081
|
added_packages,
|
|
1067
1082
|
removed_packages,
|
|
@@ -101,10 +101,11 @@ class ReachabilityAnalyzer:
|
|
|
101
101
|
additional_params: Optional[List[str]] = None,
|
|
102
102
|
allow_unverified: bool = False,
|
|
103
103
|
enable_debug: bool = False,
|
|
104
|
+
use_only_pregenerated_sboms: bool = False,
|
|
104
105
|
) -> Dict[str, Any]:
|
|
105
106
|
"""
|
|
106
107
|
Run reachability analysis.
|
|
107
|
-
|
|
108
|
+
|
|
108
109
|
Args:
|
|
109
110
|
org_slug: Socket organization slug
|
|
110
111
|
target_directory: Directory to analyze
|
|
@@ -125,7 +126,8 @@ class ReachabilityAnalyzer:
|
|
|
125
126
|
additional_params: Additional parameters to pass to coana CLI
|
|
126
127
|
allow_unverified: Disable SSL certificate verification (sets NODE_TLS_REJECT_UNAUTHORIZED=0)
|
|
127
128
|
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
|
+
|
|
129
131
|
Returns:
|
|
130
132
|
Dict containing scan_id and report_path
|
|
131
133
|
"""
|
|
@@ -179,7 +181,10 @@ class ReachabilityAnalyzer:
|
|
|
179
181
|
|
|
180
182
|
if enable_debug:
|
|
181
183
|
cmd.append("-d")
|
|
182
|
-
|
|
184
|
+
|
|
185
|
+
if use_only_pregenerated_sboms:
|
|
186
|
+
cmd.append("--use-only-pregenerated-sboms")
|
|
187
|
+
|
|
183
188
|
# Add any additional parameters provided by the user
|
|
184
189
|
if additional_params:
|
|
185
190
|
cmd.extend(additional_params)
|
|
@@ -167,6 +167,8 @@ def main_code():
|
|
|
167
167
|
|
|
168
168
|
# Variable to track if we need to override files with facts file
|
|
169
169
|
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
|
|
170
172
|
|
|
171
173
|
# Git setup
|
|
172
174
|
is_repo = False
|
|
@@ -230,12 +232,14 @@ def main_code():
|
|
|
230
232
|
# Run reachability analysis if enabled
|
|
231
233
|
if config.reach:
|
|
232
234
|
from socketsecurity.core.tools.reachability import ReachabilityAnalyzer
|
|
233
|
-
|
|
235
|
+
|
|
234
236
|
log.info("Starting reachability analysis...")
|
|
235
|
-
|
|
237
|
+
|
|
236
238
|
# Find manifest files in scan paths (excluding .socket.facts.json to avoid circular dependency)
|
|
237
239
|
log.info("Finding manifest files for reachability analysis...")
|
|
238
240
|
manifest_files = []
|
|
241
|
+
|
|
242
|
+
# Always find all manifest files for the tar hash upload
|
|
239
243
|
for scan_path in scan_paths:
|
|
240
244
|
scan_manifests = core.find_files(scan_path)
|
|
241
245
|
# Filter out .socket.facts.json files from manifest upload
|
|
@@ -289,7 +293,8 @@ def main_code():
|
|
|
289
293
|
concurrency=config.reach_concurrency,
|
|
290
294
|
additional_params=config.reach_additional_params,
|
|
291
295
|
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
|
|
293
298
|
)
|
|
294
299
|
|
|
295
300
|
log.info(f"Reachability analysis completed successfully")
|
|
@@ -301,6 +306,17 @@ def main_code():
|
|
|
301
306
|
if config.only_facts_file:
|
|
302
307
|
facts_file_to_submit = os.path.abspath(output_path)
|
|
303
308
|
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)")
|
|
304
320
|
|
|
305
321
|
except Exception as e:
|
|
306
322
|
log.error(f"Reachability analysis failed: {str(e)}")
|
|
@@ -331,9 +347,16 @@ def main_code():
|
|
|
331
347
|
files_explicitly_specified = True
|
|
332
348
|
log.debug(f"Overriding files to only submit facts file: {facts_file_to_submit}")
|
|
333
349
|
|
|
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
|
+
|
|
334
356
|
# Determine files to check based on the new logic
|
|
335
357
|
files_to_check = []
|
|
336
358
|
force_api_mode = False
|
|
359
|
+
force_diff_mode = False
|
|
337
360
|
|
|
338
361
|
if files_explicitly_specified:
|
|
339
362
|
# Case 2: Files are specified - use them and don't check commit details
|
|
@@ -343,10 +366,21 @@ def main_code():
|
|
|
343
366
|
# Case 1: Files not specified and --ignore-commit-files not set - try to find changed files from commit
|
|
344
367
|
files_to_check = git_repo.changed_files
|
|
345
368
|
log.debug(f"Using changed files from commit: {files_to_check}")
|
|
369
|
+
elif config.ignore_commit_files and is_repo:
|
|
370
|
+
# Case 3: Git repo with --ignore-commit-files - force diff mode
|
|
371
|
+
files_to_check = []
|
|
372
|
+
force_diff_mode = True
|
|
373
|
+
log.debug("Git repo with --ignore-commit-files: forcing diff mode")
|
|
346
374
|
else:
|
|
347
|
-
#
|
|
375
|
+
# Case 4: Not a git repo (ignore_commit_files was auto-set to True)
|
|
348
376
|
files_to_check = []
|
|
349
|
-
|
|
377
|
+
# If --enable-diff is set, force diff mode for non-git repos
|
|
378
|
+
log.debug(f"Case 4: Non-git repo - config.enable_diff={config.enable_diff}, type={type(config.enable_diff)}")
|
|
379
|
+
if config.enable_diff:
|
|
380
|
+
force_diff_mode = True
|
|
381
|
+
log.debug("Non-git repo with --enable-diff: forcing diff mode")
|
|
382
|
+
else:
|
|
383
|
+
log.debug("Non-git repo without --enable-diff: will use full scan mode")
|
|
350
384
|
|
|
351
385
|
# Check if we have supported manifest files
|
|
352
386
|
has_supported_files = files_to_check and core.has_manifest_files(files_to_check)
|
|
@@ -367,22 +401,21 @@ def main_code():
|
|
|
367
401
|
has_supported_files = False
|
|
368
402
|
|
|
369
403
|
# Case 3: If no supported files or files are empty, force API mode (no PR comments)
|
|
370
|
-
if
|
|
404
|
+
# BUT: Don't force API mode if we're in force_diff_mode
|
|
405
|
+
log.debug(f"files_to_check={files_to_check}, has_supported_files={has_supported_files}, force_diff_mode={force_diff_mode}, config.enable_diff={config.enable_diff}")
|
|
406
|
+
if not has_supported_files and not force_diff_mode:
|
|
371
407
|
force_api_mode = True
|
|
372
408
|
log.debug("No supported manifest files found, forcing API mode")
|
|
409
|
+
log.debug(f"force_api_mode={force_api_mode}")
|
|
373
410
|
|
|
374
411
|
# Determine scan behavior
|
|
375
412
|
should_skip_scan = False # Always perform scan, but behavior changes based on supported files
|
|
376
|
-
if
|
|
377
|
-
#
|
|
378
|
-
should_skip_scan = False
|
|
379
|
-
log.debug("Forcing full scan due to ignore_commit_files")
|
|
380
|
-
elif not has_supported_files:
|
|
381
|
-
# No supported files - still scan but in API mode
|
|
413
|
+
if not has_supported_files and not force_diff_mode:
|
|
414
|
+
# No supported files and not forcing diff - still scan but in API mode
|
|
382
415
|
should_skip_scan = False
|
|
383
416
|
log.debug("No supported files but will scan in API mode")
|
|
384
417
|
else:
|
|
385
|
-
log.debug("Found supported manifest files, proceeding with normal scan")
|
|
418
|
+
log.debug("Found supported manifest files or forcing diff mode, proceeding with normal scan")
|
|
386
419
|
|
|
387
420
|
org_slug = core.config.org_slug
|
|
388
421
|
if config.repo_is_public:
|
|
@@ -435,6 +468,7 @@ def main_code():
|
|
|
435
468
|
diff.report_url = ""
|
|
436
469
|
|
|
437
470
|
# Handle SCM-specific flows
|
|
471
|
+
log.debug(f"Flow decision: scm={scm is not None}, force_diff_mode={force_diff_mode}, force_api_mode={force_api_mode}, enable_diff={config.enable_diff}")
|
|
438
472
|
if scm is not None and scm.check_event_type() == "comment":
|
|
439
473
|
# FIXME: This entire flow should be a separate command called "filter_ignored_alerts_in_comments"
|
|
440
474
|
# It's not related to scanning or diff generation - it just:
|
|
@@ -452,7 +486,7 @@ def main_code():
|
|
|
452
486
|
log.info("Push initiated flow")
|
|
453
487
|
if scm.check_event_type() == "diff":
|
|
454
488
|
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)
|
|
489
|
+
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)
|
|
456
490
|
comments = scm.get_comments_for_pr()
|
|
457
491
|
log.debug("Removing comment alerts")
|
|
458
492
|
|
|
@@ -505,18 +539,19 @@ def main_code():
|
|
|
505
539
|
)
|
|
506
540
|
else:
|
|
507
541
|
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)
|
|
542
|
+
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)
|
|
509
543
|
|
|
510
544
|
output_handler.handle_output(diff)
|
|
511
|
-
|
|
512
|
-
elif config.enable_diff and not force_api_mode:
|
|
513
|
-
# New logic: --enable-diff
|
|
545
|
+
|
|
546
|
+
elif (config.enable_diff or force_diff_mode) and not force_api_mode:
|
|
547
|
+
# New logic: --enable-diff or force_diff_mode (from --ignore-commit-files in git repos) forces diff mode
|
|
514
548
|
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)
|
|
549
|
+
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)
|
|
516
550
|
output_handler.handle_output(diff)
|
|
517
551
|
|
|
518
|
-
elif config.enable_diff and force_api_mode:
|
|
519
|
-
# User requested diff mode but no manifest files were detected
|
|
552
|
+
elif (config.enable_diff or force_diff_mode) and force_api_mode:
|
|
553
|
+
# User requested diff mode but no manifest files were detected - this should not happen with new logic
|
|
554
|
+
# but keeping as a safety net
|
|
520
555
|
log.warning("--enable-diff was specified but no supported manifest files were detected in the changed files. Falling back to full scan mode.")
|
|
521
556
|
log.info("Creating Socket Report (full scan)")
|
|
522
557
|
serializable_params = {
|
|
@@ -530,12 +565,13 @@ def main_code():
|
|
|
530
565
|
no_change=should_skip_scan,
|
|
531
566
|
save_files_list_path=config.save_submitted_files_list,
|
|
532
567
|
save_manifest_tar_path=config.save_manifest_tar,
|
|
533
|
-
base_paths=base_paths
|
|
568
|
+
base_paths=base_paths,
|
|
569
|
+
explicit_files=sbom_files_to_submit
|
|
534
570
|
)
|
|
535
571
|
log.info(f"Full scan created with ID: {diff.id}")
|
|
536
572
|
log.info(f"Full scan report URL: {diff.report_url}")
|
|
537
573
|
output_handler.handle_output(diff)
|
|
538
|
-
|
|
574
|
+
|
|
539
575
|
else:
|
|
540
576
|
if force_api_mode:
|
|
541
577
|
log.info("No Manifest files changed, creating Socket Report")
|
|
@@ -550,7 +586,8 @@ def main_code():
|
|
|
550
586
|
no_change=should_skip_scan,
|
|
551
587
|
save_files_list_path=config.save_submitted_files_list,
|
|
552
588
|
save_manifest_tar_path=config.save_manifest_tar,
|
|
553
|
-
base_paths=base_paths
|
|
589
|
+
base_paths=base_paths,
|
|
590
|
+
explicit_files=sbom_files_to_submit
|
|
554
591
|
)
|
|
555
592
|
log.info(f"Full scan created with ID: {diff.id}")
|
|
556
593
|
log.info(f"Full scan report URL: {diff.report_url}")
|
|
@@ -561,7 +598,8 @@ def main_code():
|
|
|
561
598
|
no_change=should_skip_scan,
|
|
562
599
|
save_files_list_path=config.save_submitted_files_list,
|
|
563
600
|
save_manifest_tar_path=config.save_manifest_tar,
|
|
564
|
-
base_paths=base_paths
|
|
601
|
+
base_paths=base_paths,
|
|
602
|
+
explicit_files=sbom_files_to_submit
|
|
565
603
|
)
|
|
566
604
|
output_handler.handle_output(diff)
|
|
567
605
|
|