scanoss 1.20.5__py3-none-any.whl → 1.22.0__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.
Files changed (48) hide show
  1. protoc_gen_swagger/options/annotations_pb2.py +9 -12
  2. protoc_gen_swagger/options/annotations_pb2_grpc.py +1 -1
  3. protoc_gen_swagger/options/openapiv2_pb2.py +96 -98
  4. protoc_gen_swagger/options/openapiv2_pb2_grpc.py +1 -1
  5. scanoss/__init__.py +1 -1
  6. scanoss/api/common/v2/scanoss_common_pb2.py +20 -18
  7. scanoss/api/common/v2/scanoss_common_pb2_grpc.py +1 -1
  8. scanoss/api/components/v2/scanoss_components_pb2.py +38 -48
  9. scanoss/api/components/v2/scanoss_components_pb2_grpc.py +96 -142
  10. scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +42 -22
  11. scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +185 -75
  12. scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +32 -30
  13. scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +83 -75
  14. scanoss/api/provenance/v2/scanoss_provenance_pb2.py +20 -21
  15. scanoss/api/provenance/v2/scanoss_provenance_pb2_grpc.py +1 -1
  16. scanoss/api/scanning/v2/scanoss_scanning_pb2.py +20 -10
  17. scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +70 -40
  18. scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +18 -22
  19. scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +49 -71
  20. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +27 -37
  21. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +72 -109
  22. scanoss/cli.py +417 -74
  23. scanoss/components.py +5 -3
  24. scanoss/constants.py +12 -0
  25. scanoss/data/build_date.txt +1 -1
  26. scanoss/file_filters.py +272 -57
  27. scanoss/results.py +92 -109
  28. scanoss/scanner.py +25 -20
  29. scanoss/scanners/__init__.py +23 -0
  30. scanoss/scanners/container_scanner.py +474 -0
  31. scanoss/scanners/folder_hasher.py +302 -0
  32. scanoss/scanners/scanner_config.py +73 -0
  33. scanoss/scanners/scanner_hfh.py +172 -0
  34. scanoss/scanoss_settings.py +9 -5
  35. scanoss/scanossapi.py +29 -7
  36. scanoss/scanossbase.py +9 -3
  37. scanoss/scanossgrpc.py +145 -13
  38. scanoss/threadedscanning.py +6 -6
  39. scanoss/utils/abstract_presenter.py +103 -0
  40. scanoss/utils/crc64.py +96 -0
  41. scanoss/utils/simhash.py +198 -0
  42. {scanoss-1.20.5.dist-info → scanoss-1.22.0.dist-info}/METADATA +4 -2
  43. scanoss-1.22.0.dist-info/RECORD +83 -0
  44. {scanoss-1.20.5.dist-info → scanoss-1.22.0.dist-info}/WHEEL +1 -1
  45. scanoss-1.20.5.dist-info/RECORD +0 -74
  46. {scanoss-1.20.5.dist-info → scanoss-1.22.0.dist-info}/entry_points.txt +0 -0
  47. {scanoss-1.20.5.dist-info → scanoss-1.22.0.dist-info/licenses}/LICENSE +0 -0
  48. {scanoss-1.20.5.dist-info → scanoss-1.22.0.dist-info}/top_level.txt +0 -0
scanoss/cli.py CHANGED
@@ -25,12 +25,37 @@ SPDX-License-Identifier: MIT
25
25
  import argparse
26
26
  import os
27
27
  import sys
28
+ from dataclasses import asdict
28
29
  from pathlib import Path
30
+ from typing import List
29
31
 
30
32
  import pypac
31
33
 
34
+ from scanoss.scanners.container_scanner import (
35
+ DEFAULT_SYFT_COMMAND,
36
+ DEFAULT_SYFT_TIMEOUT,
37
+ ContainerScanner,
38
+ create_container_scanner_config_from_args,
39
+ )
40
+ from scanoss.scanners.folder_hasher import (
41
+ FolderHasher,
42
+ create_folder_hasher_config_from_args,
43
+ )
44
+ from scanoss.scanossgrpc import (
45
+ ScanossGrpc,
46
+ ScanossGrpcError,
47
+ create_grpc_config_from_args,
48
+ )
49
+
32
50
  from . import __version__
33
51
  from .components import Components
52
+ from .constants import (
53
+ DEFAULT_POST_SIZE,
54
+ DEFAULT_RETRY,
55
+ DEFAULT_TIMEOUT,
56
+ MIN_TIMEOUT,
57
+ PYTHON_MAJOR_VERSION,
58
+ )
34
59
  from .csvoutput import CsvOutput
35
60
  from .cyclonedx import CycloneDx
36
61
  from .filecount import FileCount
@@ -39,17 +64,16 @@ from .inspection.undeclared_component import UndeclaredComponent
39
64
  from .results import Results
40
65
  from .scancodedeps import ScancodeDeps
41
66
  from .scanner import FAST_WINNOWING, Scanner
67
+ from .scanners.scanner_config import create_scanner_config_from_args
68
+ from .scanners.scanner_hfh import ScannerHFH
42
69
  from .scanoss_settings import ScanossSettings, ScanossSettingsError
43
70
  from .scantype import ScanType
44
71
  from .spdxlite import SpdxLite
45
72
  from .threadeddependencies import SCOPE
46
73
  from .utils.file import validate_json_file
47
74
 
48
- DEFAULT_POST_SIZE = 32
49
- DEFAULT_TIMEOUT = 180
50
- MIN_TIMEOUT_VALUE = 5
51
- DEFAULT_RETRY = 5
52
- PYTHON3_OR_LATER = 3
75
+ HEADER_PARTS_COUNT = 2
76
+
53
77
 
54
78
  def print_stderr(*args, **kwargs):
55
79
  """
@@ -58,7 +82,7 @@ def print_stderr(*args, **kwargs):
58
82
  print(*args, file=sys.stderr, **kwargs)
59
83
 
60
84
 
61
- def setup_args() -> None: # noqa: PLR0915
85
+ def setup_args() -> None: # noqa: PLR0912, PLR0915
62
86
  """
63
87
  Setup all the command line arguments for processing
64
88
  """
@@ -93,14 +117,6 @@ def setup_args() -> None: # noqa: PLR0915
93
117
  p_scan.add_argument('--files', '-e', type=str, nargs='*', help='List of files to scan.')
94
118
  p_scan.add_argument('--identify', '-i', type=str, help='Scan and identify components in SBOM file')
95
119
  p_scan.add_argument('--ignore', '-n', type=str, help='Ignore components specified in the SBOM file')
96
- p_scan.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
97
- p_scan.add_argument(
98
- '--format',
99
- '-f',
100
- type=str,
101
- choices=['plain', 'cyclonedx', 'spdxlite', 'csv'],
102
- help='Result output format (optional - default: plain)',
103
- )
104
120
  p_scan.add_argument(
105
121
  '--threads', '-T', type=int, default=5, help='Number of threads to use while scanning (optional - default 5)'
106
122
  )
@@ -130,15 +146,17 @@ def setup_args() -> None: # noqa: PLR0915
130
146
  help='Timeout (in seconds) for API communication (optional - default 180)',
131
147
  )
132
148
  p_scan.add_argument(
133
- '--retry', '-R', type=int, default=DEFAULT_RETRY,
134
- help='Retry limit for API communication (optional - default 5)'
149
+ '--retry',
150
+ '-R',
151
+ type=int,
152
+ default=DEFAULT_RETRY,
153
+ help='Retry limit for API communication (optional - default 5)',
135
154
  )
136
155
  p_scan.add_argument('--no-wfp-output', action='store_true', help='Skip WFP file generation')
137
156
  p_scan.add_argument('--dependencies', '-D', action='store_true', help='Add Dependency scanning')
138
157
  p_scan.add_argument('--dependencies-only', action='store_true', help='Run Dependency scanning only')
139
158
  p_scan.add_argument(
140
- '--sc-command', type=str,
141
- help='Scancode command and path if required (optional - default scancode).'
159
+ '--sc-command', type=str, help='Scancode command and path if required (optional - default scancode).'
142
160
  )
143
161
  p_scan.add_argument(
144
162
  '--sc-timeout',
@@ -151,18 +169,6 @@ def setup_args() -> None: # noqa: PLR0915
151
169
  )
152
170
  p_scan.add_argument('--dep-scope-inc', '-dsi', type=str, help='Include dependencies with declared scopes')
153
171
  p_scan.add_argument('--dep-scope-exc', '-dse', type=str, help='Exclude dependencies with declared scopes')
154
- p_scan.add_argument(
155
- '--settings',
156
- '-st',
157
- type=str,
158
- help='Settings file to use for scanning (optional - default scanoss.json)',
159
- )
160
- p_scan.add_argument(
161
- '--skip-settings-file',
162
- '-stf',
163
- action='store_true',
164
- help='Skip default settings file (scanoss.json) if it exists',
165
- )
166
172
 
167
173
  # Sub-command: fingerprint
168
174
  p_wfp = subparsers.add_parser(
@@ -180,19 +186,6 @@ def setup_args() -> None: # noqa: PLR0915
180
186
  type=str,
181
187
  help='Fingerprint the file contents supplied via STDIN (optional)',
182
188
  )
183
- p_wfp.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
184
- p_wfp.add_argument(
185
- '--settings',
186
- '-st',
187
- type=str,
188
- help='Settings file to use for fingerprinting (optional - default scanoss.json)',
189
- )
190
- p_wfp.add_argument(
191
- '--skip-settings-file',
192
- '-stf',
193
- action='store_true',
194
- help='Skip default settings file (scanoss.json) if it exists',
195
- )
196
189
 
197
190
  # Sub-command: dependency
198
191
  p_dep = subparsers.add_parser(
@@ -201,9 +194,13 @@ def setup_args() -> None: # noqa: PLR0915
201
194
  description=f'Produce dependency file summary: {__version__}',
202
195
  help='Scan source code for dependencies, but do not decorate them',
203
196
  )
204
- p_dep.set_defaults(func=dependency)
205
- p_dep.add_argument('scan_dir', metavar='FILE/DIR', type=str, nargs='?', help='A file or folder to scan')
206
- p_dep.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
197
+ p_dep.add_argument('scan_loc', metavar='FILE/DIR', type=str, nargs='?', help='A file or folder to scan')
198
+ p_dep.add_argument(
199
+ '--container',
200
+ type=str,
201
+ help='Container image to scan. Supports yourrepo/yourimage:tag, Docker tar, '
202
+ 'OCI tar, OCI directory, SIF Container, or generic filesystem directory.',
203
+ )
207
204
  p_dep.add_argument(
208
205
  '--sc-command', type=str, help='Scancode command and path if required (optional - default scancode).'
209
206
  )
@@ -213,6 +210,40 @@ def setup_args() -> None: # noqa: PLR0915
213
210
  default=600,
214
211
  help='Timeout (in seconds) for scancode to complete (optional - default 600)',
215
212
  )
213
+ p_dep.set_defaults(func=dependency)
214
+
215
+ # Container scan sub-command
216
+ p_cs = subparsers.add_parser(
217
+ 'container-scan',
218
+ aliases=['cs'],
219
+ description=f'Analyse/scan the given container image: {__version__}',
220
+ help='Scan container image',
221
+ )
222
+ p_cs.add_argument(
223
+ 'scan_loc',
224
+ metavar='IMAGE',
225
+ type=str,
226
+ nargs='?',
227
+ help=(
228
+ 'Container image to scan. Supports yourrepo/yourimage:tag, Docker tar, '
229
+ 'OCI tar, OCI directory, SIF Container, or generic filesystem directory.'
230
+ ),
231
+ )
232
+ p_cs.add_argument(
233
+ '--retry',
234
+ '-R',
235
+ type=int,
236
+ default=DEFAULT_RETRY,
237
+ help='Retry limit for API communication (optional - default 5)',
238
+ )
239
+ p_cs.add_argument(
240
+ '--timeout',
241
+ '-M',
242
+ type=int,
243
+ default=DEFAULT_TIMEOUT,
244
+ help='Timeout (in seconds) for API communication (optional - default 180)',
245
+ )
246
+ p_cs.set_defaults(func=container_scan)
216
247
 
217
248
  # Sub-command: file_count
218
249
  p_fc = subparsers.add_parser(
@@ -223,7 +254,6 @@ def setup_args() -> None: # noqa: PLR0915
223
254
  )
224
255
  p_fc.set_defaults(func=file_count)
225
256
  p_fc.add_argument('scan_dir', metavar='DIR', type=str, nargs='?', help='A folder to search')
226
- p_fc.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
227
257
  p_fc.add_argument('--all-hidden', action='store_true', help='Scan all hidden files/folders')
228
258
 
229
259
  # Sub-command: convert
@@ -235,7 +265,6 @@ def setup_args() -> None: # noqa: PLR0915
235
265
  )
236
266
  p_cnv.set_defaults(func=convert)
237
267
  p_cnv.add_argument('--input', '-i', type=str, required=True, help='Input file name')
238
- p_cnv.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
239
268
  p_cnv.add_argument(
240
269
  '--format',
241
270
  '-f',
@@ -331,9 +360,9 @@ def setup_args() -> None: # noqa: PLR0915
331
360
  for p in [c_crypto, c_vulns, c_semgrep, c_provenance]:
332
361
  p.add_argument('--purl', '-p', type=str, nargs='*', help='Package URL - PURL to process.')
333
362
  p.add_argument('--input', '-i', type=str, help='Input file name')
363
+
334
364
  # Common Component sub-command options
335
365
  for p in [c_crypto, c_vulns, c_search, c_versions, c_semgrep, c_provenance]:
336
- p.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
337
366
  p.add_argument(
338
367
  '--timeout',
339
368
  '-M',
@@ -381,7 +410,6 @@ def setup_args() -> None: # noqa: PLR0915
381
410
  p_c_dwnld.add_argument(
382
411
  '--port', '-p', required=False, type=int, default=443, help='Server port number (default: 443).'
383
412
  )
384
- p_c_dwnld.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
385
413
 
386
414
  # Utils Sub-command: utils pac-proxy
387
415
  p_p_proxy = utils_sub.add_parser(
@@ -489,6 +517,114 @@ def setup_args() -> None: # noqa: PLR0915
489
517
  )
490
518
  p_undeclared.set_defaults(func=inspect_undeclared)
491
519
 
520
+ # Sub-command: folder-scan
521
+ p_folder_scan = subparsers.add_parser(
522
+ 'folder-scan',
523
+ aliases=['fs'],
524
+ description=f'Scan the given directory using folder hashing: {__version__}',
525
+ help='Scan the given directory using folder hashing',
526
+ )
527
+ p_folder_scan.add_argument('scan_dir', metavar='FILE/DIR', type=str, nargs='?', help='The root directory to scan')
528
+ p_folder_scan.add_argument(
529
+ '--timeout',
530
+ '-M',
531
+ type=int,
532
+ default=600,
533
+ help='Timeout (in seconds) for API communication (optional - default 600)',
534
+ )
535
+ p_folder_scan.add_argument(
536
+ '--format',
537
+ '-f',
538
+ type=str,
539
+ choices=['json'],
540
+ default='json',
541
+ help='Result output format (optional - default: json)',
542
+ )
543
+ p_folder_scan.add_argument(
544
+ '--best-match',
545
+ '-bm',
546
+ action='store_true',
547
+ default=False,
548
+ help='Enable best match mode (optional - default: False)',
549
+ )
550
+ p_folder_scan.add_argument(
551
+ '--threshold',
552
+ type=int,
553
+ choices=range(1, 101),
554
+ metavar='1-100',
555
+ default=100,
556
+ help='Threshold for result matching (optional - default: 100)',
557
+ )
558
+ p_folder_scan.set_defaults(func=folder_hashing_scan)
559
+
560
+ # Sub-command: folder-hash
561
+ p_folder_hash = subparsers.add_parser(
562
+ 'folder-hash',
563
+ aliases=['fh'],
564
+ description=f'Produce a folder hash for the given directory: {__version__}',
565
+ help='Produce a folder hash for the given directory',
566
+ )
567
+ p_folder_hash.add_argument('scan_dir', metavar='FILE/DIR', type=str, nargs='?', help='A file or folder to scan')
568
+ p_folder_hash.add_argument(
569
+ '--format',
570
+ '-f',
571
+ type=str,
572
+ choices=['json'],
573
+ default='json',
574
+ help='Result output format (optional - default: json)',
575
+ )
576
+ p_folder_hash.set_defaults(func=folder_hash)
577
+
578
+ # Output options
579
+ for p in [
580
+ p_scan,
581
+ p_cs,
582
+ p_wfp,
583
+ p_dep,
584
+ p_fc,
585
+ p_cnv,
586
+ c_crypto,
587
+ c_vulns,
588
+ c_search,
589
+ c_versions,
590
+ c_semgrep,
591
+ c_provenance,
592
+ p_c_dwnld,
593
+ p_folder_scan,
594
+ p_folder_hash,
595
+ ]:
596
+ p.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
597
+
598
+ # Format options
599
+ for p in [p_scan, p_cs]:
600
+ choices = ['plain', 'cyclonedx', 'spdxlite', 'csv']
601
+ if p is p_cs:
602
+ choices.append('raw')
603
+
604
+ p.add_argument(
605
+ '--format',
606
+ '-f',
607
+ type=str,
608
+ choices=choices,
609
+ default='plain',
610
+ help='Result output format (optional - default: plain)',
611
+ )
612
+
613
+ # Scanoss settings options
614
+ for p in [p_folder_scan, p_scan, p_wfp, p_folder_hash]:
615
+ p.add_argument(
616
+ '--settings',
617
+ '-st',
618
+ type=str,
619
+ help='Settings file to use for scanning (optional - default scanoss.json)',
620
+ )
621
+ p.add_argument(
622
+ '--skip-settings-file',
623
+ '-stf',
624
+ action='store_true',
625
+ help='Skip default settings file (scanoss.json) if it exists',
626
+ )
627
+
492
628
  for p in [p_copyleft, p_undeclared]:
493
629
  p.add_argument('-i', '--input', nargs='?', help='Path to results file')
494
630
  p.add_argument(
@@ -503,7 +639,7 @@ def setup_args() -> None: # noqa: PLR0915
503
639
  p.add_argument('-s', '--status', type=str, help='Save summary data into Markdown file')
504
640
 
505
641
  # Global Scan command options
506
- for p in [p_scan]:
642
+ for p in [p_scan, p_cs]:
507
643
  p.add_argument(
508
644
  '--apiurl', type=str, help='SCANOSS API URL (optional - default: https://api.osskb.org/scan/direct)'
509
645
  )
@@ -512,9 +648,9 @@ def setup_args() -> None: # noqa: PLR0915
512
648
  # Global Scan/Fingerprint filter options
513
649
  for p in [p_scan, p_wfp]:
514
650
  p.add_argument('--obfuscate', action='store_true', help='Obfuscate fingerprints')
515
- p.add_argument('--all-extensions', action='store_true', help='Fingerprint all file extensions')
516
- p.add_argument('--all-folders', action='store_true', help='Fingerprint all folders')
517
- p.add_argument('--all-hidden', action='store_true', help='Fingerprint all hidden files/folders')
651
+ p.add_argument('--all-extensions', action='store_true', help='Fingerprint all file extensions/types...')
652
+ p.add_argument('--all-folders', action='store_true', help='Fingerprint all folders...')
653
+ p.add_argument('--all-hidden', action='store_true', help='Fingerprint all hidden files/folders...')
518
654
  p.add_argument('--hpsm', '-H', action='store_true', help='Use High Precision Snippet Matching algorithm.')
519
655
  p.add_argument('--skip-snippets', '-S', action='store_true', help='Skip the generation of snippets')
520
656
  p.add_argument('--skip-extension', '-E', type=str, action='append', help='File Extension to skip.')
@@ -531,7 +667,17 @@ def setup_args() -> None: # noqa: PLR0915
531
667
  p.add_argument('--strip-snippet', '-N', type=str, action='append', help='Strip Snippet ID string from WFP.')
532
668
 
533
669
  # Global Scan/GRPC options
534
- for p in [p_scan, c_crypto, c_vulns, c_search, c_versions, c_semgrep, c_provenance]:
670
+ for p in [
671
+ p_scan,
672
+ c_crypto,
673
+ c_vulns,
674
+ c_search,
675
+ c_versions,
676
+ c_semgrep,
677
+ c_provenance,
678
+ p_folder_scan,
679
+ p_cs,
680
+ ]:
535
681
  p.add_argument(
536
682
  '--key', '-k', type=str, help='SCANOSS API Key token (optional - not required for default OSSKB URL)'
537
683
  )
@@ -557,7 +703,7 @@ def setup_args() -> None: # noqa: PLR0915
557
703
  )
558
704
 
559
705
  # Global GRPC options
560
- for p in [p_scan, c_crypto, c_vulns, c_search, c_versions, c_semgrep, c_provenance]:
706
+ for p in [p_scan, c_crypto, c_vulns, c_search, c_versions, c_semgrep, c_provenance, p_folder_scan, p_cs]:
561
707
  p.add_argument(
562
708
  '--api2url', type=str, help='SCANOSS gRPC API 2.0 URL (optional - default: https://api.osskb.org)'
563
709
  )
@@ -567,6 +713,28 @@ def setup_args() -> None: # noqa: PLR0915
567
713
  help='GRPC Proxy URL to use for connections (optional). '
568
714
  'Can also use the environment variable "grcp_proxy=<ip>:<port>"',
569
715
  )
716
+ p.add_argument(
717
+ '--header',
718
+ '-hdr',
719
+ action='append', # This allows multiple -H flags
720
+ type=str,
721
+ help='Headers to be sent on request (e.g., -hdr "Name: Value") - can be used multiple times',
722
+ )
723
+
724
+ # Syft options
725
+ for p in [p_cs, p_dep]:
726
+ p.add_argument(
727
+ '--syft-command',
728
+ type=str,
729
+ help='Syft command and path if required (optional - default syft).',
730
+ default=DEFAULT_SYFT_COMMAND,
731
+ )
732
+ p.add_argument(
733
+ '--syft-timeout',
734
+ type=int,
735
+ default=DEFAULT_SYFT_TIMEOUT,
736
+ help='Timeout (in seconds) for syft to complete (optional - default 600)',
737
+ )
570
738
 
571
739
  # Help/Trace command options
572
740
  for p in [
@@ -586,7 +754,10 @@ def setup_args() -> None: # noqa: PLR0915
586
754
  p_results,
587
755
  p_undeclared,
588
756
  p_copyleft,
589
- c_provenance
757
+ c_provenance,
758
+ p_folder_scan,
759
+ p_folder_hash,
760
+ p_cs,
590
761
  ]:
591
762
  p.add_argument('--debug', '-d', action='store_true', help='Enable debug messages')
592
763
  p.add_argument('--trace', '-t', action='store_true', help='Enable trace messages, including API posts')
@@ -599,8 +770,7 @@ def setup_args() -> None: # noqa: PLR0915
599
770
  if not args.subparser:
600
771
  parser.print_help() # No sub command subcommand, print general help
601
772
  sys.exit(1)
602
- elif (
603
- args.subparser in {'utils', 'ut', 'component', 'comp', 'inspect', 'insp', 'ins'} and not args.subparsercmd):
773
+ elif (args.subparser in ('utils', 'ut', 'component', 'comp', 'inspect', 'insp', 'ins')) and not args.subparsercmd:
604
774
  parser.parse_args([args.subparser, '--help']) # Force utils helps to be displayed
605
775
  sys.exit(1)
606
776
  args.func(parser, args) # Execute the function associated with the sub-command
@@ -684,7 +854,7 @@ def wfp(parser, args):
684
854
  if not args.skip_settings_file:
685
855
  scan_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
686
856
  try:
687
- scan_settings.load_json_file(args.settings)
857
+ scan_settings.load_json_file(args.settings, args.scan_dir)
688
858
  except ScanossSettingsError as e:
689
859
  print_stderr(f'Error: {e}')
690
860
  sys.exit(1)
@@ -759,7 +929,7 @@ def get_scan_options(args):
759
929
  return scan_options
760
930
 
761
931
 
762
- def scan(parser, args): # noqa: PLR0912, PLR0915
932
+ def scan(parser, args): # noqa: PLR0912, PLR0915
763
933
  """
764
934
  Run the "scan" sub-command
765
935
  Parameters
@@ -854,7 +1024,7 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
854
1024
  if flags:
855
1025
  print_stderr(f'Using flags {flags}...')
856
1026
  elif not args.quiet:
857
- if args.timeout < MIN_TIMEOUT_VALUE:
1027
+ if args.timeout < MIN_TIMEOUT:
858
1028
  print_stderr(f'POST timeout (--timeout) too small: {args.timeout}. Reverting to default.')
859
1029
  if args.retry < 0:
860
1030
  print_stderr(f'POST retry (--retry) too small: {args.retry}. Reverting to default.')
@@ -903,6 +1073,7 @@ def scan(parser, args): # noqa: PLR0912, PLR0915
903
1073
  strip_hpsm_ids=args.strip_hpsm,
904
1074
  strip_snippet_ids=args.strip_snippet,
905
1075
  scan_settings=scan_settings,
1076
+ req_headers=process_req_headers(args.header),
906
1077
  )
907
1078
  if args.wfp:
908
1079
  if not scanner.is_file_or_snippet_scan():
@@ -972,12 +1143,18 @@ def dependency(parser, args):
972
1143
  args: Namespace
973
1144
  Parsed arguments
974
1145
  """
975
- if not args.scan_dir:
976
- print_stderr('Please specify a file/folder')
1146
+ if not args.scan_loc and not args.container:
1147
+ print_stderr('Please specify a file/folder or container image')
977
1148
  parser.parse_args([args.subparser, '-h'])
978
1149
  sys.exit(1)
979
- if not os.path.exists(args.scan_dir):
980
- print_stderr(f'Error: File or folder specified does not exist: {args.scan_dir}.')
1150
+
1151
+ # Workaround to return syft scan results converted to our dependency output format
1152
+ if args.container:
1153
+ args.scan_loc = args.container
1154
+ return container_scan(parser, args, only_interim_results=True)
1155
+
1156
+ if not os.path.exists(args.scan_loc):
1157
+ print_stderr(f'Error: File or folder specified does not exist: {args.scan_loc}.')
981
1158
  sys.exit(1)
982
1159
  scan_output: str = None
983
1160
  if args.output:
@@ -987,7 +1164,7 @@ def dependency(parser, args):
987
1164
  sc_deps = ScancodeDeps(
988
1165
  debug=args.debug, quiet=args.quiet, trace=args.trace, sc_command=args.sc_command, timeout=args.sc_timeout
989
1166
  )
990
- if not sc_deps.get_dependencies(what_to_scan=args.scan_dir, result_output=scan_output):
1167
+ if not sc_deps.get_dependencies(what_to_scan=args.scan_loc, result_output=scan_output):
991
1168
  sys.exit(1)
992
1169
 
993
1170
 
@@ -1114,7 +1291,7 @@ def utils_certloc(*_):
1114
1291
  print(f'CA Cert File: {certifi.where()}')
1115
1292
 
1116
1293
 
1117
- def utils_cert_download(_, args): # pylint: disable=PLR0912 # noqa: PLR0912
1294
+ def utils_cert_download(_, args): # pylint: disable=PLR0912 # noqa: PLR0912
1118
1295
  """
1119
1296
  Run the "utils cert-download" sub-command
1120
1297
  :param _: ignore/unused
@@ -1141,13 +1318,14 @@ def utils_cert_download(_, args): # pylint: disable=PLR0912 # noqa: PLR0912
1141
1318
  certs = conn.get_peer_cert_chain()
1142
1319
  for index, cert in enumerate(certs):
1143
1320
  cert_components = dict(cert.get_subject().get_components())
1144
- if sys.version_info[0] >= PYTHON3_OR_LATER:
1321
+ if sys.version_info[0] >= PYTHON_MAJOR_VERSION:
1145
1322
  cn = cert_components.get(b'CN')
1146
1323
  else:
1324
+ # Fallback for Python versions less than PYTHON_MAJOR_VERSION
1147
1325
  cn = cert_components.get('CN')
1148
1326
  if not args.quiet:
1149
1327
  print_stderr(f'Certificate {index} - CN: {cn}')
1150
- if sys.version_info[0] >= PYTHON3_OR_LATER:
1328
+ if sys.version_info[0] >= PYTHON_MAJOR_VERSION:
1151
1329
  print(
1152
1330
  (crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf-8')).strip(), file=file
1153
1331
  ) # Print the downloaded PEM certificate
@@ -1196,7 +1374,7 @@ def get_pac_file(pac: str):
1196
1374
  if pac == 'auto':
1197
1375
  pac_file = pypac.get_pac() # try to determine the PAC file
1198
1376
  elif pac.startswith('file://'):
1199
- pac_local = pac.strip('file://')
1377
+ pac_local = pac[7:] # Remove 'file://' prefix (7 characters)
1200
1378
  if not os.path.exists(pac_local):
1201
1379
  print_stderr(f'Error: PAC file does not exist: {pac_local}.')
1202
1380
  sys.exit(1)
@@ -1228,6 +1406,7 @@ def comp_crypto(parser, args):
1228
1406
  print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
1229
1407
  sys.exit(1)
1230
1408
  pac_file = get_pac_file(args.pac)
1409
+
1231
1410
  comps = Components(
1232
1411
  debug=args.debug,
1233
1412
  trace=args.trace,
@@ -1239,6 +1418,7 @@ def comp_crypto(parser, args):
1239
1418
  grpc_proxy=args.grpc_proxy,
1240
1419
  pac=pac_file,
1241
1420
  timeout=args.timeout,
1421
+ req_headers=process_req_headers(args.header),
1242
1422
  )
1243
1423
  if not comps.get_crypto_details(args.input, args.purl, args.output):
1244
1424
  sys.exit(1)
@@ -1273,6 +1453,7 @@ def comp_vulns(parser, args):
1273
1453
  grpc_proxy=args.grpc_proxy,
1274
1454
  pac=pac_file,
1275
1455
  timeout=args.timeout,
1456
+ req_headers=process_req_headers(args.header),
1276
1457
  )
1277
1458
  if not comps.get_vulnerabilities(args.input, args.purl, args.output):
1278
1459
  sys.exit(1)
@@ -1307,6 +1488,7 @@ def comp_semgrep(parser, args):
1307
1488
  grpc_proxy=args.grpc_proxy,
1308
1489
  pac=pac_file,
1309
1490
  timeout=args.timeout,
1491
+ req_headers=process_req_headers(args.header),
1310
1492
  )
1311
1493
  if not comps.get_semgrep_details(args.input, args.purl, args.output):
1312
1494
  sys.exit(1)
@@ -1344,6 +1526,7 @@ def comp_search(parser, args):
1344
1526
  grpc_proxy=args.grpc_proxy,
1345
1527
  pac=pac_file,
1346
1528
  timeout=args.timeout,
1529
+ req_headers=process_req_headers(args.header),
1347
1530
  )
1348
1531
  if not comps.search_components(
1349
1532
  args.output,
@@ -1388,10 +1571,12 @@ def comp_versions(parser, args):
1388
1571
  grpc_proxy=args.grpc_proxy,
1389
1572
  pac=pac_file,
1390
1573
  timeout=args.timeout,
1574
+ req_headers=process_req_headers(args.header),
1391
1575
  )
1392
1576
  if not comps.get_component_versions(args.output, json_file=args.input, purl=args.purl, limit=args.limit):
1393
1577
  sys.exit(1)
1394
1578
 
1579
+
1395
1580
  def comp_provenance(parser, args):
1396
1581
  """
1397
1582
  Run the "component semgrep" sub-command
@@ -1410,12 +1595,23 @@ def comp_provenance(parser, args):
1410
1595
  print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
1411
1596
  sys.exit(1)
1412
1597
  pac_file = get_pac_file(args.pac)
1413
- comps = Components(debug=args.debug, trace=args.trace, quiet=args.quiet, grpc_url=args.api2url, api_key=args.key,
1414
- ca_cert=args.ca_cert, proxy=args.proxy, grpc_proxy=args.grpc_proxy, pac=pac_file,
1415
- timeout=args.timeout)
1598
+ comps = Components(
1599
+ debug=args.debug,
1600
+ trace=args.trace,
1601
+ quiet=args.quiet,
1602
+ grpc_url=args.api2url,
1603
+ api_key=args.key,
1604
+ ca_cert=args.ca_cert,
1605
+ proxy=args.proxy,
1606
+ grpc_proxy=args.grpc_proxy,
1607
+ pac=pac_file,
1608
+ timeout=args.timeout,
1609
+ req_headers=process_req_headers(args.header),
1610
+ )
1416
1611
  if not comps.get_provenance_details(args.input, args.purl, args.output):
1417
1612
  sys.exit(1)
1418
1613
 
1614
+
1419
1615
  def results(parser, args):
1420
1616
  """
1421
1617
  Run the "results" sub-command
@@ -1456,6 +1652,153 @@ def results(parser, args):
1456
1652
  results.apply_filters().present()
1457
1653
 
1458
1654
 
1655
+ def process_req_headers(headers_array: List[str]) -> dict:
1656
+ """
1657
+ Process a list of header strings in the format "Name: Value" into a dictionary.
1658
+
1659
+ Args:
1660
+ headers_array (list): List of header strings from command line args
1661
+
1662
+ Returns:
1663
+ dict: Dictionary of header name-value pairs
1664
+ """
1665
+ # Check if headers_array is empty
1666
+ if not headers_array:
1667
+ # Array is empty
1668
+ return {}
1669
+
1670
+ dict_headers = {}
1671
+ for header_str in headers_array:
1672
+ # Split each "Name: Value" header
1673
+ parts = header_str.split(':', 1)
1674
+ if len(parts) == HEADER_PARTS_COUNT:
1675
+ name = parts[0].strip()
1676
+ value = parts[1].strip()
1677
+ dict_headers[name] = value
1678
+ return dict_headers
1679
+
1680
+
1681
+ def folder_hashing_scan(parser, args):
1682
+ """Run the "folder-scan" sub-command
1683
+
1684
+ Args:
1685
+ parser (ArgumentParser): command line parser object
1686
+ args (Namespace): Parsed arguments
1687
+ """
1688
+ try:
1689
+ if not args.scan_dir:
1690
+ print_stderr('ERROR: Please specify a directory to scan')
1691
+ parser.parse_args([args.subparser, '-h'])
1692
+ sys.exit(1)
1693
+
1694
+ if not os.path.exists(args.scan_dir) or not os.path.isdir(args.scan_dir):
1695
+ print_stderr(f'ERROR: The specified directory {args.scan_dir} does not exist')
1696
+ sys.exit(1)
1697
+
1698
+ scanner_config = create_scanner_config_from_args(args)
1699
+ scanoss_settings = get_scanoss_settings_from_args(args)
1700
+ grpc_config = create_grpc_config_from_args(args)
1701
+
1702
+ client = ScanossGrpc(**asdict(grpc_config))
1703
+
1704
+ scanner = ScannerHFH(
1705
+ scan_dir=args.scan_dir,
1706
+ config=scanner_config,
1707
+ client=client,
1708
+ scanoss_settings=scanoss_settings,
1709
+ )
1710
+
1711
+ scanner.best_match = args.best_match
1712
+ scanner.threshold = args.threshold
1713
+
1714
+ scanner.scan()
1715
+ scanner.present(output_file=args.output, output_format=args.format)
1716
+ except ScanossGrpcError as e:
1717
+ print_stderr(f'ERROR: {e}')
1718
+ sys.exit(1)
1719
+
1720
+
1721
+ def folder_hash(parser, args):
1722
+ """Run the "folder-hash" sub-command
1723
+
1724
+ Args:
1725
+ parser (ArgumentParser): command line parser object
1726
+ args (Namespace): Parsed arguments
1727
+ """
1728
+ try:
1729
+ if not args.scan_dir:
1730
+ print_stderr('ERROR: Please specify a directory to scan')
1731
+ parser.parse_args([args.subparser, '-h'])
1732
+ sys.exit(1)
1733
+
1734
+ if not os.path.exists(args.scan_dir) or not os.path.isdir(args.scan_dir):
1735
+ print_stderr(f'ERROR: The specified directory {args.scan_dir} does not exist')
1736
+ sys.exit(1)
1737
+
1738
+ folder_hasher_config = create_folder_hasher_config_from_args(args)
1739
+ scanoss_settings = get_scanoss_settings_from_args(args)
1740
+
1741
+ folder_hasher = FolderHasher(
1742
+ scan_dir=args.scan_dir,
1743
+ config=folder_hasher_config,
1744
+ scanoss_settings=scanoss_settings,
1745
+ )
1746
+
1747
+ folder_hasher.hash_directory(args.scan_dir)
1748
+ folder_hasher.present(output_file=args.output, output_format=args.format)
1749
+ except Exception as e:
1750
+ print_stderr(f'ERROR: {e}')
1751
+ sys.exit(1)
1752
+
1753
+
1754
+ def container_scan(parser, args, only_interim_results: bool = False):
1755
+ """
1756
+ Run the "container-scan" sub-command
1757
+ Parameters
1758
+ ----------
1759
+ parser: ArgumentParser
1760
+ command line parser object
1761
+ args: Namespace
1762
+ Parsed arguments
1763
+ """
1764
+ if not args.scan_loc:
1765
+ print_stderr(
1766
+ 'Please specify a container image, Docker tar, OCI tar, OCI directory, SIF Container, or directory to scan'
1767
+ )
1768
+ parser.parse_args([args.subparser, '-h'])
1769
+ sys.exit(1)
1770
+
1771
+ try:
1772
+ config = create_container_scanner_config_from_args(args)
1773
+ config.only_interim_results = only_interim_results
1774
+ container_scanner = ContainerScanner(
1775
+ config=config,
1776
+ what_to_scan=args.scan_loc,
1777
+ )
1778
+
1779
+ container_scanner.scan()
1780
+ if only_interim_results:
1781
+ container_scanner.present(output_file=config.output, output_format='raw')
1782
+ else:
1783
+ container_scanner.decorate_scan_results_with_dependencies()
1784
+ container_scanner.present(output_file=config.output, output_format=config.format)
1785
+ except Exception as e:
1786
+ print_stderr(f'ERROR: {e}')
1787
+ sys.exit(1)
1788
+
1789
+
1790
+ def get_scanoss_settings_from_args(args):
1791
+ scanoss_settings = None
1792
+ if not args.skip_settings_file:
1793
+ scanoss_settings = ScanossSettings(debug=args.debug, trace=args.trace, quiet=args.quiet)
1794
+ try:
1795
+ scanoss_settings.load_json_file(args.settings, args.scan_dir).set_file_type('new').set_scan_type('identify')
1796
+ except ScanossSettingsError as e:
1797
+ print_stderr(f'Error: {e}')
1798
+ sys.exit(1)
1799
+ return scanoss_settings
1800
+
1801
+
1459
1802
  def main():
1460
1803
  """
1461
1804
  Run the ScanOSS CLI