scanoss 1.27.1__py3-none-any.whl → 1.43.1__py3-none-any.whl
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.
- protoc_gen_swagger/options/annotations_pb2.py +18 -12
- protoc_gen_swagger/options/annotations_pb2.pyi +48 -0
- protoc_gen_swagger/options/annotations_pb2_grpc.py +20 -0
- protoc_gen_swagger/options/openapiv2_pb2.py +110 -99
- protoc_gen_swagger/options/openapiv2_pb2.pyi +1317 -0
- protoc_gen_swagger/options/openapiv2_pb2_grpc.py +20 -0
- scanoss/__init__.py +1 -1
- scanoss/api/common/v2/scanoss_common_pb2.py +49 -22
- scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
- scanoss/api/components/v2/scanoss_components_pb2.py +68 -43
- scanoss/api/components/v2/scanoss_components_pb2_grpc.py +83 -22
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +136 -47
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +650 -33
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -37
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +64 -12
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +74 -31
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +252 -13
- scanoss/api/licenses/__init__.py +23 -0
- scanoss/api/licenses/v2/__init__.py +23 -0
- scanoss/api/licenses/v2/scanoss_licenses_pb2.py +84 -0
- scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +302 -0
- scanoss/api/scanning/v2/scanoss_scanning_pb2.py +32 -21
- scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +49 -8
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
- scanoss/cli.py +1000 -186
- scanoss/components.py +80 -50
- scanoss/constants.py +7 -1
- scanoss/cryptography.py +89 -55
- scanoss/csvoutput.py +13 -7
- scanoss/cyclonedx.py +141 -9
- scanoss/data/build_date.txt +1 -1
- scanoss/data/osadl-copyleft.json +133 -0
- scanoss/delta.py +197 -0
- scanoss/export/__init__.py +23 -0
- scanoss/export/dependency_track.py +227 -0
- scanoss/file_filters.py +2 -163
- scanoss/filecount.py +37 -38
- scanoss/gitlabqualityreport.py +214 -0
- scanoss/header_filter.py +563 -0
- scanoss/inspection/policy_check/__init__.py +0 -0
- scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
- scanoss/inspection/policy_check/dependency_track/project_violation.py +479 -0
- scanoss/inspection/{policy_check.py → policy_check/policy_check.py} +65 -72
- scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
- scanoss/inspection/{copyleft.py → policy_check/scanoss/copyleft.py} +89 -73
- scanoss/inspection/{undeclared_component.py → policy_check/scanoss/undeclared_component.py} +52 -46
- scanoss/inspection/summary/__init__.py +0 -0
- scanoss/inspection/summary/component_summary.py +170 -0
- scanoss/inspection/{license_summary.py → summary/license_summary.py} +62 -12
- scanoss/inspection/summary/match_summary.py +341 -0
- scanoss/inspection/utils/file_utils.py +44 -0
- scanoss/inspection/utils/license_utils.py +57 -71
- scanoss/inspection/utils/markdown_utils.py +63 -0
- scanoss/inspection/{inspect_base.py → utils/scan_result_processor.py} +53 -67
- scanoss/osadl.py +125 -0
- scanoss/scanner.py +135 -253
- scanoss/scanners/folder_hasher.py +47 -32
- scanoss/scanners/scanner_hfh.py +50 -18
- scanoss/scanoss_settings.py +33 -3
- scanoss/scanossapi.py +23 -25
- scanoss/scanossbase.py +1 -1
- scanoss/scanossgrpc.py +543 -289
- scanoss/services/dependency_track_service.py +132 -0
- scanoss/spdxlite.py +11 -4
- scanoss/threadeddependencies.py +19 -18
- scanoss/threadedscanning.py +10 -0
- scanoss/utils/scanoss_scan_results_utils.py +41 -0
- scanoss/winnowing.py +71 -19
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/METADATA +8 -5
- scanoss-1.43.1.dist-info/RECORD +110 -0
- scanoss/inspection/component_summary.py +0 -94
- scanoss-1.27.1.dist-info/RECORD +0 -87
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/WHEEL +0 -0
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/licenses/LICENSE +0 -0
- {scanoss-1.27.1.dist-info → scanoss-1.43.1.dist-info}/top_level.txt +0 -0
scanoss/cli.py
CHANGED
|
@@ -25,6 +25,7 @@ SPDX-License-Identifier: MIT
|
|
|
25
25
|
import argparse
|
|
26
26
|
import os
|
|
27
27
|
import sys
|
|
28
|
+
import traceback
|
|
28
29
|
from dataclasses import asdict
|
|
29
30
|
from pathlib import Path
|
|
30
31
|
from typing import List
|
|
@@ -32,8 +33,8 @@ from typing import List
|
|
|
32
33
|
import pypac
|
|
33
34
|
|
|
34
35
|
from scanoss.cryptography import Cryptography, create_cryptography_config_from_args
|
|
35
|
-
from scanoss.
|
|
36
|
-
from scanoss.
|
|
36
|
+
from scanoss.delta import Delta
|
|
37
|
+
from scanoss.export.dependency_track import DependencyTrackExporter
|
|
37
38
|
from scanoss.scanners.container_scanner import (
|
|
38
39
|
DEFAULT_SYFT_COMMAND,
|
|
39
40
|
DEFAULT_SYFT_TIMEOUT,
|
|
@@ -54,18 +55,30 @@ from . import __version__
|
|
|
54
55
|
from .components import Components
|
|
55
56
|
from .constants import (
|
|
56
57
|
DEFAULT_API_TIMEOUT,
|
|
58
|
+
DEFAULT_COPYLEFT_LICENSE_SOURCES,
|
|
59
|
+
DEFAULT_HFH_DEPTH,
|
|
60
|
+
DEFAULT_HFH_MIN_ACCEPTED_SCORE,
|
|
57
61
|
DEFAULT_HFH_RANK_THRESHOLD,
|
|
62
|
+
DEFAULT_HFH_RECURSIVE_THRESHOLD,
|
|
58
63
|
DEFAULT_POST_SIZE,
|
|
59
64
|
DEFAULT_RETRY,
|
|
60
65
|
DEFAULT_TIMEOUT,
|
|
61
66
|
MIN_TIMEOUT,
|
|
62
67
|
PYTHON_MAJOR_VERSION,
|
|
68
|
+
VALID_LICENSE_SOURCES,
|
|
63
69
|
)
|
|
64
70
|
from .csvoutput import CsvOutput
|
|
65
71
|
from .cyclonedx import CycloneDx
|
|
66
72
|
from .filecount import FileCount
|
|
67
|
-
from .
|
|
68
|
-
from .inspection.
|
|
73
|
+
from .gitlabqualityreport import GitLabQualityReport
|
|
74
|
+
from .inspection.policy_check.dependency_track.project_violation import (
|
|
75
|
+
DependencyTrackProjectViolationPolicyCheck,
|
|
76
|
+
)
|
|
77
|
+
from .inspection.policy_check.scanoss.copyleft import Copyleft
|
|
78
|
+
from .inspection.policy_check.scanoss.undeclared_component import UndeclaredComponent
|
|
79
|
+
from .inspection.summary.component_summary import ComponentSummary
|
|
80
|
+
from .inspection.summary.license_summary import LicenseSummary
|
|
81
|
+
from .inspection.summary.match_summary import MatchSummary
|
|
69
82
|
from .results import Results
|
|
70
83
|
from .scancodedeps import ScancodeDeps
|
|
71
84
|
from .scanner import FAST_WINNOWING, Scanner
|
|
@@ -157,7 +170,6 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
157
170
|
default=DEFAULT_RETRY,
|
|
158
171
|
help='Retry limit for API communication (optional - default 5)',
|
|
159
172
|
)
|
|
160
|
-
p_scan.add_argument('--no-wfp-output', action='store_true', help='Skip WFP file generation')
|
|
161
173
|
p_scan.add_argument('--dependencies', '-D', action='store_true', help='Add Dependency scanning')
|
|
162
174
|
p_scan.add_argument('--dependencies-only', action='store_true', help='Run Dependency scanning only')
|
|
163
175
|
p_scan.add_argument(
|
|
@@ -174,6 +186,10 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
174
186
|
)
|
|
175
187
|
p_scan.add_argument('--dep-scope-inc', '-dsi', type=str, help='Include dependencies with declared scopes')
|
|
176
188
|
p_scan.add_argument('--dep-scope-exc', '-dse', type=str, help='Exclude dependencies with declared scopes')
|
|
189
|
+
p_scan.add_argument(
|
|
190
|
+
'--no-wfp-output', action='store_true',
|
|
191
|
+
help='DEPRECATED: Scans no longer generate scanner_output.wfp. Use "fingerprint -o" to create WFP files.'
|
|
192
|
+
)
|
|
177
193
|
|
|
178
194
|
# Sub-command: fingerprint
|
|
179
195
|
p_wfp = subparsers.add_parser(
|
|
@@ -274,7 +290,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
274
290
|
'--format',
|
|
275
291
|
'-f',
|
|
276
292
|
type=str,
|
|
277
|
-
choices=['cyclonedx', 'spdxlite', 'csv'],
|
|
293
|
+
choices=['cyclonedx', 'spdxlite', 'csv', 'glc-codequality'],
|
|
278
294
|
default='spdxlite',
|
|
279
295
|
help='Output format (optional - default: spdxlite)',
|
|
280
296
|
)
|
|
@@ -306,6 +322,15 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
306
322
|
)
|
|
307
323
|
c_vulns.set_defaults(func=comp_vulns)
|
|
308
324
|
|
|
325
|
+
# Component Sub-command: component licenses
|
|
326
|
+
c_licenses = comp_sub.add_parser(
|
|
327
|
+
'licenses',
|
|
328
|
+
aliases=['lics'],
|
|
329
|
+
description=f'Show License details: {__version__}',
|
|
330
|
+
help='Retrieve licenses for the given components',
|
|
331
|
+
)
|
|
332
|
+
c_licenses.set_defaults(func=comp_licenses)
|
|
333
|
+
|
|
309
334
|
# Component Sub-command: component semgrep
|
|
310
335
|
c_semgrep = comp_sub.add_parser(
|
|
311
336
|
'semgrep',
|
|
@@ -407,7 +432,15 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
407
432
|
p_crypto_versions_in_range.set_defaults(func=crypto_versions_in_range)
|
|
408
433
|
|
|
409
434
|
# Common purl Component sub-command options
|
|
410
|
-
for p in [
|
|
435
|
+
for p in [
|
|
436
|
+
c_vulns,
|
|
437
|
+
c_semgrep,
|
|
438
|
+
c_provenance,
|
|
439
|
+
p_crypto_algorithms,
|
|
440
|
+
p_crypto_hints,
|
|
441
|
+
p_crypto_versions_in_range,
|
|
442
|
+
c_licenses,
|
|
443
|
+
]:
|
|
411
444
|
p.add_argument('--purl', '-p', type=str, nargs='*', help='Package URL - PURL to process.')
|
|
412
445
|
p.add_argument('--input', '-i', type=str, help='Input file name')
|
|
413
446
|
|
|
@@ -421,6 +454,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
421
454
|
p_crypto_algorithms,
|
|
422
455
|
p_crypto_hints,
|
|
423
456
|
p_crypto_versions_in_range,
|
|
457
|
+
c_licenses,
|
|
424
458
|
]:
|
|
425
459
|
p.add_argument(
|
|
426
460
|
'--timeout',
|
|
@@ -534,76 +568,355 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
534
568
|
)
|
|
535
569
|
p_results.set_defaults(func=results)
|
|
536
570
|
|
|
537
|
-
|
|
538
|
-
#
|
|
571
|
+
# =========================================================================
|
|
572
|
+
# INSPECT SUBCOMMAND - Analysis and validation of scan results
|
|
573
|
+
# =========================================================================
|
|
574
|
+
|
|
575
|
+
# Main inspect parser - provides tools for analyzing scan results
|
|
539
576
|
p_inspect = subparsers.add_parser(
|
|
540
|
-
'inspect',
|
|
577
|
+
'inspect',
|
|
578
|
+
aliases=['insp', 'ins'],
|
|
579
|
+
description=f'Inspect and analyse scan results: {__version__}',
|
|
580
|
+
help='Inspect and analyse scan results',
|
|
541
581
|
)
|
|
542
|
-
|
|
582
|
+
|
|
583
|
+
# Inspect sub-commands parser
|
|
543
584
|
p_inspect_sub = p_inspect.add_subparsers(
|
|
544
|
-
title='Inspect Commands',
|
|
585
|
+
title='Inspect Commands',
|
|
586
|
+
dest='subparsercmd',
|
|
587
|
+
description='Available inspection sub-commands',
|
|
588
|
+
help='Choose an inspection type',
|
|
545
589
|
)
|
|
546
590
|
|
|
547
|
-
|
|
548
|
-
#
|
|
549
|
-
|
|
550
|
-
|
|
591
|
+
# -------------------------------------------------------------------------
|
|
592
|
+
# RAW RESULTS INSPECTION - Analyse raw scan output
|
|
593
|
+
# -------------------------------------------------------------------------
|
|
594
|
+
|
|
595
|
+
# Raw results parser - handles inspection of unprocessed scan results
|
|
596
|
+
p_inspect_raw = p_inspect_sub.add_parser(
|
|
597
|
+
'raw',
|
|
598
|
+
description='Inspect and analyse SCANOSS raw scan results',
|
|
599
|
+
help='Analyse raw scan results for various compliance issues',
|
|
551
600
|
)
|
|
552
601
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
602
|
+
# Raw results sub-commands parser
|
|
603
|
+
p_inspect_raw_sub = p_inspect_raw.add_subparsers(
|
|
604
|
+
title='Raw Results Inspection Commands',
|
|
605
|
+
dest='subparser_subcmd',
|
|
606
|
+
description='Tools for analyzing raw scan results',
|
|
607
|
+
help='Choose a raw results analysis type',
|
|
558
608
|
)
|
|
559
609
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
610
|
+
# Copyleft license inspection - identifies copyleft license violations
|
|
611
|
+
p_inspect_raw_copyleft = p_inspect_raw_sub.add_parser(
|
|
612
|
+
'copyleft',
|
|
613
|
+
aliases=['cp'],
|
|
614
|
+
description='Identify components with copyleft licenses that may require compliance action',
|
|
615
|
+
help='Find copyleft license violations',
|
|
563
616
|
)
|
|
564
617
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
618
|
+
# License summary inspection - provides overview of all detected licenses
|
|
619
|
+
p_inspect_raw_license_summary = p_inspect_raw_sub.add_parser(
|
|
620
|
+
'license-summary',
|
|
621
|
+
aliases=['lic-summary', 'licsum'],
|
|
622
|
+
description='Generate comprehensive summary of all licenses found in scan results',
|
|
623
|
+
help='Generate license summary report',
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
# Component summary inspection - provides overview of all detected components
|
|
627
|
+
p_inspect_raw_component_summary = p_inspect_raw_sub.add_parser(
|
|
628
|
+
'component-summary',
|
|
629
|
+
aliases=['comp-summary', 'compsum'],
|
|
630
|
+
description='Generate comprehensive summary of all components found in scan results',
|
|
631
|
+
help='Generate component summary report',
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
# Undeclared components inspection - finds components not declared in SBOM
|
|
635
|
+
p_inspect_raw_undeclared = p_inspect_raw_sub.add_parser(
|
|
568
636
|
'undeclared',
|
|
569
637
|
aliases=['un'],
|
|
570
|
-
description='
|
|
571
|
-
help='
|
|
638
|
+
description='Identify components present in code but not declared in SBOM files',
|
|
639
|
+
help='Find undeclared components',
|
|
572
640
|
)
|
|
573
|
-
|
|
641
|
+
# SBOM format option for undeclared components inspection
|
|
642
|
+
p_inspect_raw_undeclared.add_argument(
|
|
574
643
|
'--sbom-format',
|
|
575
644
|
required=False,
|
|
576
645
|
choices=['legacy', 'settings'],
|
|
577
646
|
default='settings',
|
|
578
|
-
help='
|
|
647
|
+
help='SBOM format type for comparison: legacy or settings (default)',
|
|
579
648
|
)
|
|
580
649
|
|
|
581
|
-
#
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
650
|
+
# -------------------------------------------------------------------------
|
|
651
|
+
# BACKWARD COMPATIBILITY - Support old inspect command format
|
|
652
|
+
# -------------------------------------------------------------------------
|
|
653
|
+
|
|
654
|
+
# Legacy copyleft inspection - backward compatibility for 'scanoss-py inspect copyleft'
|
|
655
|
+
p_inspect_legacy_copyleft = p_inspect_sub.add_parser(
|
|
656
|
+
'copyleft',
|
|
657
|
+
aliases=['cp'],
|
|
658
|
+
description='Identify components with copyleft licenses that may require compliance action',
|
|
659
|
+
help='Find copyleft license violations (legacy format)',
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
# Legacy undeclared components inspection - backward compatibility for 'scanoss-py inspect undeclared'
|
|
663
|
+
p_inspect_legacy_undeclared = p_inspect_sub.add_parser(
|
|
664
|
+
'undeclared',
|
|
665
|
+
aliases=['un'],
|
|
666
|
+
description='Identify components present in code but not declared in SBOM files',
|
|
667
|
+
help='Find undeclared components (legacy format)',
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
# SBOM format option for legacy undeclared components inspection
|
|
671
|
+
p_inspect_legacy_undeclared.add_argument(
|
|
672
|
+
'--sbom-format',
|
|
673
|
+
required=False,
|
|
674
|
+
choices=['legacy', 'settings'],
|
|
675
|
+
default='settings',
|
|
676
|
+
help='SBOM format type for comparison: legacy or settings (default)',
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# Legacy license summary inspection - backward compatibility for 'scanoss-py inspect license-summary'
|
|
680
|
+
p_inspect_legacy_license_summary = p_inspect_sub.add_parser(
|
|
681
|
+
'license-summary',
|
|
682
|
+
aliases=['lic-summary', 'licsum'],
|
|
683
|
+
description='Generate comprehensive summary of all licenses found in scan results',
|
|
684
|
+
help='Generate license summary report (legacy format)',
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
# Legacy component summary inspection - backward compatibility for 'scanoss-py inspect component-summary'
|
|
688
|
+
p_inspect_legacy_component_summary = p_inspect_sub.add_parser(
|
|
689
|
+
'component-summary',
|
|
690
|
+
aliases=['comp-summary', 'compsum'],
|
|
691
|
+
description='Generate comprehensive summary of all components found in scan results',
|
|
692
|
+
help='Generate component summary report (legacy format)',
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
# Applies the same configuration to both legacy and raw versions
|
|
696
|
+
# License filtering options - common to (legacy) copyleft and license summary commands
|
|
697
|
+
for p in [
|
|
698
|
+
p_inspect_raw_copyleft,
|
|
699
|
+
p_inspect_raw_license_summary,
|
|
700
|
+
p_inspect_legacy_copyleft,
|
|
701
|
+
p_inspect_legacy_license_summary,
|
|
702
|
+
]:
|
|
703
|
+
p.add_argument('--include', help='Additional licenses to include in analysis (comma-separated list)')
|
|
704
|
+
p.add_argument('--exclude', help='Licenses to exclude from analysis (comma-separated list)')
|
|
705
|
+
p.add_argument('--explicit', help='Use only these specific licenses for analysis (comma-separated list)')
|
|
706
|
+
|
|
707
|
+
# License source filtering
|
|
708
|
+
for p in [p_inspect_raw_copyleft, p_inspect_legacy_copyleft]:
|
|
587
709
|
p.add_argument(
|
|
588
|
-
'--
|
|
589
|
-
|
|
710
|
+
'-ls', '--license-sources',
|
|
711
|
+
action='extend',
|
|
712
|
+
nargs='+',
|
|
713
|
+
choices=VALID_LICENSE_SOURCES,
|
|
714
|
+
help=f'Specify which license sources to check for copyleft violations. Each license object in scan results '
|
|
715
|
+
f'has a source field indicating its origin. Default: {", ".join(DEFAULT_COPYLEFT_LICENSE_SOURCES)}',
|
|
590
716
|
)
|
|
717
|
+
|
|
718
|
+
# Common options for (legacy) copyleft and undeclared component inspection
|
|
719
|
+
for p in [p_inspect_raw_copyleft, p_inspect_raw_undeclared, p_inspect_legacy_copyleft, p_inspect_legacy_undeclared]:
|
|
720
|
+
p.add_argument('-i', '--input', nargs='?', help='Path to scan results file to analyse')
|
|
591
721
|
p.add_argument(
|
|
592
|
-
'
|
|
593
|
-
|
|
722
|
+
'-f',
|
|
723
|
+
'--format',
|
|
724
|
+
required=False,
|
|
725
|
+
choices=['json', 'md', 'jira_md'],
|
|
726
|
+
default='json',
|
|
727
|
+
help='Output format: json (default), md (Markdown), or jira_md (JIRA Markdown)',
|
|
594
728
|
)
|
|
729
|
+
p.add_argument('-o', '--output', type=str, help='Save detailed results to specified file')
|
|
730
|
+
p.add_argument('-s', '--status', type=str, help='Save summary status report to Markdown file')
|
|
595
731
|
|
|
596
|
-
|
|
597
|
-
for p in [
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
732
|
+
# Common options for (legacy) license and component summary commands
|
|
733
|
+
for p in [
|
|
734
|
+
p_inspect_raw_license_summary,
|
|
735
|
+
p_inspect_raw_component_summary,
|
|
736
|
+
p_inspect_legacy_license_summary,
|
|
737
|
+
p_inspect_legacy_component_summary,
|
|
738
|
+
]:
|
|
739
|
+
p.add_argument('-i', '--input', nargs='?', help='Path to scan results file to analyse')
|
|
740
|
+
p.add_argument('-o', '--output', type=str, help='Save summary report to specified file')
|
|
741
|
+
|
|
742
|
+
# -------------------------------------------------------------------------
|
|
743
|
+
# DEPENDENCY TRACK INSPECTION - Analyse Dependency Track project data
|
|
744
|
+
# -------------------------------------------------------------------------
|
|
745
|
+
|
|
746
|
+
# Dependency Track parser - handles inspection of DT project status and violations
|
|
747
|
+
p_dep_track_sub = p_inspect_sub.add_parser(
|
|
748
|
+
'dependency-track',
|
|
749
|
+
aliases=['dt'],
|
|
750
|
+
description='Inspect and analyse Dependency Track project status and policy violations',
|
|
751
|
+
help='Analyse Dependency Track projects',
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
# Dependency Track sub-commands parser
|
|
755
|
+
p_inspect_dep_track_sub = p_dep_track_sub.add_subparsers(
|
|
756
|
+
title='Dependency Track Inspection Commands',
|
|
757
|
+
dest='subparser_subcmd',
|
|
758
|
+
description='Tools for analysing Dependency Track project data',
|
|
759
|
+
help='Choose a Dependency Track analysis type',
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
# Project violations inspection - analyses policy violations in DT projects
|
|
763
|
+
p_inspect_dt_project_violation = p_inspect_dep_track_sub.add_parser(
|
|
764
|
+
'project-violations',
|
|
765
|
+
aliases=['pv'],
|
|
766
|
+
description='Analyse policy violations and compliance issues in Dependency Track projects',
|
|
767
|
+
help='Inspect project policy violations',
|
|
768
|
+
)
|
|
769
|
+
# Dependency Track connection and authentication options
|
|
770
|
+
p_inspect_dt_project_violation.add_argument(
|
|
771
|
+
'--url', required=True, type=str, help='Dependency Track server base URL (e.g., https://dtrack.example.com)'
|
|
772
|
+
)
|
|
773
|
+
p_inspect_dt_project_violation.add_argument(
|
|
774
|
+
'--upload-token',
|
|
775
|
+
'-ut',
|
|
776
|
+
required=False,
|
|
777
|
+
type=str,
|
|
778
|
+
help='Project-specific upload token for accessing DT project data',
|
|
779
|
+
)
|
|
780
|
+
p_inspect_dt_project_violation.add_argument(
|
|
781
|
+
'--project-id', '-pid', required=False, type=str, help='Dependency Track project UUID to inspect'
|
|
782
|
+
)
|
|
783
|
+
p_inspect_dt_project_violation.add_argument(
|
|
784
|
+
'--apikey', '-k', required=True, type=str, help='Dependency Track API key for authentication'
|
|
785
|
+
)
|
|
786
|
+
p_inspect_dt_project_violation.add_argument(
|
|
787
|
+
'--project-name', '-pn', required=False, type=str, help='Dependency Track project name'
|
|
788
|
+
)
|
|
789
|
+
p_inspect_dt_project_violation.add_argument(
|
|
790
|
+
'--project-version', '-pv', required=False, type=str, help='Dependency Track project version'
|
|
791
|
+
)
|
|
792
|
+
p_inspect_dt_project_violation.add_argument(
|
|
793
|
+
'--output', '-o', required=False, type=str, help='Save inspection results to specified file'
|
|
794
|
+
)
|
|
795
|
+
p_inspect_dt_project_violation.add_argument(
|
|
796
|
+
'--status', required=False, type=str, help='Save summary status report to specified file'
|
|
797
|
+
)
|
|
798
|
+
p_inspect_dt_project_violation.add_argument(
|
|
799
|
+
'--format',
|
|
800
|
+
'-f',
|
|
801
|
+
required=False,
|
|
802
|
+
choices=['json', 'md', 'jira_md'],
|
|
803
|
+
default='json',
|
|
804
|
+
help='Output format: json (default), md (Markdown) or jira_md (JIRA Markdown)',
|
|
805
|
+
)
|
|
806
|
+
p_inspect_dt_project_violation.add_argument(
|
|
807
|
+
'--timeout',
|
|
808
|
+
'-M',
|
|
809
|
+
required=False,
|
|
810
|
+
default=300,
|
|
811
|
+
type=float,
|
|
812
|
+
help='Timeout (in seconds) for API communication (optional - default 300 sec)',
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
# ==============================================================================
|
|
816
|
+
# GitLab Integration Parser
|
|
817
|
+
# ==============================================================================
|
|
818
|
+
# Main parser for GitLab-specific inspection commands and report generation
|
|
819
|
+
p_gitlab_sub = p_inspect_sub.add_parser(
|
|
820
|
+
'gitlab',
|
|
821
|
+
aliases=['glc'],
|
|
822
|
+
description='Generate GitLab-compatible reports from SCANOSS scan results (Markdown summaries)',
|
|
823
|
+
help='Generate GitLab integration reports',
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
# GitLab sub-commands parser
|
|
827
|
+
# Provides access to different GitLab report formats and inspection tools
|
|
828
|
+
p_gitlab_sub_parser = p_gitlab_sub.add_subparsers(
|
|
829
|
+
title='GitLab Report Types',
|
|
830
|
+
dest='subparser_subcmd',
|
|
831
|
+
description='Available GitLab report formats for scan result analysis',
|
|
832
|
+
help='Select the type of GitLab report to generate',
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
# ==============================================================================
|
|
836
|
+
# GitLab Matches Summary Command
|
|
837
|
+
# ==============================================================================
|
|
838
|
+
# Analyzes scan results and generates a GitLab-compatible Markdown summary
|
|
839
|
+
p_gl_inspect_matches = p_gitlab_sub_parser.add_parser(
|
|
840
|
+
'matches',
|
|
841
|
+
aliases=['ms'],
|
|
842
|
+
description='Generate a Markdown summary report of scan matches for GitLab integration',
|
|
843
|
+
help='Generate Markdown summary report of scan matches',
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
# Input file argument - SCANOSS scan results in JSON format
|
|
847
|
+
p_gl_inspect_matches.add_argument(
|
|
848
|
+
'-i', '--input', required=True, type=str, help='Path to SCANOSS scan results file (JSON format) to analyze'
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
# Line range prefix for GitLab file navigation
|
|
852
|
+
# Enables clickable file references in the generated report that link to specific lines in GitLab
|
|
853
|
+
p_gl_inspect_matches.add_argument(
|
|
854
|
+
'-lpr',
|
|
855
|
+
'--line-range-prefix',
|
|
856
|
+
required=True,
|
|
857
|
+
type=str,
|
|
858
|
+
help='Base URL prefix for GitLab file links with line ranges (e.g., https://gitlab.com/org/project/-/blob/main)',
|
|
859
|
+
)
|
|
605
860
|
|
|
606
|
-
|
|
861
|
+
# Output file argument - where to save the generated Markdown report
|
|
862
|
+
p_gl_inspect_matches.add_argument(
|
|
863
|
+
'--output',
|
|
864
|
+
'-o',
|
|
865
|
+
required=False,
|
|
866
|
+
type=str,
|
|
867
|
+
help='Output file path for the generated Markdown report (default: stdout)',
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
# TODO Move to the command call def location
|
|
871
|
+
# RAW results
|
|
872
|
+
p_inspect_raw_undeclared.set_defaults(func=inspect_undeclared)
|
|
873
|
+
p_inspect_raw_copyleft.set_defaults(func=inspect_copyleft)
|
|
874
|
+
p_inspect_raw_license_summary.set_defaults(func=inspect_license_summary)
|
|
875
|
+
p_inspect_raw_component_summary.set_defaults(func=inspect_component_summary)
|
|
876
|
+
# Legacy backward compatibility commands
|
|
877
|
+
p_inspect_legacy_copyleft.set_defaults(func=inspect_copyleft)
|
|
878
|
+
p_inspect_legacy_undeclared.set_defaults(func=inspect_undeclared)
|
|
879
|
+
p_inspect_legacy_license_summary.set_defaults(func=inspect_license_summary)
|
|
880
|
+
p_inspect_legacy_component_summary.set_defaults(func=inspect_component_summary)
|
|
881
|
+
# Dependency Track
|
|
882
|
+
p_inspect_dt_project_violation.set_defaults(func=inspect_dep_track_project_violations)
|
|
883
|
+
# GitLab
|
|
884
|
+
p_gl_inspect_matches.set_defaults(func=inspect_gitlab_matches)
|
|
885
|
+
|
|
886
|
+
# =========================================================================
|
|
887
|
+
# END INSPECT SUBCOMMAND CONFIGURATION
|
|
888
|
+
# =========================================================================
|
|
889
|
+
|
|
890
|
+
# Sub-command: export
|
|
891
|
+
p_export = subparsers.add_parser(
|
|
892
|
+
'export',
|
|
893
|
+
aliases=['exp'],
|
|
894
|
+
description=f'Export SBOM files to external platforms: {__version__}',
|
|
895
|
+
help='Export SBOM files to external platforms',
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
export_sub = p_export.add_subparsers(
|
|
899
|
+
title='Export Commands',
|
|
900
|
+
dest='subparsercmd',
|
|
901
|
+
description='export sub-commands',
|
|
902
|
+
help='export sub-commands',
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
# Export Sub-command: export dt (Dependency Track)
|
|
906
|
+
e_dt = export_sub.add_parser(
|
|
907
|
+
'dt',
|
|
908
|
+
aliases=['dependency-track'],
|
|
909
|
+
description='Export SBOM to Dependency Track',
|
|
910
|
+
help='Upload SBOM files to Dependency Track',
|
|
911
|
+
)
|
|
912
|
+
e_dt.add_argument('-i', '--input', type=str, required=True, help='Input SBOM file (CycloneDX JSON format)')
|
|
913
|
+
e_dt.add_argument('--url', type=str, required=True, help='Dependency Track base URL')
|
|
914
|
+
e_dt.add_argument('--apikey', '-k', type=str, required=True, help='Dependency Track API key')
|
|
915
|
+
e_dt.add_argument('--output', '-o', type=str, help='File to save export token and uuid into')
|
|
916
|
+
e_dt.add_argument('--project-id', '-pid', type=str, help='Dependency Track project UUID')
|
|
917
|
+
e_dt.add_argument('--project-name', '-pn', type=str, help='Dependency Track project name')
|
|
918
|
+
e_dt.add_argument('--project-version', '-pv', type=str, help='Dependency Track project version')
|
|
919
|
+
e_dt.set_defaults(func=export_dt)
|
|
607
920
|
|
|
608
921
|
# Sub-command: folder-scan
|
|
609
922
|
p_folder_scan = subparsers.add_parser(
|
|
@@ -635,6 +948,27 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
635
948
|
help='Filter results to only show those with rank value at or below this threshold (e.g., --rank-threshold 3 '
|
|
636
949
|
'returns results with rank 1, 2, or 3). Lower rank values indicate higher quality matches.',
|
|
637
950
|
)
|
|
951
|
+
p_folder_scan.add_argument(
|
|
952
|
+
'--depth',
|
|
953
|
+
type=int,
|
|
954
|
+
default=DEFAULT_HFH_DEPTH,
|
|
955
|
+
help=f'Defines how deep to scan the root directory (optional - default {DEFAULT_HFH_DEPTH})',
|
|
956
|
+
)
|
|
957
|
+
p_folder_scan.add_argument(
|
|
958
|
+
'--recursive-threshold',
|
|
959
|
+
type=float,
|
|
960
|
+
default=DEFAULT_HFH_RECURSIVE_THRESHOLD,
|
|
961
|
+
help=f'Minimum score threshold to consider a match (optional - default: {DEFAULT_HFH_RECURSIVE_THRESHOLD})',
|
|
962
|
+
)
|
|
963
|
+
p_folder_scan.add_argument(
|
|
964
|
+
'--min-accepted-score',
|
|
965
|
+
type=float,
|
|
966
|
+
default=DEFAULT_HFH_MIN_ACCEPTED_SCORE,
|
|
967
|
+
help=(
|
|
968
|
+
'Only show results with a score at or above this threshold '
|
|
969
|
+
f'(optional - default: {DEFAULT_HFH_MIN_ACCEPTED_SCORE})'
|
|
970
|
+
),
|
|
971
|
+
)
|
|
638
972
|
p_folder_scan.set_defaults(func=folder_hashing_scan)
|
|
639
973
|
|
|
640
974
|
# Sub-command: folder-hash
|
|
@@ -653,8 +987,38 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
653
987
|
default='json',
|
|
654
988
|
help='Result output format (optional - default: json)',
|
|
655
989
|
)
|
|
990
|
+
p_folder_hash.add_argument(
|
|
991
|
+
'--depth',
|
|
992
|
+
type=int,
|
|
993
|
+
default=DEFAULT_HFH_DEPTH,
|
|
994
|
+
help=f'Defines how deep to hash the root directory (optional - default {DEFAULT_HFH_DEPTH})',
|
|
995
|
+
)
|
|
656
996
|
p_folder_hash.set_defaults(func=folder_hash)
|
|
657
997
|
|
|
998
|
+
# Sub-command: delta
|
|
999
|
+
p_delta = subparsers.add_parser(
|
|
1000
|
+
'delta',
|
|
1001
|
+
aliases=['dl'],
|
|
1002
|
+
description=f'SCANOSS Delta commands: {__version__}',
|
|
1003
|
+
help='Delta support commands',
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
delta_sub = p_delta.add_subparsers(
|
|
1007
|
+
title='Delta Commands', dest='subparsercmd', description='Delta sub-commands', help='Delta sub-commands'
|
|
1008
|
+
)
|
|
1009
|
+
|
|
1010
|
+
# Delta Sub-command: copy
|
|
1011
|
+
p_copy = delta_sub.add_parser(
|
|
1012
|
+
'copy',
|
|
1013
|
+
aliases=['cp'],
|
|
1014
|
+
description=f'Copy file list into delta dir: {__version__}',
|
|
1015
|
+
help='Copy the given list of files into a delta directory',
|
|
1016
|
+
)
|
|
1017
|
+
p_copy.add_argument('--input', '-i', type=str, required=True, help='Input file with diff list')
|
|
1018
|
+
p_copy.add_argument('--folder', '-fd', type=str, help='Delta folder to copy into')
|
|
1019
|
+
p_copy.add_argument('--root', '-rd', type=str, help='Root directory to place delta folder')
|
|
1020
|
+
p_copy.set_defaults(func=delta_copy)
|
|
1021
|
+
|
|
658
1022
|
# Output options
|
|
659
1023
|
for p in [
|
|
660
1024
|
p_scan,
|
|
@@ -674,6 +1038,8 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
674
1038
|
p_crypto_algorithms,
|
|
675
1039
|
p_crypto_hints,
|
|
676
1040
|
p_crypto_versions_in_range,
|
|
1041
|
+
c_licenses,
|
|
1042
|
+
p_copy,
|
|
677
1043
|
]:
|
|
678
1044
|
p.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
|
|
679
1045
|
|
|
@@ -707,25 +1073,11 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
707
1073
|
help='Skip default settings file (scanoss.json) if it exists',
|
|
708
1074
|
)
|
|
709
1075
|
|
|
710
|
-
for p in [p_copyleft, p_undeclared]:
|
|
711
|
-
p.add_argument('-i', '--input', nargs='?', help='Path to results file')
|
|
712
|
-
p.add_argument(
|
|
713
|
-
'-f',
|
|
714
|
-
'--format',
|
|
715
|
-
required=False,
|
|
716
|
-
choices=['json', 'md', 'jira_md'],
|
|
717
|
-
default='json',
|
|
718
|
-
help='Output format (default: json)',
|
|
719
|
-
)
|
|
720
|
-
p.add_argument('-o', '--output', type=str, help='Save details into a file')
|
|
721
|
-
p.add_argument('-s', '--status', type=str, help='Save summary data into Markdown file')
|
|
722
|
-
|
|
723
1076
|
# Global Scan command options
|
|
724
1077
|
for p in [p_scan, p_cs]:
|
|
725
1078
|
p.add_argument(
|
|
726
1079
|
'--apiurl', type=str, help='SCANOSS API URL (optional - default: https://api.osskb.org/scan/direct)'
|
|
727
1080
|
)
|
|
728
|
-
p.add_argument('--ignore-cert-errors', action='store_true', help='Ignore certificate errors')
|
|
729
1081
|
|
|
730
1082
|
# Global Scan/Fingerprint filter options
|
|
731
1083
|
for p in [p_scan, p_wfp]:
|
|
@@ -747,6 +1099,19 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
747
1099
|
p.add_argument('--skip-md5', '-5', type=str, action='append', help='Skip files matching MD5.')
|
|
748
1100
|
p.add_argument('--strip-hpsm', '-G', type=str, action='append', help='Strip HPSM string from WFP.')
|
|
749
1101
|
p.add_argument('--strip-snippet', '-N', type=str, action='append', help='Strip Snippet ID string from WFP.')
|
|
1102
|
+
p.add_argument(
|
|
1103
|
+
'--skip-headers',
|
|
1104
|
+
'-skh',
|
|
1105
|
+
action='store_true',
|
|
1106
|
+
help='Skip license headers, comments and imports at the beginning of files.',
|
|
1107
|
+
)
|
|
1108
|
+
p.add_argument(
|
|
1109
|
+
'--skip-headers-limit',
|
|
1110
|
+
'-shl',
|
|
1111
|
+
type=int,
|
|
1112
|
+
default=0,
|
|
1113
|
+
help='Maximum number of lines to skip when filtering headers (default: 0 = no limit).',
|
|
1114
|
+
)
|
|
750
1115
|
|
|
751
1116
|
# Global Scan/GRPC options
|
|
752
1117
|
for p in [
|
|
@@ -761,6 +1126,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
761
1126
|
p_crypto_algorithms,
|
|
762
1127
|
p_crypto_hints,
|
|
763
1128
|
p_crypto_versions_in_range,
|
|
1129
|
+
c_licenses,
|
|
764
1130
|
]:
|
|
765
1131
|
p.add_argument(
|
|
766
1132
|
'--key', '-k', type=str, help='SCANOSS API Key token (optional - not required for default OSSKB URL)'
|
|
@@ -799,6 +1165,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
799
1165
|
p_crypto_algorithms,
|
|
800
1166
|
p_crypto_hints,
|
|
801
1167
|
p_crypto_versions_in_range,
|
|
1168
|
+
c_licenses,
|
|
802
1169
|
]:
|
|
803
1170
|
p.add_argument(
|
|
804
1171
|
'--api2url', type=str, help='SCANOSS gRPC API 2.0 URL (optional - default: https://api.osskb.org)'
|
|
@@ -816,6 +1183,7 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
816
1183
|
type=str,
|
|
817
1184
|
help='Headers to be sent on request (e.g., -hdr "Name: Value") - can be used multiple times',
|
|
818
1185
|
)
|
|
1186
|
+
p.add_argument('--ignore-cert-errors', action='store_true', help='Ignore certificate errors')
|
|
819
1187
|
|
|
820
1188
|
# Syft options
|
|
821
1189
|
for p in [p_cs, p_dep]:
|
|
@@ -832,6 +1200,24 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
832
1200
|
help='Timeout (in seconds) for syft to complete (optional - default 600)',
|
|
833
1201
|
)
|
|
834
1202
|
|
|
1203
|
+
# gRPC support options
|
|
1204
|
+
for p in [
|
|
1205
|
+
c_vulns,
|
|
1206
|
+
p_scan,
|
|
1207
|
+
p_cs,
|
|
1208
|
+
p_crypto_algorithms,
|
|
1209
|
+
p_crypto_hints,
|
|
1210
|
+
p_crypto_versions_in_range,
|
|
1211
|
+
c_semgrep,
|
|
1212
|
+
c_provenance,
|
|
1213
|
+
c_search,
|
|
1214
|
+
c_versions,
|
|
1215
|
+
c_licenses,
|
|
1216
|
+
p_folder_scan,
|
|
1217
|
+
]:
|
|
1218
|
+
p.add_argument('--grpc', action='store_true', default=True, help='Use gRPC (default)')
|
|
1219
|
+
p.add_argument('--rest', action='store_true', dest='rest', help='Use REST instead of gRPC')
|
|
1220
|
+
|
|
835
1221
|
# Help/Trace command options
|
|
836
1222
|
for p in [
|
|
837
1223
|
p_scan,
|
|
@@ -847,10 +1233,16 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
847
1233
|
c_versions,
|
|
848
1234
|
c_semgrep,
|
|
849
1235
|
p_results,
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1236
|
+
p_inspect_raw_undeclared,
|
|
1237
|
+
p_inspect_raw_copyleft,
|
|
1238
|
+
p_inspect_raw_license_summary,
|
|
1239
|
+
p_inspect_raw_component_summary,
|
|
1240
|
+
p_inspect_legacy_copyleft,
|
|
1241
|
+
p_inspect_legacy_undeclared,
|
|
1242
|
+
p_inspect_legacy_license_summary,
|
|
1243
|
+
p_inspect_legacy_component_summary,
|
|
1244
|
+
p_inspect_dt_project_violation,
|
|
1245
|
+
p_gl_inspect_matches,
|
|
854
1246
|
c_provenance,
|
|
855
1247
|
p_folder_scan,
|
|
856
1248
|
p_folder_hash,
|
|
@@ -858,12 +1250,27 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
858
1250
|
p_crypto_algorithms,
|
|
859
1251
|
p_crypto_hints,
|
|
860
1252
|
p_crypto_versions_in_range,
|
|
1253
|
+
c_licenses,
|
|
1254
|
+
e_dt,
|
|
1255
|
+
p_copy,
|
|
861
1256
|
]:
|
|
862
|
-
p.add_argument(
|
|
1257
|
+
p.add_argument(
|
|
1258
|
+
'--debug',
|
|
1259
|
+
'-d',
|
|
1260
|
+
action='store_true',
|
|
1261
|
+
default=os.environ.get('SCANOSS_DEBUG', '').lower() == 'true',
|
|
1262
|
+
help='Enable debug messages (can also be set via environment variable SCANOSS_DEBUG)',
|
|
1263
|
+
)
|
|
863
1264
|
p.add_argument('--trace', '-t', action='store_true', help='Enable trace messages, including API posts')
|
|
864
1265
|
p.add_argument('--quiet', '-q', action='store_true', help='Enable quiet mode')
|
|
865
1266
|
|
|
866
1267
|
args = parser.parse_args()
|
|
1268
|
+
|
|
1269
|
+
# TODO: Remove this hack once we go back to using REST as default
|
|
1270
|
+
# Handle --rest overriding --grpc default
|
|
1271
|
+
if hasattr(args, 'rest') and args.rest:
|
|
1272
|
+
args.grpc = False
|
|
1273
|
+
|
|
867
1274
|
if args.version:
|
|
868
1275
|
ver(parser, args)
|
|
869
1276
|
sys.exit(0)
|
|
@@ -871,10 +1278,32 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
871
1278
|
parser.print_help() # No sub command subcommand, print general help
|
|
872
1279
|
sys.exit(1)
|
|
873
1280
|
elif (
|
|
874
|
-
args.subparser
|
|
1281
|
+
args.subparser
|
|
1282
|
+
in (
|
|
1283
|
+
'utils',
|
|
1284
|
+
'ut',
|
|
1285
|
+
'component',
|
|
1286
|
+
'comp',
|
|
1287
|
+
'inspect',
|
|
1288
|
+
'insp',
|
|
1289
|
+
'ins',
|
|
1290
|
+
'crypto',
|
|
1291
|
+
'cr',
|
|
1292
|
+
'export',
|
|
1293
|
+
'exp',
|
|
1294
|
+
'delta',
|
|
1295
|
+
'dl',
|
|
1296
|
+
)
|
|
875
1297
|
) and not args.subparsercmd:
|
|
876
1298
|
parser.parse_args([args.subparser, '--help']) # Force utils helps to be displayed
|
|
877
1299
|
sys.exit(1)
|
|
1300
|
+
elif (
|
|
1301
|
+
(args.subparser in 'inspect')
|
|
1302
|
+
and (args.subparsercmd in ('raw', 'dt', 'glc', 'gitlab'))
|
|
1303
|
+
and (args.subparser_subcmd is None)
|
|
1304
|
+
):
|
|
1305
|
+
parser.parse_args([args.subparser, args.subparsercmd, '--help']) # Force utils helps to be displayed
|
|
1306
|
+
sys.exit(1)
|
|
878
1307
|
args.func(parser, args) # Execute the function associated with the sub-command
|
|
879
1308
|
|
|
880
1309
|
|
|
@@ -908,16 +1337,14 @@ def file_count(parser, args):
|
|
|
908
1337
|
print_stderr('Please specify a folder')
|
|
909
1338
|
parser.parse_args([args.subparser, '-h'])
|
|
910
1339
|
sys.exit(1)
|
|
911
|
-
scan_output: str = None
|
|
912
1340
|
if args.output:
|
|
913
|
-
|
|
914
|
-
open(scan_output, 'w').close()
|
|
1341
|
+
initialise_empty_file(args.output)
|
|
915
1342
|
|
|
916
1343
|
counter = FileCount(
|
|
917
1344
|
debug=args.debug,
|
|
918
1345
|
quiet=args.quiet,
|
|
919
1346
|
trace=args.trace,
|
|
920
|
-
scan_output=
|
|
1347
|
+
scan_output=args.output,
|
|
921
1348
|
hidden_files_folders=args.all_hidden,
|
|
922
1349
|
)
|
|
923
1350
|
if not os.path.exists(args.scan_dir):
|
|
@@ -946,10 +1373,8 @@ def wfp(parser, args):
|
|
|
946
1373
|
sys.exit(1)
|
|
947
1374
|
if args.strip_hpsm and not args.hpsm and not args.quiet:
|
|
948
1375
|
print_stderr('Warning: --strip-hpsm option supplied without enabling HPSM (--hpsm). Ignoring.')
|
|
949
|
-
scan_output: str = None
|
|
950
1376
|
if args.output:
|
|
951
|
-
|
|
952
|
-
open(scan_output, 'w').close()
|
|
1377
|
+
initialise_empty_file(args.output)
|
|
953
1378
|
|
|
954
1379
|
# Load scan settings
|
|
955
1380
|
scan_settings = None
|
|
@@ -979,18 +1404,20 @@ def wfp(parser, args):
|
|
|
979
1404
|
strip_hpsm_ids=args.strip_hpsm,
|
|
980
1405
|
strip_snippet_ids=args.strip_snippet,
|
|
981
1406
|
scan_settings=scan_settings,
|
|
1407
|
+
skip_headers=args.skip_headers,
|
|
1408
|
+
skip_headers_limit=args.skip_headers_limit,
|
|
982
1409
|
)
|
|
983
1410
|
if args.stdin:
|
|
984
1411
|
contents = sys.stdin.buffer.read()
|
|
985
|
-
scanner.wfp_contents(args.stdin, contents,
|
|
1412
|
+
scanner.wfp_contents(args.stdin, contents, args.output)
|
|
986
1413
|
elif args.scan_dir:
|
|
987
1414
|
if not os.path.exists(args.scan_dir):
|
|
988
1415
|
print_stderr(f'Error: File or folder specified does not exist: {args.scan_dir}.')
|
|
989
1416
|
sys.exit(1)
|
|
990
1417
|
if os.path.isdir(args.scan_dir):
|
|
991
|
-
scanner.wfp_folder(args.scan_dir,
|
|
1418
|
+
scanner.wfp_folder(args.scan_dir, args.output)
|
|
992
1419
|
elif os.path.isfile(args.scan_dir):
|
|
993
|
-
scanner.wfp_file(args.scan_dir,
|
|
1420
|
+
scanner.wfp_file(args.scan_dir, args.output)
|
|
994
1421
|
else:
|
|
995
1422
|
print_stderr(f'Error: Path specified is neither a file or a folder: {args.scan_dir}.')
|
|
996
1423
|
sys.exit(1)
|
|
@@ -1047,6 +1474,8 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
|
|
|
1047
1474
|
)
|
|
1048
1475
|
parser.parse_args([args.subparser, '-h'])
|
|
1049
1476
|
sys.exit(1)
|
|
1477
|
+
if args.no_wfp_output:
|
|
1478
|
+
print_stderr('Warning: --no-wfp-output is deprecated and has no effect. It will be removed in a future version')
|
|
1050
1479
|
if args.pac and args.proxy:
|
|
1051
1480
|
print_stderr('Please specify one of --proxy or --pac, not both')
|
|
1052
1481
|
parser.parse_args([args.subparser, '-h'])
|
|
@@ -1071,9 +1500,8 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
|
|
|
1071
1500
|
'blacklist'
|
|
1072
1501
|
)
|
|
1073
1502
|
else:
|
|
1074
|
-
scan_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new')
|
|
1075
|
-
|
|
1076
|
-
)
|
|
1503
|
+
scan_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new')
|
|
1504
|
+
|
|
1077
1505
|
except ScanossSettingsError as e:
|
|
1078
1506
|
print_stderr(f'Error: {e}')
|
|
1079
1507
|
sys.exit(1)
|
|
@@ -1088,10 +1516,8 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
|
|
|
1088
1516
|
if args.strip_hpsm and not args.hpsm and not args.quiet:
|
|
1089
1517
|
print_stderr('Warning: --strip-hpsm option supplied without enabling HPSM (--hpsm). Ignoring.')
|
|
1090
1518
|
|
|
1091
|
-
scan_output: str = None
|
|
1092
1519
|
if args.output:
|
|
1093
|
-
|
|
1094
|
-
open(scan_output, 'w').close()
|
|
1520
|
+
initialise_empty_file(args.output)
|
|
1095
1521
|
output_format = args.format if args.format else 'plain'
|
|
1096
1522
|
flags = args.flags if args.flags else None
|
|
1097
1523
|
if args.debug and not args.quiet:
|
|
@@ -1131,9 +1557,6 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
|
|
|
1131
1557
|
if args.retry < 0:
|
|
1132
1558
|
print_stderr(f'POST retry (--retry) too small: {args.retry}. Reverting to default.')
|
|
1133
1559
|
|
|
1134
|
-
if not os.access(os.getcwd(), os.W_OK): # Make sure the current directory is writable. If not disable saving WFP
|
|
1135
|
-
print_stderr(f'Warning: Current directory is not writable: {os.getcwd()}')
|
|
1136
|
-
args.no_wfp_output = True
|
|
1137
1560
|
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
1138
1561
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
1139
1562
|
sys.exit(1)
|
|
@@ -1146,13 +1569,12 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
|
|
|
1146
1569
|
quiet=args.quiet,
|
|
1147
1570
|
api_key=args.key,
|
|
1148
1571
|
url=args.apiurl,
|
|
1149
|
-
scan_output=
|
|
1572
|
+
scan_output=args.output,
|
|
1150
1573
|
output_format=output_format,
|
|
1151
1574
|
flags=flags,
|
|
1152
1575
|
nb_threads=args.threads,
|
|
1153
1576
|
post_size=args.post_size,
|
|
1154
1577
|
timeout=args.timeout,
|
|
1155
|
-
no_wfp_file=args.no_wfp_output,
|
|
1156
1578
|
all_extensions=args.all_extensions,
|
|
1157
1579
|
all_folders=args.all_folders,
|
|
1158
1580
|
hidden_files_folders=args.all_hidden,
|
|
@@ -1176,6 +1598,9 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
|
|
|
1176
1598
|
strip_snippet_ids=args.strip_snippet,
|
|
1177
1599
|
scan_settings=scan_settings,
|
|
1178
1600
|
req_headers=process_req_headers(args.header),
|
|
1601
|
+
use_grpc=args.grpc,
|
|
1602
|
+
skip_headers=args.skip_headers,
|
|
1603
|
+
skip_headers_limit=args.skip_headers_limit,
|
|
1179
1604
|
)
|
|
1180
1605
|
if args.wfp:
|
|
1181
1606
|
if not scanner.is_file_or_snippet_scan():
|
|
@@ -1258,16 +1683,15 @@ def dependency(parser, args):
|
|
|
1258
1683
|
if not os.path.exists(args.scan_loc):
|
|
1259
1684
|
print_stderr(f'Error: File or folder specified does not exist: {args.scan_loc}.')
|
|
1260
1685
|
sys.exit(1)
|
|
1261
|
-
scan_output: str = None
|
|
1262
1686
|
if args.output:
|
|
1263
|
-
|
|
1264
|
-
open(scan_output, 'w').close()
|
|
1687
|
+
initialise_empty_file(args.output)
|
|
1265
1688
|
|
|
1266
1689
|
sc_deps = ScancodeDeps(
|
|
1267
1690
|
debug=args.debug, quiet=args.quiet, trace=args.trace, sc_command=args.sc_command, timeout=args.sc_timeout
|
|
1268
1691
|
)
|
|
1269
|
-
if not sc_deps.get_dependencies(what_to_scan=args.scan_loc, result_output=
|
|
1692
|
+
if not sc_deps.get_dependencies(what_to_scan=args.scan_loc, result_output=args.output):
|
|
1270
1693
|
sys.exit(1)
|
|
1694
|
+
return None
|
|
1271
1695
|
|
|
1272
1696
|
|
|
1273
1697
|
def convert(parser, args):
|
|
@@ -1300,148 +1724,421 @@ def convert(parser, args):
|
|
|
1300
1724
|
print_stderr('Producing CSV report...')
|
|
1301
1725
|
csvo = CsvOutput(debug=args.debug, output_file=args.output)
|
|
1302
1726
|
success = csvo.produce_from_file(args.input)
|
|
1727
|
+
elif args.format == 'glc-codequality':
|
|
1728
|
+
if not args.quiet:
|
|
1729
|
+
print_stderr('Producing GitLab code quality report...')
|
|
1730
|
+
glc_code_quality = GitLabQualityReport(debug=args.debug, trace=args.trace, quiet=args.quiet)
|
|
1731
|
+
success = glc_code_quality.produce_from_file(args.input, output_file=args.output)
|
|
1303
1732
|
else:
|
|
1304
1733
|
print_stderr(f'ERROR: Unknown output format (--format): {args.format}')
|
|
1305
1734
|
if not success:
|
|
1306
1735
|
sys.exit(1)
|
|
1307
1736
|
|
|
1308
|
-
|
|
1737
|
+
|
|
1738
|
+
# =============================================================================
|
|
1739
|
+
# INSPECT COMMAND HANDLERS - Functions that execute inspection operations
|
|
1740
|
+
# =============================================================================
|
|
1741
|
+
|
|
1742
|
+
|
|
1309
1743
|
def inspect_copyleft(parser, args):
|
|
1310
1744
|
"""
|
|
1311
|
-
|
|
1745
|
+
Handle copyleft license inspection command.
|
|
1746
|
+
|
|
1747
|
+
Analyses scan results to identify components using copyleft licenses
|
|
1748
|
+
that may require compliance actions such as source code disclosure.
|
|
1749
|
+
|
|
1312
1750
|
Parameters
|
|
1313
1751
|
----------
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1752
|
+
parser : ArgumentParser
|
|
1753
|
+
Command line parser object for help display
|
|
1754
|
+
args : Namespace
|
|
1755
|
+
Parsed command line arguments containing:
|
|
1756
|
+
- input: Path to scan results file
|
|
1757
|
+
- output: Optional output file path
|
|
1758
|
+
- status: Optional status summary file path
|
|
1759
|
+
- format: Output format (json, md, jira_md)
|
|
1760
|
+
- include/exclude/explicit: License filter options
|
|
1318
1761
|
"""
|
|
1762
|
+
# Validate required input file parameter
|
|
1319
1763
|
if args.input is None:
|
|
1320
|
-
print_stderr('
|
|
1321
|
-
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
1764
|
+
print_stderr('ERROR: Input file is required for copyleft inspection')
|
|
1765
|
+
parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
|
|
1322
1766
|
sys.exit(1)
|
|
1323
|
-
output
|
|
1767
|
+
# Initialise output file if specified
|
|
1324
1768
|
if args.output:
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
status_output: str = None
|
|
1769
|
+
initialise_empty_file(args.output)
|
|
1770
|
+
# Initialise status summary file if specified
|
|
1329
1771
|
if args.status:
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1772
|
+
initialise_empty_file(args.status)
|
|
1773
|
+
try:
|
|
1774
|
+
# Create and configure copyleft inspector
|
|
1775
|
+
i_copyleft = Copyleft(
|
|
1776
|
+
debug=args.debug,
|
|
1777
|
+
trace=args.trace,
|
|
1778
|
+
quiet=args.quiet,
|
|
1779
|
+
filepath=args.input,
|
|
1780
|
+
format_type=args.format,
|
|
1781
|
+
status=args.status,
|
|
1782
|
+
output=args.output,
|
|
1783
|
+
include=args.include, # Additional licenses to check
|
|
1784
|
+
exclude=args.exclude, # Licenses to ignore
|
|
1785
|
+
explicit=args.explicit, # Explicit license list
|
|
1786
|
+
license_sources=args.license_sources, # License sources to check (list)
|
|
1787
|
+
)
|
|
1788
|
+
# Execute inspection and exit with appropriate status code
|
|
1789
|
+
status, _ = i_copyleft.run()
|
|
1790
|
+
sys.exit(status)
|
|
1791
|
+
except Exception as e:
|
|
1792
|
+
print_stderr(e)
|
|
1793
|
+
if args.debug:
|
|
1794
|
+
traceback.print_exc()
|
|
1795
|
+
sys.exit(1)
|
|
1347
1796
|
|
|
1348
1797
|
|
|
1349
1798
|
def inspect_undeclared(parser, args):
|
|
1350
1799
|
"""
|
|
1351
|
-
|
|
1800
|
+
Handle undeclared components inspection command.
|
|
1801
|
+
|
|
1802
|
+
Analyses scan results to identify components that are present in the
|
|
1803
|
+
codebase but not declared in SBOM or manifest files, which may indicate
|
|
1804
|
+
security or compliance risks.
|
|
1805
|
+
|
|
1352
1806
|
Parameters
|
|
1353
1807
|
----------
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1808
|
+
parser : ArgumentParser
|
|
1809
|
+
Command line parser object for help display
|
|
1810
|
+
args : Namespace
|
|
1811
|
+
Parsed command line arguments containing:
|
|
1812
|
+
- input: Path to scan results file
|
|
1813
|
+
- output: Optional output file path
|
|
1814
|
+
- status: Optional status summary file path
|
|
1815
|
+
- format: Output format (json, md, jira_md)
|
|
1816
|
+
- sbom_format: SBOM format type (legacy, settings)
|
|
1358
1817
|
"""
|
|
1818
|
+
# Validate required input file parameter
|
|
1359
1819
|
if args.input is None:
|
|
1360
|
-
print_stderr('
|
|
1361
|
-
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
1820
|
+
print_stderr('ERROR: Input file is required for undeclared component inspection')
|
|
1821
|
+
parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
|
|
1362
1822
|
sys.exit(1)
|
|
1363
|
-
|
|
1823
|
+
|
|
1824
|
+
# Initialise output file if specified
|
|
1364
1825
|
if args.output:
|
|
1365
|
-
|
|
1366
|
-
open(output, 'w').close()
|
|
1826
|
+
initialise_empty_file(args.output)
|
|
1367
1827
|
|
|
1368
|
-
|
|
1828
|
+
# Initialise status summary file if specified
|
|
1369
1829
|
if args.status:
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1830
|
+
initialise_empty_file(args.status)
|
|
1831
|
+
|
|
1832
|
+
try:
|
|
1833
|
+
# Create and configure undeclared component inspector
|
|
1834
|
+
i_undeclared = UndeclaredComponent(
|
|
1835
|
+
debug=args.debug,
|
|
1836
|
+
trace=args.trace,
|
|
1837
|
+
quiet=args.quiet,
|
|
1838
|
+
filepath=args.input,
|
|
1839
|
+
format_type=args.format,
|
|
1840
|
+
status=args.status,
|
|
1841
|
+
output=args.output,
|
|
1842
|
+
sbom_format=args.sbom_format, # Format for SBOM comparison
|
|
1843
|
+
)
|
|
1844
|
+
|
|
1845
|
+
# Execute inspection and exit with appropriate status code
|
|
1846
|
+
status, _ = i_undeclared.run()
|
|
1847
|
+
sys.exit(status)
|
|
1848
|
+
except Exception as e:
|
|
1849
|
+
print_stderr(e)
|
|
1850
|
+
if args.debug:
|
|
1851
|
+
traceback.print_exc()
|
|
1852
|
+
sys.exit(1)
|
|
1853
|
+
|
|
1384
1854
|
|
|
1385
1855
|
def inspect_license_summary(parser, args):
|
|
1386
1856
|
"""
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1857
|
+
Handle license summary inspection command.
|
|
1858
|
+
|
|
1859
|
+
Generates comprehensive summary of all licenses detected in scan results,
|
|
1860
|
+
including license counts, risk levels, and compliance recommendations.
|
|
1861
|
+
|
|
1862
|
+
Parameters
|
|
1863
|
+
----------
|
|
1864
|
+
parser : ArgumentParser
|
|
1865
|
+
Command line parser object for help display
|
|
1866
|
+
args : Namespace
|
|
1867
|
+
Parsed command line arguments containing:
|
|
1868
|
+
- input: Path to scan results file
|
|
1869
|
+
- output: Optional output file path
|
|
1870
|
+
- include/exclude/explicit: License filter options
|
|
1871
|
+
"""
|
|
1872
|
+
# Validate required input file parameter
|
|
1395
1873
|
if args.input is None:
|
|
1396
|
-
print_stderr('
|
|
1397
|
-
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
1874
|
+
print_stderr('ERROR: Input file is required for license summary')
|
|
1875
|
+
parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
|
|
1398
1876
|
sys.exit(1)
|
|
1399
|
-
|
|
1877
|
+
|
|
1878
|
+
# Initialise output file if specified
|
|
1400
1879
|
if args.output:
|
|
1401
|
-
|
|
1402
|
-
open(output, 'w').close()
|
|
1880
|
+
initialise_empty_file(args.output)
|
|
1403
1881
|
|
|
1882
|
+
# Create and configure license summary generator
|
|
1404
1883
|
i_license_summary = LicenseSummary(
|
|
1405
1884
|
debug=args.debug,
|
|
1406
1885
|
trace=args.trace,
|
|
1407
1886
|
quiet=args.quiet,
|
|
1408
1887
|
filepath=args.input,
|
|
1409
|
-
output=output,
|
|
1410
|
-
include=args.include,
|
|
1411
|
-
exclude=args.exclude,
|
|
1412
|
-
explicit=args.explicit,
|
|
1888
|
+
output=args.output,
|
|
1889
|
+
include=args.include, # Additional licenses to include
|
|
1890
|
+
exclude=args.exclude, # Licenses to exclude from summary
|
|
1891
|
+
explicit=args.explicit, # Explicit license list to summarize
|
|
1413
1892
|
)
|
|
1414
|
-
|
|
1893
|
+
try:
|
|
1894
|
+
# Execute summary generation
|
|
1895
|
+
i_license_summary.run()
|
|
1896
|
+
except Exception as e:
|
|
1897
|
+
print_stderr(e)
|
|
1898
|
+
if args.debug:
|
|
1899
|
+
traceback.print_exc()
|
|
1900
|
+
sys.exit(1)
|
|
1901
|
+
|
|
1415
1902
|
|
|
1416
1903
|
def inspect_component_summary(parser, args):
|
|
1417
1904
|
"""
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1905
|
+
Handle component summary inspection command.
|
|
1906
|
+
|
|
1907
|
+
Generates a comprehensive summary of all components detected in scan results,
|
|
1908
|
+
including component counts, versions, match types, and security information.
|
|
1909
|
+
|
|
1910
|
+
Parameters
|
|
1911
|
+
----------
|
|
1912
|
+
parser : ArgumentParser
|
|
1913
|
+
Command line parser object for help display
|
|
1914
|
+
args : Namespace
|
|
1915
|
+
Parsed command line arguments containing:
|
|
1916
|
+
- input: Path to scan results file
|
|
1917
|
+
- output: Optional output file path
|
|
1918
|
+
"""
|
|
1919
|
+
# Validate required input file parameter
|
|
1426
1920
|
if args.input is None:
|
|
1427
|
-
print_stderr('
|
|
1428
|
-
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
1921
|
+
print_stderr('ERROR: Input file is required for component summary')
|
|
1922
|
+
parser.parse_args([args.subparser, args.subparsercmd, args.subparser_subcmd, '-h'])
|
|
1429
1923
|
sys.exit(1)
|
|
1430
|
-
|
|
1924
|
+
|
|
1925
|
+
# Initialise an output file if specified
|
|
1431
1926
|
if args.output:
|
|
1432
|
-
output
|
|
1433
|
-
open(output, 'w').close()
|
|
1927
|
+
initialise_empty_file(args.output) # Create/clear output file
|
|
1434
1928
|
|
|
1929
|
+
# Create and configure component summary generator
|
|
1435
1930
|
i_component_summary = ComponentSummary(
|
|
1436
1931
|
debug=args.debug,
|
|
1437
1932
|
trace=args.trace,
|
|
1438
1933
|
quiet=args.quiet,
|
|
1439
1934
|
filepath=args.input,
|
|
1440
|
-
output=output,
|
|
1935
|
+
output=args.output,
|
|
1441
1936
|
)
|
|
1442
|
-
i_component_summary.run()
|
|
1443
1937
|
|
|
1444
|
-
|
|
1938
|
+
try:
|
|
1939
|
+
# Execute summary generation
|
|
1940
|
+
i_component_summary.run()
|
|
1941
|
+
except Exception as e:
|
|
1942
|
+
print_stderr(e)
|
|
1943
|
+
if args.debug:
|
|
1944
|
+
traceback.print_exc()
|
|
1945
|
+
sys.exit(1)
|
|
1946
|
+
|
|
1947
|
+
|
|
1948
|
+
def inspect_dep_track_project_violations(parser, args):
|
|
1949
|
+
"""
|
|
1950
|
+
Handle Dependency Track project inspection command.
|
|
1951
|
+
|
|
1952
|
+
Analyses Dependency Track projects for policy violations, security issues,
|
|
1953
|
+
and compliance status. Connects to DT API to retrieve project data and
|
|
1954
|
+
generate detailed violation reports.
|
|
1955
|
+
|
|
1956
|
+
Parameters
|
|
1957
|
+
----------
|
|
1958
|
+
parser : ArgumentParser
|
|
1959
|
+
Command line parser object for help display
|
|
1960
|
+
args : Namespace
|
|
1961
|
+
Parsed command line arguments containing:
|
|
1962
|
+
- url: Dependency Track base URL
|
|
1963
|
+
- apikey: API key for authentication
|
|
1964
|
+
- project_id: Project UUID to inspect
|
|
1965
|
+
- project_name: Project name to inspect
|
|
1966
|
+
- project_version: Project version to inspect
|
|
1967
|
+
- upload_token: Upload token for project access
|
|
1968
|
+
- output: Optional output file path
|
|
1969
|
+
- format: Output format (json, md)
|
|
1970
|
+
- timeout: Optional timeout for API requests
|
|
1971
|
+
|
|
1972
|
+
"""
|
|
1973
|
+
# Make sure we have project id/project name and version
|
|
1974
|
+
_dt_args_validator(parser, args)
|
|
1975
|
+
# Initialise the output file if specified
|
|
1976
|
+
if args.output:
|
|
1977
|
+
initialise_empty_file(args.output)
|
|
1978
|
+
# Create and configure Dependency Track inspector
|
|
1979
|
+
try:
|
|
1980
|
+
dt_proj_violations = DependencyTrackProjectViolationPolicyCheck(
|
|
1981
|
+
debug=args.debug,
|
|
1982
|
+
trace=args.trace,
|
|
1983
|
+
quiet=args.quiet,
|
|
1984
|
+
output=args.output,
|
|
1985
|
+
status=args.status,
|
|
1986
|
+
format_type=args.format,
|
|
1987
|
+
url=args.url, # DT server URL
|
|
1988
|
+
api_key=args.apikey, # Authentication key
|
|
1989
|
+
project_id=args.project_id, # Target project UUID
|
|
1990
|
+
upload_token=args.upload_token, # Upload access token
|
|
1991
|
+
project_name=args.project_name, # DT project name
|
|
1992
|
+
project_version=args.project_version, # DT project version
|
|
1993
|
+
timeout=args.timeout,
|
|
1994
|
+
)
|
|
1995
|
+
# Execute inspection and exit with appropriate status code
|
|
1996
|
+
status = dt_proj_violations.run()
|
|
1997
|
+
sys.exit(status)
|
|
1998
|
+
except Exception as e:
|
|
1999
|
+
print_stderr(e)
|
|
2000
|
+
if args.debug:
|
|
2001
|
+
traceback.print_exc()
|
|
2002
|
+
sys.exit(1)
|
|
2003
|
+
|
|
2004
|
+
|
|
2005
|
+
def inspect_gitlab_matches(parser, args):
|
|
2006
|
+
"""
|
|
2007
|
+
Handle GitLab matches the summary inspection command.
|
|
2008
|
+
|
|
2009
|
+
Analyzes SCANOSS scan results and generates a GitLab-compatible Markdown summary
|
|
2010
|
+
report of component matches. The report includes match details, file locations,
|
|
2011
|
+
and optionally clickable links to source files in GitLab repositories.
|
|
2012
|
+
|
|
2013
|
+
This command processes SCANOSS scan output and creates human-readable Markdown.
|
|
2014
|
+
|
|
2015
|
+
Parameters
|
|
2016
|
+
----------
|
|
2017
|
+
parser : ArgumentParser
|
|
2018
|
+
Command line parser object for help display
|
|
2019
|
+
args : Namespace
|
|
2020
|
+
Parsed command line arguments containing:
|
|
2021
|
+
- input: Path to SCANOSS scan results file (JSON format) to analyze
|
|
2022
|
+
- line_range_prefix: Base URL prefix for generating GitLab file links with line ranges
|
|
2023
|
+
(e.g., 'https://gitlab.com/org/project/-/blob/main')
|
|
2024
|
+
- output: Optional output file path for the generated Markdown report (default: stdout)
|
|
2025
|
+
- debug: Enable debug output for troubleshooting
|
|
2026
|
+
- trace: Enable trace-level logging
|
|
2027
|
+
- quiet: Suppress informational messages
|
|
2028
|
+
|
|
2029
|
+
Notes
|
|
2030
|
+
-----
|
|
2031
|
+
- The output is formatted in Markdown for optimal display in GitLab
|
|
2032
|
+
- Line range prefix enables clickable file references in the report
|
|
2033
|
+
- If output is not specified, the report is written to stdout
|
|
2034
|
+
"""
|
|
2035
|
+
|
|
2036
|
+
if args.input is None:
|
|
2037
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2038
|
+
sys.exit(1)
|
|
2039
|
+
|
|
2040
|
+
if args.line_range_prefix is None:
|
|
2041
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2042
|
+
sys.exit(1)
|
|
2043
|
+
|
|
2044
|
+
# Initialize output file if specified (create/truncate)
|
|
2045
|
+
if args.output:
|
|
2046
|
+
initialise_empty_file(args.output)
|
|
2047
|
+
|
|
2048
|
+
try:
|
|
2049
|
+
# Create GitLab matches summary generator with configuration
|
|
2050
|
+
match_summary = MatchSummary(
|
|
2051
|
+
debug=args.debug,
|
|
2052
|
+
trace=args.trace,
|
|
2053
|
+
quiet=args.quiet,
|
|
2054
|
+
scanoss_results_path=args.input, # Path to SCANOSS JSON results
|
|
2055
|
+
output=args.output, # Output file path or None for stdout
|
|
2056
|
+
line_range_prefix=args.line_range_prefix, # GitLab URL prefix for file links
|
|
2057
|
+
)
|
|
2058
|
+
|
|
2059
|
+
# Execute the summary generation
|
|
2060
|
+
match_summary.run()
|
|
2061
|
+
except Exception as e:
|
|
2062
|
+
# Handle any errors during report generation
|
|
2063
|
+
print_stderr(e)
|
|
2064
|
+
if args.debug:
|
|
2065
|
+
traceback.print_exc()
|
|
2066
|
+
sys.exit(1)
|
|
2067
|
+
|
|
2068
|
+
|
|
2069
|
+
# =============================================================================
|
|
2070
|
+
# END INSPECT COMMAND HANDLERS
|
|
2071
|
+
# =============================================================================
|
|
2072
|
+
|
|
2073
|
+
|
|
2074
|
+
def export_dt(parser, args):
|
|
2075
|
+
"""
|
|
2076
|
+
Validates and exports a Software Bill of Materials (SBOM) to a Dependency-Track server.
|
|
2077
|
+
|
|
2078
|
+
Parameters:
|
|
2079
|
+
parser (argparse.ArgumentParser): The argument parser to validate input arguments.
|
|
2080
|
+
args (argparse.Namespace): Parsed arguments passed to the command.
|
|
2081
|
+
|
|
2082
|
+
Raises:
|
|
2083
|
+
SystemExit: If argument validation fails or uploading the SBOM to the Dependency-Track server
|
|
2084
|
+
is unsuccessful.
|
|
2085
|
+
"""
|
|
2086
|
+
# Make sure we have project id/project name and version
|
|
2087
|
+
_dt_args_validator(parser, args)
|
|
2088
|
+
if args.output:
|
|
2089
|
+
initialise_empty_file(args.output)
|
|
2090
|
+
if not args.quiet:
|
|
2091
|
+
print_stderr(f'Outputting export data result to: {args.output}')
|
|
2092
|
+
try:
|
|
2093
|
+
dt_exporter = DependencyTrackExporter(
|
|
2094
|
+
url=args.url,
|
|
2095
|
+
apikey=args.apikey,
|
|
2096
|
+
output=args.output,
|
|
2097
|
+
debug=args.debug,
|
|
2098
|
+
trace=args.trace,
|
|
2099
|
+
quiet=args.quiet,
|
|
2100
|
+
)
|
|
2101
|
+
success = dt_exporter.upload_sbom_file(
|
|
2102
|
+
args.input, args.project_id, args.project_name, args.project_version, args.output
|
|
2103
|
+
)
|
|
2104
|
+
if not success:
|
|
2105
|
+
sys.exit(1)
|
|
2106
|
+
except Exception as e:
|
|
2107
|
+
print_stderr(f'ERROR: {e}')
|
|
2108
|
+
if args.debug:
|
|
2109
|
+
traceback.print_exc()
|
|
2110
|
+
sys.exit(1)
|
|
2111
|
+
|
|
2112
|
+
|
|
2113
|
+
def _dt_args_validator(parser, args):
|
|
2114
|
+
"""
|
|
2115
|
+
Validates command-line arguments related to project identification.
|
|
2116
|
+
|
|
2117
|
+
Parameters
|
|
2118
|
+
----------
|
|
2119
|
+
parser : argparse.ArgumentParser
|
|
2120
|
+
An argument parser instance for handling command-line arguments.
|
|
2121
|
+
args : argparse.Namespace
|
|
2122
|
+
Parsed arguments from the command line containing project-related information.
|
|
2123
|
+
|
|
2124
|
+
Raises
|
|
2125
|
+
------
|
|
2126
|
+
SystemExit
|
|
2127
|
+
If neither a project ID nor the required combination of project name and
|
|
2128
|
+
project version is provided, or if any of the compulsory arguments
|
|
2129
|
+
are missing.
|
|
2130
|
+
"""
|
|
2131
|
+
if not args.project_id and not args.project_name and not args.project_version:
|
|
2132
|
+
print_stderr(
|
|
2133
|
+
'Please specify either a project ID (--project-id) or a project name (--project-name) and '
|
|
2134
|
+
'version (--project-version)'
|
|
2135
|
+
)
|
|
2136
|
+
parser.parse_args([args.subparser, '-h'])
|
|
2137
|
+
sys.exit(1)
|
|
2138
|
+
if not args.project_id and (not args.project_name or not args.project_version):
|
|
2139
|
+
print_stderr('Please supply a project name (--project-name) and version (--project-version)')
|
|
2140
|
+
sys.exit(1)
|
|
2141
|
+
|
|
1445
2142
|
|
|
1446
2143
|
def utils_certloc(*_):
|
|
1447
2144
|
"""
|
|
@@ -1706,6 +2403,8 @@ def comp_vulns(parser, args):
|
|
|
1706
2403
|
pac=pac_file,
|
|
1707
2404
|
timeout=args.timeout,
|
|
1708
2405
|
req_headers=process_req_headers(args.header),
|
|
2406
|
+
ignore_cert_errors=args.ignore_cert_errors,
|
|
2407
|
+
use_grpc=args.grpc,
|
|
1709
2408
|
)
|
|
1710
2409
|
if not comps.get_vulnerabilities(args.input, args.purl, args.output):
|
|
1711
2410
|
sys.exit(1)
|
|
@@ -1741,6 +2440,7 @@ def comp_semgrep(parser, args):
|
|
|
1741
2440
|
pac=pac_file,
|
|
1742
2441
|
timeout=args.timeout,
|
|
1743
2442
|
req_headers=process_req_headers(args.header),
|
|
2443
|
+
use_grpc=args.grpc,
|
|
1744
2444
|
)
|
|
1745
2445
|
if not comps.get_semgrep_details(args.input, args.purl, args.output):
|
|
1746
2446
|
sys.exit(1)
|
|
@@ -1779,6 +2479,7 @@ def comp_search(parser, args):
|
|
|
1779
2479
|
pac=pac_file,
|
|
1780
2480
|
timeout=args.timeout,
|
|
1781
2481
|
req_headers=process_req_headers(args.header),
|
|
2482
|
+
use_grpc=args.grpc,
|
|
1782
2483
|
)
|
|
1783
2484
|
if not comps.search_components(
|
|
1784
2485
|
args.output,
|
|
@@ -1824,6 +2525,7 @@ def comp_versions(parser, args):
|
|
|
1824
2525
|
pac=pac_file,
|
|
1825
2526
|
timeout=args.timeout,
|
|
1826
2527
|
req_headers=process_req_headers(args.header),
|
|
2528
|
+
use_grpc=args.grpc,
|
|
1827
2529
|
)
|
|
1828
2530
|
if not comps.get_component_versions(args.output, json_file=args.input, purl=args.purl, limit=args.limit):
|
|
1829
2531
|
sys.exit(1)
|
|
@@ -1859,11 +2561,48 @@ def comp_provenance(parser, args):
|
|
|
1859
2561
|
pac=pac_file,
|
|
1860
2562
|
timeout=args.timeout,
|
|
1861
2563
|
req_headers=process_req_headers(args.header),
|
|
2564
|
+
use_grpc=args.grpc,
|
|
1862
2565
|
)
|
|
1863
2566
|
if not comps.get_provenance_details(args.input, args.purl, args.output, args.origin):
|
|
1864
2567
|
sys.exit(1)
|
|
1865
2568
|
|
|
1866
2569
|
|
|
2570
|
+
def comp_licenses(parser, args):
|
|
2571
|
+
"""
|
|
2572
|
+
Run the "component licenses" sub-command
|
|
2573
|
+
Parameters
|
|
2574
|
+
----------
|
|
2575
|
+
parser: ArgumentParser
|
|
2576
|
+
command line parser object
|
|
2577
|
+
args: Namespace
|
|
2578
|
+
Parsed arguments
|
|
2579
|
+
"""
|
|
2580
|
+
if (not args.purl and not args.input) or (args.purl and args.input):
|
|
2581
|
+
print_stderr('ERROR: Please specify an input file or purl to decorate (--purl or --input)')
|
|
2582
|
+
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
2583
|
+
sys.exit(1)
|
|
2584
|
+
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
2585
|
+
print_stderr(f'ERROR: Certificate file does not exist: {args.ca_cert}.')
|
|
2586
|
+
sys.exit(1)
|
|
2587
|
+
pac_file = get_pac_file(args.pac)
|
|
2588
|
+
comps = Components(
|
|
2589
|
+
debug=args.debug,
|
|
2590
|
+
trace=args.trace,
|
|
2591
|
+
quiet=args.quiet,
|
|
2592
|
+
grpc_url=args.api2url,
|
|
2593
|
+
api_key=args.key,
|
|
2594
|
+
ca_cert=args.ca_cert,
|
|
2595
|
+
proxy=args.proxy,
|
|
2596
|
+
grpc_proxy=args.grpc_proxy,
|
|
2597
|
+
pac=pac_file,
|
|
2598
|
+
timeout=args.timeout,
|
|
2599
|
+
req_headers=process_req_headers(args.header),
|
|
2600
|
+
use_grpc=args.grpc,
|
|
2601
|
+
)
|
|
2602
|
+
if not comps.get_licenses(args.input, args.purl, args.output):
|
|
2603
|
+
sys.exit(1)
|
|
2604
|
+
|
|
2605
|
+
|
|
1867
2606
|
def results(parser, args):
|
|
1868
2607
|
"""
|
|
1869
2608
|
Run the "results" sub-command
|
|
@@ -1959,6 +2698,10 @@ def folder_hashing_scan(parser, args):
|
|
|
1959
2698
|
client=client,
|
|
1960
2699
|
scanoss_settings=scanoss_settings,
|
|
1961
2700
|
rank_threshold=args.rank_threshold,
|
|
2701
|
+
depth=args.depth,
|
|
2702
|
+
recursive_threshold=args.recursive_threshold,
|
|
2703
|
+
min_accepted_score=args.min_accepted_score,
|
|
2704
|
+
use_grpc=args.grpc,
|
|
1962
2705
|
)
|
|
1963
2706
|
|
|
1964
2707
|
if scanner.scan():
|
|
@@ -1992,6 +2735,7 @@ def folder_hash(parser, args):
|
|
|
1992
2735
|
scan_dir=args.scan_dir,
|
|
1993
2736
|
config=folder_hasher_config,
|
|
1994
2737
|
scanoss_settings=scanoss_settings,
|
|
2738
|
+
depth=args.depth,
|
|
1995
2739
|
)
|
|
1996
2740
|
|
|
1997
2741
|
folder_hasher.hash_directory(args.scan_dir)
|
|
@@ -2046,7 +2790,77 @@ def get_scanoss_settings_from_args(args):
|
|
|
2046
2790
|
except ScanossSettingsError as e:
|
|
2047
2791
|
print_stderr(f'Error: {e}')
|
|
2048
2792
|
sys.exit(1)
|
|
2049
|
-
|
|
2793
|
+
return scanoss_settings
|
|
2794
|
+
|
|
2795
|
+
|
|
2796
|
+
def initialise_empty_file(filename: str):
|
|
2797
|
+
"""
|
|
2798
|
+
Initialises an empty file with the specified name. If the file already exists,
|
|
2799
|
+
it truncates its content. Ensures proper error handling in case of failure.
|
|
2800
|
+
|
|
2801
|
+
Args:
|
|
2802
|
+
filename (str): The name of the file to be initialised.
|
|
2803
|
+
|
|
2804
|
+
Raises:
|
|
2805
|
+
SystemExit: If the file cannot be created or written due to an exception,
|
|
2806
|
+
the function prints an error message and exits the program.
|
|
2807
|
+
|
|
2808
|
+
Note:
|
|
2809
|
+
This function writes an empty file and handles exceptions to ensure the
|
|
2810
|
+
program does not continue execution in case of an error.
|
|
2811
|
+
"""
|
|
2812
|
+
if filename:
|
|
2813
|
+
try:
|
|
2814
|
+
open(filename, 'w').close()
|
|
2815
|
+
except Exception as e:
|
|
2816
|
+
print_stderr(f'Error: Unable to create output file {filename}: {e}')
|
|
2817
|
+
sys.exit(1)
|
|
2818
|
+
|
|
2819
|
+
|
|
2820
|
+
def delta_copy(parser, args):
|
|
2821
|
+
"""
|
|
2822
|
+
Handle delta copy command.
|
|
2823
|
+
|
|
2824
|
+
Copies files listed in an input file to a target directory while preserving
|
|
2825
|
+
their directory structure. Creates a unique delta directory if none is specified.
|
|
2826
|
+
|
|
2827
|
+
Parameters
|
|
2828
|
+
----------
|
|
2829
|
+
parser : ArgumentParser
|
|
2830
|
+
Command line parser object for help display
|
|
2831
|
+
args : Namespace
|
|
2832
|
+
Parsed command line arguments containing:
|
|
2833
|
+
- input: Path to file containing list of files to copy
|
|
2834
|
+
- folder: Optional target directory path
|
|
2835
|
+
- output: Optional output file path
|
|
2836
|
+
"""
|
|
2837
|
+
# Validate required input file parameter
|
|
2838
|
+
if args.input is None:
|
|
2839
|
+
print_stderr('ERROR: Input file is required for copying')
|
|
2840
|
+
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
2841
|
+
sys.exit(1)
|
|
2842
|
+
# Initialise output file if specified
|
|
2843
|
+
if args.output:
|
|
2844
|
+
initialise_empty_file(args.output)
|
|
2845
|
+
try:
|
|
2846
|
+
# Create and configure delta copy command
|
|
2847
|
+
delta = Delta(
|
|
2848
|
+
debug=args.debug,
|
|
2849
|
+
trace=args.trace,
|
|
2850
|
+
quiet=args.quiet,
|
|
2851
|
+
filepath=args.input,
|
|
2852
|
+
folder=args.folder,
|
|
2853
|
+
output=args.output,
|
|
2854
|
+
root_dir=args.root,
|
|
2855
|
+
)
|
|
2856
|
+
# Execute copy and exit with appropriate status code
|
|
2857
|
+
status, _ = delta.copy()
|
|
2858
|
+
sys.exit(status)
|
|
2859
|
+
except Exception as e:
|
|
2860
|
+
print_stderr(e)
|
|
2861
|
+
if args.debug:
|
|
2862
|
+
traceback.print_exc()
|
|
2863
|
+
sys.exit(1)
|
|
2050
2864
|
|
|
2051
2865
|
|
|
2052
2866
|
def main():
|