cycode 1.10.2.dev1__tar.gz → 1.10.3.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/PKG-INFO +3 -1
  2. cycode-1.10.3.dev1/cycode/__init__.py +1 -0
  3. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/auth/auth_command.py +22 -3
  4. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/configure/configure_command.py +3 -0
  5. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/ignore/ignore_command.py +3 -0
  6. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/report/report_command.py +2 -0
  7. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/report/sbom/path/path_command.py +3 -0
  8. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/report/sbom/repository_url/repository_url_command.py +3 -0
  9. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/report/sbom/sbom_command.py +3 -0
  10. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/commit_history/commit_history_command.py +3 -0
  11. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/path/path_command.py +3 -0
  12. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/pre_commit/pre_commit_command.py +3 -0
  13. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/pre_receive/pre_receive_command.py +3 -0
  14. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/repository/repository_command.py +3 -0
  15. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/scan_ci/scan_ci_command.py +2 -0
  16. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/scan_command.py +5 -0
  17. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/consts.py +9 -0
  18. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/exceptions/handle_report_sbom_errors.py +3 -0
  19. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/exceptions/handle_scan_errors.py +6 -4
  20. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/main.py +4 -0
  21. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/models.py +1 -0
  22. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/printers/json_printer.py +1 -1
  23. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/printers/text_printer.py +7 -0
  24. cycode-1.10.3.dev1/cycode/cli/sentry.py +99 -0
  25. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/user_settings/credentials_manager.py +5 -0
  26. cycode-1.10.3.dev1/cycode/cli/utils/jwt_utils.py +14 -0
  27. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/headers.py +5 -2
  28. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/pyproject.toml +3 -1
  29. cycode-1.10.2.dev1/cycode/__init__.py +0 -1
  30. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/README.md +0 -0
  31. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/__init__.py +0 -0
  32. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/__init__.py +0 -0
  33. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/auth/__init__.py +0 -0
  34. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/auth/auth_manager.py +0 -0
  35. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/configure/__init__.py +0 -0
  36. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/ignore/__init__.py +0 -0
  37. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/main_cli.py +0 -0
  38. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/report/__init__.py +0 -0
  39. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/report/sbom/__init__.py +0 -0
  40. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/report/sbom/common.py +0 -0
  41. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/report/sbom/path/__init__.py +0 -0
  42. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/report/sbom/repository_url/__init__.py +0 -0
  43. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/report/sbom/sbom_report_file.py +0 -0
  44. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/__init__.py +0 -0
  45. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/code_scanner.py +0 -0
  46. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/commit_history/__init__.py +0 -0
  47. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/path/__init__.py +0 -0
  48. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/pre_commit/__init__.py +0 -0
  49. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/pre_receive/__init__.py +0 -0
  50. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/repository/__init__.py +0 -0
  51. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/scan_ci/__init__.py +0 -0
  52. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/scan/scan_ci/ci_integrations.py +0 -0
  53. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/version/__init__.py +0 -0
  54. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/commands/version/version_command.py +0 -0
  55. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/config.py +0 -0
  56. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/config.yaml +0 -0
  57. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/exceptions/__init__.py +0 -0
  58. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/exceptions/custom_exceptions.py +0 -0
  59. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/__init__.py +0 -0
  60. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/excluder.py +0 -0
  61. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/iac/__init__.py +0 -0
  62. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
  63. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/models/__init__.py +0 -0
  64. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
  65. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/path_documents.py +0 -0
  66. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/repository_documents.py +0 -0
  67. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/sca/__init__.py +0 -0
  68. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/sca/maven/__init__.py +0 -0
  69. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/sca/maven/base_restore_maven_dependencies.py +0 -0
  70. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
  71. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
  72. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/sca/sca_code_scanner.py +0 -0
  73. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/files_collector/zip_documents.py +0 -0
  74. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/printers/__init__.py +0 -0
  75. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/printers/console_printer.py +0 -0
  76. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/printers/printer_base.py +0 -0
  77. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/printers/tables/__init__.py +0 -0
  78. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
  79. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/printers/tables/table.py +0 -0
  80. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/printers/tables/table_models.py +0 -0
  81. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/printers/tables/table_printer.py +0 -0
  82. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/printers/tables/table_printer_base.py +0 -0
  83. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/user_settings/__init__.py +0 -0
  84. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/user_settings/base_file_manager.py +0 -0
  85. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/user_settings/config_file_manager.py +0 -0
  86. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/user_settings/configuration_manager.py +0 -0
  87. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/user_settings/jwt_creator.py +0 -0
  88. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/utils/__init__.py +0 -0
  89. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/utils/enum_utils.py +0 -0
  90. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/utils/get_api_client.py +0 -0
  91. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/utils/git_proxy.py +0 -0
  92. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/utils/path_utils.py +0 -0
  93. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/utils/progress_bar.py +0 -0
  94. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/utils/scan_batch.py +0 -0
  95. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/utils/scan_utils.py +0 -0
  96. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/utils/shell_executor.py +0 -0
  97. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/utils/string_utils.py +0 -0
  98. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/utils/task_timer.py +0 -0
  99. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cli/utils/yaml_utils.py +0 -0
  100. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/__init__.py +0 -0
  101. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/auth_client.py +0 -0
  102. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/client_creator.py +0 -0
  103. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/config.py +0 -0
  104. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/config.yaml +0 -0
  105. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/config_dev.py +0 -0
  106. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/cycode_client.py +0 -0
  107. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/cycode_client_base.py +0 -0
  108. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/cycode_dev_based_client.py +0 -0
  109. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/cycode_token_based_client.py +0 -0
  110. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/models.py +0 -0
  111. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/report_client.py +0 -0
  112. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/scan_client.py +0 -0
  113. {cycode-1.10.2.dev1 → cycode-1.10.3.dev1}/cycode/cyclient/scan_config_base.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cycode
3
- Version: 1.10.2.dev1
3
+ Version: 1.10.3.dev1
4
4
  Summary: Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning.
5
5
  Home-page: https://github.com/cycodehq/cycode-cli
6
6
  License: MIT
@@ -29,8 +29,10 @@ Requires-Dist: colorama (>=0.4.3,<0.5.0)
29
29
  Requires-Dist: gitpython (>=3.1.30,<3.2.0)
30
30
  Requires-Dist: marshmallow (>=3.15.0,<3.21.0)
31
31
  Requires-Dist: pathspec (>=0.11.1,<0.12.0)
32
+ Requires-Dist: pyjwt (>=2.8.0,<3.0)
32
33
  Requires-Dist: pyyaml (>=6.0,<7.0)
33
34
  Requires-Dist: requests (>=2.24,<3.0)
35
+ Requires-Dist: sentry-sdk (>=2.8.0,<3.0)
34
36
  Requires-Dist: texttable (>=1.6.7,<1.8.0)
35
37
  Requires-Dist: urllib3 (==1.26.18)
36
38
  Project-URL: Repository, https://github.com/cycodehq/cycode-cli
@@ -0,0 +1 @@
1
+ __version__ = '1.10.3.dev1' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
@@ -4,7 +4,9 @@ from cycode.cli.commands.auth.auth_manager import AuthManager
4
4
  from cycode.cli.exceptions.custom_exceptions import AuthProcessError, HttpUnauthorizedError, NetworkError
5
5
  from cycode.cli.models import CliError, CliErrors, CliResult
6
6
  from cycode.cli.printers import ConsolePrinter
7
+ from cycode.cli.sentry import add_breadcrumb, capture_exception
7
8
  from cycode.cli.user_settings.credentials_manager import CredentialsManager
9
+ from cycode.cli.utils.jwt_utils import get_user_and_tenant_ids_from_access_token
8
10
  from cycode.cyclient import logger
9
11
  from cycode.cyclient.cycode_token_based_client import CycodeTokenBasedClient
10
12
 
@@ -15,6 +17,8 @@ from cycode.cyclient.cycode_token_based_client import CycodeTokenBasedClient
15
17
  @click.pass_context
16
18
  def auth_command(context: click.Context) -> None:
17
19
  """Authenticates your machine."""
20
+ add_breadcrumb('auth')
21
+
18
22
  if context.invoked_subcommand is not None:
19
23
  # if it is a subcommand, do nothing
20
24
  return
@@ -37,9 +41,10 @@ def auth_command(context: click.Context) -> None:
37
41
  @click.pass_context
38
42
  def authorization_check(context: click.Context) -> None:
39
43
  """Validates that your Cycode account has permission to work with the CLI."""
44
+ add_breadcrumb('check')
45
+
40
46
  printer = ConsolePrinter(context)
41
47
 
42
- passed_auth_check_res = CliResult(success=True, message='Cycode authentication verified')
43
48
  failed_auth_check_res = CliResult(success=False, message='Cycode authentication failed')
44
49
 
45
50
  client_id, client_secret = CredentialsManager().get_credentials()
@@ -48,9 +53,21 @@ def authorization_check(context: click.Context) -> None:
48
53
  return
49
54
 
50
55
  try:
51
- if CycodeTokenBasedClient(client_id, client_secret).get_access_token():
52
- printer.print_result(passed_auth_check_res)
56
+ access_token = CycodeTokenBasedClient(client_id, client_secret).get_access_token()
57
+ if not access_token:
58
+ printer.print_result(failed_auth_check_res)
53
59
  return
60
+
61
+ user_id, tenant_id = get_user_and_tenant_ids_from_access_token(access_token)
62
+ printer.print_result(
63
+ CliResult(
64
+ success=True,
65
+ message='Cycode authentication verified',
66
+ data={'user_id': user_id, 'tenant_id': tenant_id},
67
+ )
68
+ )
69
+
70
+ return
54
71
  except (NetworkError, HttpUnauthorizedError):
55
72
  ConsolePrinter(context).print_exception()
56
73
 
@@ -78,4 +95,6 @@ def _handle_exception(context: click.Context, e: Exception) -> None:
78
95
  if isinstance(e, click.ClickException):
79
96
  raise e
80
97
 
98
+ capture_exception(e)
99
+
81
100
  raise click.ClickException(str(e))
@@ -3,6 +3,7 @@ from typing import Optional
3
3
  import click
4
4
 
5
5
  from cycode.cli import config, consts
6
+ from cycode.cli.sentry import add_breadcrumb
6
7
  from cycode.cli.user_settings.configuration_manager import ConfigurationManager
7
8
  from cycode.cli.user_settings.credentials_manager import CredentialsManager
8
9
  from cycode.cli.utils.string_utils import obfuscate_text
@@ -26,6 +27,8 @@ _CONFIGURATION_MANAGER = ConfigurationManager()
26
27
  @click.command(short_help='Initial command to configure your CLI client authentication.')
27
28
  def configure_command() -> None:
28
29
  """Configure your CLI client authentication manually."""
30
+ add_breadcrumb('configure')
31
+
29
32
  global_config_manager = _CONFIGURATION_MANAGER.global_config_file_manager
30
33
 
31
34
  current_api_url = global_config_manager.get_api_url()
@@ -5,6 +5,7 @@ import click
5
5
 
6
6
  from cycode.cli import consts
7
7
  from cycode.cli.config import config, configuration_manager
8
+ from cycode.cli.sentry import add_breadcrumb
8
9
  from cycode.cli.utils.path_utils import get_absolute_path
9
10
  from cycode.cli.utils.string_utils import hash_string_to_sha256
10
11
  from cycode.cyclient import logger
@@ -67,6 +68,8 @@ def ignore_command(
67
68
  by_value: str, by_sha: str, by_path: str, by_rule: str, by_package: str, scan_type: str, is_global: bool
68
69
  ) -> None:
69
70
  """Ignores a specific value, path or rule ID."""
71
+ add_breadcrumb('ignore')
72
+
70
73
  if not by_value and not by_sha and not by_path and not by_rule and not by_package:
71
74
  raise click.ClickException('ignore by type is missing')
72
75
 
@@ -1,6 +1,7 @@
1
1
  import click
2
2
 
3
3
  from cycode.cli.commands.report.sbom.sbom_command import sbom_command
4
+ from cycode.cli.sentry import add_breadcrumb
4
5
  from cycode.cli.utils.progress_bar import SBOM_REPORT_PROGRESS_BAR_SECTIONS, get_progress_bar
5
6
 
6
7
 
@@ -15,5 +16,6 @@ def report_command(
15
16
  context: click.Context,
16
17
  ) -> int:
17
18
  """Generate report."""
19
+ add_breadcrumb('report')
18
20
  context.obj['progress_bar'] = get_progress_bar(hidden=False, sections=SBOM_REPORT_PROGRESS_BAR_SECTIONS)
19
21
  return 1
@@ -8,6 +8,7 @@ from cycode.cli.exceptions.handle_report_sbom_errors import handle_report_except
8
8
  from cycode.cli.files_collector.path_documents import get_relevant_documents
9
9
  from cycode.cli.files_collector.sca.sca_code_scanner import perform_pre_scan_documents_actions
10
10
  from cycode.cli.files_collector.zip_documents import zip_documents
11
+ from cycode.cli.sentry import add_breadcrumb
11
12
  from cycode.cli.utils.get_api_client import get_report_cycode_client
12
13
  from cycode.cli.utils.progress_bar import SbomReportProgressBarSection
13
14
 
@@ -16,6 +17,8 @@ from cycode.cli.utils.progress_bar import SbomReportProgressBarSection
16
17
  @click.argument('path', nargs=1, type=click.Path(exists=True, resolve_path=True), required=True)
17
18
  @click.pass_context
18
19
  def path_command(context: click.Context, path: str) -> None:
20
+ add_breadcrumb('path')
21
+
19
22
  client = get_report_cycode_client()
20
23
  report_parameters = context.obj['report_parameters']
21
24
  output_format = report_parameters.output_format
@@ -4,6 +4,7 @@ import click
4
4
 
5
5
  from cycode.cli.commands.report.sbom.common import create_sbom_report, send_report_feedback
6
6
  from cycode.cli.exceptions.handle_report_sbom_errors import handle_report_exception
7
+ from cycode.cli.sentry import add_breadcrumb
7
8
  from cycode.cli.utils.get_api_client import get_report_cycode_client
8
9
  from cycode.cli.utils.progress_bar import SbomReportProgressBarSection
9
10
 
@@ -12,6 +13,8 @@ from cycode.cli.utils.progress_bar import SbomReportProgressBarSection
12
13
  @click.argument('uri', nargs=1, type=str, required=True)
13
14
  @click.pass_context
14
15
  def repository_url_command(context: click.Context, uri: str) -> None:
16
+ add_breadcrumb('repository_url')
17
+
15
18
  progress_bar = context.obj['progress_bar']
16
19
  progress_bar.start()
17
20
  progress_bar.set_section_length(SbomReportProgressBarSection.PREPARE_LOCAL_FILES)
@@ -6,6 +6,7 @@ import click
6
6
  from cycode.cli.commands.report.sbom.path.path_command import path_command
7
7
  from cycode.cli.commands.report.sbom.repository_url.repository_url_command import repository_url_command
8
8
  from cycode.cli.config import config
9
+ from cycode.cli.sentry import add_breadcrumb
9
10
  from cycode.cyclient.report_client import ReportParameters
10
11
 
11
12
 
@@ -64,6 +65,8 @@ def sbom_command(
64
65
  include_dev_dependencies: bool,
65
66
  ) -> int:
66
67
  """Generate SBOM report."""
68
+ add_breadcrumb('sbom')
69
+
67
70
  sbom_format_parts = format.split('-')
68
71
  if len(sbom_format_parts) != 2:
69
72
  raise click.ClickException('Invalid SBOM format.')
@@ -2,6 +2,7 @@ import click
2
2
 
3
3
  from cycode.cli.commands.scan.code_scanner import scan_commit_range
4
4
  from cycode.cli.exceptions.handle_scan_errors import handle_scan_exception
5
+ from cycode.cli.sentry import add_breadcrumb
5
6
  from cycode.cyclient import logger
6
7
 
7
8
 
@@ -18,6 +19,8 @@ from cycode.cyclient import logger
18
19
  @click.pass_context
19
20
  def commit_history_command(context: click.Context, path: str, commit_range: str) -> None:
20
21
  try:
22
+ add_breadcrumb('commit_history')
23
+
21
24
  logger.debug('Starting commit history scan process, %s', {'path': path, 'commit_range': commit_range})
22
25
  scan_commit_range(context, path=path, commit_range=commit_range)
23
26
  except Exception as e:
@@ -3,6 +3,7 @@ from typing import Tuple
3
3
  import click
4
4
 
5
5
  from cycode.cli.commands.scan.code_scanner import scan_disk_files
6
+ from cycode.cli.sentry import add_breadcrumb
6
7
  from cycode.cyclient import logger
7
8
 
8
9
 
@@ -10,6 +11,8 @@ from cycode.cyclient import logger
10
11
  @click.argument('paths', nargs=-1, type=click.Path(exists=True, resolve_path=True), required=True)
11
12
  @click.pass_context
12
13
  def path_command(context: click.Context, paths: Tuple[str]) -> None:
14
+ add_breadcrumb('path')
15
+
13
16
  progress_bar = context.obj['progress_bar']
14
17
  progress_bar.start()
15
18
 
@@ -11,6 +11,7 @@ from cycode.cli.files_collector.repository_documents import (
11
11
  get_diff_file_path,
12
12
  )
13
13
  from cycode.cli.models import Document
14
+ from cycode.cli.sentry import add_breadcrumb
14
15
  from cycode.cli.utils.git_proxy import git_proxy
15
16
  from cycode.cli.utils.path_utils import (
16
17
  get_path_by_os,
@@ -22,6 +23,8 @@ from cycode.cli.utils.progress_bar import ScanProgressBarSection
22
23
  @click.argument('ignored_args', nargs=-1, type=click.UNPROCESSED)
23
24
  @click.pass_context
24
25
  def pre_commit_command(context: click.Context, ignored_args: List[str]) -> None:
26
+ add_breadcrumb('pre_commit')
27
+
25
28
  scan_type = context.obj['scan_type']
26
29
 
27
30
  progress_bar = context.obj['progress_bar']
@@ -17,6 +17,7 @@ from cycode.cli.exceptions.handle_scan_errors import handle_scan_exception
17
17
  from cycode.cli.files_collector.repository_documents import (
18
18
  calculate_pre_receive_commit_range,
19
19
  )
20
+ from cycode.cli.sentry import add_breadcrumb
20
21
  from cycode.cli.utils.task_timer import TimeoutAfter
21
22
  from cycode.cyclient import logger
22
23
 
@@ -26,6 +27,8 @@ from cycode.cyclient import logger
26
27
  @click.pass_context
27
28
  def pre_receive_command(context: click.Context, ignored_args: List[str]) -> None:
28
29
  try:
30
+ add_breadcrumb('pre_receive')
31
+
29
32
  scan_type = context.obj['scan_type']
30
33
  if scan_type != consts.SECRET_SCAN_TYPE:
31
34
  raise click.ClickException(f'Commit range scanning for {scan_type.upper()} is not supported')
@@ -9,6 +9,7 @@ from cycode.cli.files_collector.excluder import exclude_irrelevant_documents_to_
9
9
  from cycode.cli.files_collector.repository_documents import get_git_repository_tree_file_entries
10
10
  from cycode.cli.files_collector.sca.sca_code_scanner import perform_pre_scan_documents_actions
11
11
  from cycode.cli.models import Document
12
+ from cycode.cli.sentry import add_breadcrumb
12
13
  from cycode.cli.utils.path_utils import get_path_by_os
13
14
  from cycode.cli.utils.progress_bar import ScanProgressBarSection
14
15
  from cycode.cyclient import logger
@@ -27,6 +28,8 @@ from cycode.cyclient import logger
27
28
  @click.pass_context
28
29
  def repository_command(context: click.Context, path: str, branch: str) -> None:
29
30
  try:
31
+ add_breadcrumb('repository')
32
+
30
33
  logger.debug('Starting repository scan process, %s', {'path': path, 'branch': branch})
31
34
 
32
35
  scan_type = context.obj['scan_type']
@@ -4,6 +4,7 @@ import click
4
4
 
5
5
  from cycode.cli.commands.scan.code_scanner import scan_commit_range
6
6
  from cycode.cli.commands.scan.scan_ci.ci_integrations import get_commit_range
7
+ from cycode.cli.sentry import add_breadcrumb
7
8
 
8
9
  # This command is not finished yet. It is not used in the codebase.
9
10
 
@@ -14,4 +15,5 @@ from cycode.cli.commands.scan.scan_ci.ci_integrations import get_commit_range
14
15
  )
15
16
  @click.pass_context
16
17
  def scan_ci_command(context: click.Context) -> None:
18
+ add_breadcrumb('ci')
17
19
  scan_commit_range(context, path=os.getcwd(), commit_range=get_commit_range())
@@ -15,6 +15,7 @@ from cycode.cli.consts import (
15
15
  SCA_SKIP_RESTORE_DEPENDENCIES_FLAG,
16
16
  )
17
17
  from cycode.cli.models import Severity
18
+ from cycode.cli.sentry import add_breadcrumb
18
19
  from cycode.cli.utils import scan_utils
19
20
  from cycode.cli.utils.get_api_client import get_scan_cycode_client
20
21
 
@@ -124,6 +125,8 @@ def scan_command(
124
125
  sync: bool,
125
126
  ) -> int:
126
127
  """Scans for Secrets, IaC, SCA or SAST violations."""
128
+ add_breadcrumb('scan')
129
+
127
130
  if show_secret:
128
131
  context.obj['show_secret'] = show_secret
129
132
  else:
@@ -155,6 +158,8 @@ def _sca_scan_to_context(context: click.Context, sca_scan_user_selected: List[st
155
158
  @scan_command.result_callback()
156
159
  @click.pass_context
157
160
  def finalize(context: click.Context, *_, **__) -> None:
161
+ add_breadcrumb('scan_finalize')
162
+
158
163
  progress_bar = context.obj.get('progress_bar')
159
164
  if progress_bar:
160
165
  progress_bar.stop()
@@ -1,4 +1,5 @@
1
1
  PROGRAM_NAME = 'cycode'
2
+ APP_NAME = 'CycodeCLI'
2
3
  CLI_CONTEXT_SETTINGS = {
3
4
  'terminal_width': 10**9,
4
5
  'max_content_width': 10**9,
@@ -142,6 +143,14 @@ SCAN_BATCH_MAX_FILES_COUNT = 1000
142
143
  SCAN_BATCH_MAX_PARALLEL_SCANS = 5
143
144
  SCAN_BATCH_SCANS_PER_CPU = 1
144
145
 
146
+ # sentry
147
+ SENTRY_DSN = 'https://5e26b304b30ced3a34394b6f81f1076d@o1026942.ingest.us.sentry.io/4507543840096256'
148
+ SENTRY_DEBUG = False
149
+ SENTRY_SAMPLE_RATE = 1.0
150
+ SENTRY_SEND_DEFAULT_PII = False
151
+ SENTRY_INCLUDE_LOCAL_VARIABLES = False
152
+ SENTRY_MAX_REQUEST_BODY_SIZE = 'never'
153
+
145
154
  # report with polling
146
155
  REPORT_POLLING_WAIT_INTERVAL_IN_SECONDS = 5
147
156
  DEFAULT_REPORT_POLLING_TIMEOUT_IN_SECONDS = 600
@@ -5,6 +5,7 @@ import click
5
5
  from cycode.cli.exceptions import custom_exceptions
6
6
  from cycode.cli.models import CliError, CliErrors
7
7
  from cycode.cli.printers import ConsolePrinter
8
+ from cycode.cli.sentry import capture_exception
8
9
 
9
10
 
10
11
  def handle_report_exception(context: click.Context, err: Exception) -> Optional[CliError]:
@@ -42,4 +43,6 @@ def handle_report_exception(context: click.Context, err: Exception) -> Optional[
42
43
  if isinstance(err, click.ClickException):
43
44
  raise err
44
45
 
46
+ capture_exception(err)
47
+
45
48
  raise click.ClickException(str(err))
@@ -5,6 +5,7 @@ import click
5
5
  from cycode.cli.exceptions import custom_exceptions
6
6
  from cycode.cli.models import CliError, CliErrors
7
7
  from cycode.cli.printers import ConsolePrinter
8
+ from cycode.cli.sentry import capture_exception
8
9
  from cycode.cli.utils.git_proxy import git_proxy
9
10
 
10
11
 
@@ -69,13 +70,14 @@ def handle_scan_exception(
69
70
  ConsolePrinter(context).print_error(error)
70
71
  return None
71
72
 
72
- unknown_error = CliError(code='unknown_error', message=str(e))
73
+ if isinstance(e, click.ClickException):
74
+ raise e
75
+
76
+ capture_exception(e)
73
77
 
78
+ unknown_error = CliError(code='unknown_error', message=str(e))
74
79
  if return_exception:
75
80
  return unknown_error
76
81
 
77
- if isinstance(e, click.ClickException):
78
- raise e
79
-
80
82
  ConsolePrinter(context).print_error(unknown_error)
81
83
  exit(1)
@@ -1,6 +1,7 @@
1
1
  from multiprocessing import freeze_support
2
2
 
3
3
  from cycode.cli.commands.main_cli import main_cli
4
+ from cycode.cli.sentry import add_breadcrumb, init_sentry
4
5
 
5
6
  if __name__ == '__main__':
6
7
  # DO NOT REMOVE OR MOVE THIS LINE
@@ -8,4 +9,7 @@ if __name__ == '__main__':
8
9
  # see https://pyinstaller.org/en/latest/common-issues-and-pitfalls.html#multi-processing
9
10
  freeze_support()
10
11
 
12
+ init_sentry()
13
+ add_breadcrumb('cycode')
14
+
11
15
  main_cli()
@@ -62,6 +62,7 @@ CliErrors = Dict[Type[Exception], CliError]
62
62
  class CliResult(NamedTuple):
63
63
  success: bool
64
64
  message: str
65
+ data: Optional[Dict[str, any]] = None
65
66
 
66
67
 
67
68
  class LocalScanResult(NamedTuple):
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
13
13
 
14
14
  class JsonPrinter(PrinterBase):
15
15
  def print_result(self, result: CliResult) -> None:
16
- result = {'result': result.success, 'message': result.message}
16
+ result = {'result': result.success, 'message': result.message, 'data': result.data}
17
17
 
18
18
  click.echo(self.get_data_json(result))
19
19
 
@@ -27,6 +27,13 @@ class TextPrinter(PrinterBase):
27
27
 
28
28
  click.secho(result.message, fg=color)
29
29
 
30
+ if not result.data:
31
+ return
32
+
33
+ click.secho('\nAdditional data:', fg=color)
34
+ for name, value in result.data.items():
35
+ click.secho(f'- {name}: {value}', fg=color)
36
+
30
37
  def print_error(self, error: CliError) -> None:
31
38
  click.secho(error.message, fg=self.RED_COLOR_NAME)
32
39
 
@@ -0,0 +1,99 @@
1
+ import logging
2
+ from dataclasses import dataclass
3
+ from typing import Optional
4
+
5
+ import sentry_sdk
6
+ from sentry_sdk.integrations.atexit import AtexitIntegration
7
+ from sentry_sdk.scrubber import DEFAULT_DENYLIST, EventScrubber
8
+
9
+ from cycode import __version__
10
+ from cycode.cli import consts
11
+ from cycode.cli.utils.jwt_utils import get_user_and_tenant_ids_from_access_token
12
+ from cycode.cyclient import logger
13
+
14
+ # when Sentry is blocked on the machine, we want to keep clean output without retries warnings
15
+ logging.getLogger('urllib3.connectionpool').setLevel(logging.ERROR)
16
+ logging.getLogger('sentry_sdk').setLevel(logging.ERROR)
17
+
18
+
19
+ @dataclass
20
+ class _SentrySession:
21
+ user_id: Optional[str] = None
22
+ tenant_id: Optional[str] = None
23
+ correlation_id: Optional[str] = None
24
+
25
+
26
+ _SENTRY_SESSION = _SentrySession()
27
+ _DENY_LIST = [*DEFAULT_DENYLIST, 'access_token']
28
+
29
+
30
+ def _get_sentry_release() -> str:
31
+ return f'{consts.APP_NAME}@{__version__}'
32
+
33
+
34
+ def _get_sentry_local_release() -> str:
35
+ return f'{consts.APP_NAME}@0.0.0'
36
+
37
+
38
+ _SENTRY_LOCAL_RELEASE = _get_sentry_local_release()
39
+
40
+
41
+ def _before_sentry_event_send(event: dict, _: dict) -> Optional[dict]:
42
+ if event.get('release') == _SENTRY_LOCAL_RELEASE:
43
+ logger.debug('Dropping Sentry event due to local development setup')
44
+ return None
45
+
46
+ return event
47
+
48
+
49
+ def init_sentry() -> None:
50
+ sentry_sdk.init(
51
+ dsn=consts.SENTRY_DSN,
52
+ debug=consts.SENTRY_DEBUG,
53
+ release=_get_sentry_release(),
54
+ before_send=_before_sentry_event_send,
55
+ sample_rate=consts.SENTRY_SAMPLE_RATE,
56
+ send_default_pii=consts.SENTRY_SEND_DEFAULT_PII,
57
+ include_local_variables=consts.SENTRY_INCLUDE_LOCAL_VARIABLES,
58
+ max_request_body_size=consts.SENTRY_MAX_REQUEST_BODY_SIZE,
59
+ event_scrubber=EventScrubber(denylist=_DENY_LIST, recursive=True),
60
+ integrations=[
61
+ AtexitIntegration(lambda _, __: None) # disable output to stderr about pending events
62
+ ],
63
+ )
64
+ sentry_sdk.set_user(None)
65
+
66
+
67
+ def setup_scope_from_access_token(access_token: Optional[str]) -> None:
68
+ if not access_token:
69
+ return
70
+
71
+ user_id, tenant_id = get_user_and_tenant_ids_from_access_token(access_token)
72
+
73
+ _SENTRY_SESSION.user_id = user_id
74
+ _SENTRY_SESSION.tenant_id = tenant_id
75
+
76
+ _setup_scope(user_id, tenant_id, _SENTRY_SESSION.correlation_id)
77
+
78
+
79
+ def add_correlation_id_to_scope(correlation_id: str) -> None:
80
+ _setup_scope(_SENTRY_SESSION.user_id, _SENTRY_SESSION.tenant_id, correlation_id)
81
+
82
+
83
+ def _setup_scope(user_id: str, tenant_id: str, correlation_id: Optional[str] = None) -> None:
84
+ scope = sentry_sdk.Scope.get_current_scope()
85
+ sentry_sdk.set_tag('tenant_id', tenant_id)
86
+
87
+ user = {'id': user_id, 'tenant_id': tenant_id}
88
+ if correlation_id:
89
+ user['correlation_id'] = correlation_id
90
+
91
+ scope.set_user(user)
92
+
93
+
94
+ def capture_exception(exception: BaseException) -> None:
95
+ sentry_sdk.capture_exception(exception)
96
+
97
+
98
+ def add_breadcrumb(message: str, category: str = 'cli') -> None:
99
+ sentry_sdk.add_breadcrumb(category=category, message=message, level='info')
@@ -3,6 +3,7 @@ from pathlib import Path
3
3
  from typing import Optional, Tuple
4
4
 
5
5
  from cycode.cli.config import CYCODE_CLIENT_ID_ENV_VAR_NAME, CYCODE_CLIENT_SECRET_ENV_VAR_NAME
6
+ from cycode.cli.sentry import setup_scope_from_access_token
6
7
  from cycode.cli.user_settings.base_file_manager import BaseFileManager
7
8
  from cycode.cli.user_settings.jwt_creator import JwtCreator
8
9
 
@@ -52,6 +53,8 @@ class CredentialsManager(BaseFileManager):
52
53
  if hashed_creator:
53
54
  creator = JwtCreator(hashed_creator)
54
55
 
56
+ setup_scope_from_access_token(access_token)
57
+
55
58
  return access_token, expires_in, creator
56
59
 
57
60
  def update_access_token(
@@ -64,5 +67,7 @@ class CredentialsManager(BaseFileManager):
64
67
  }
65
68
  self.write_content_to_file(file_content_to_update)
66
69
 
70
+ setup_scope_from_access_token(access_token)
71
+
67
72
  def get_filename(self) -> str:
68
73
  return os.path.join(self.HOME_PATH, self.CYCODE_HIDDEN_DIRECTORY, self.FILE_NAME)
@@ -0,0 +1,14 @@
1
+ from typing import Tuple
2
+
3
+ import jwt
4
+
5
+
6
+ def get_user_and_tenant_ids_from_access_token(access_token: str) -> Tuple[str, str]:
7
+ payload = jwt.decode(access_token, options={'verify_signature': False})
8
+ user_id = payload.get('userId')
9
+ tenant_id = payload.get('tenantId')
10
+
11
+ if not user_id or not tenant_id:
12
+ raise ValueError('Invalid access token')
13
+
14
+ return user_id, tenant_id
@@ -3,6 +3,8 @@ from typing import Optional
3
3
  from uuid import uuid4
4
4
 
5
5
  from cycode import __version__
6
+ from cycode.cli import consts
7
+ from cycode.cli.sentry import add_correlation_id_to_scope
6
8
  from cycode.cli.user_settings.configuration_manager import ConfigurationManager
7
9
  from cycode.cyclient import logger
8
10
 
@@ -12,7 +14,6 @@ def get_cli_user_agent() -> str:
12
14
 
13
15
  Example: CycodeCLI/0.2.3 (OS: Darwin; Arch: arm64; Python: 3.8.16; InstallID: *uuid4*)
14
16
  """
15
- app_name = 'CycodeCLI'
16
17
  version = __version__
17
18
 
18
19
  os = platform.system()
@@ -21,7 +22,7 @@ def get_cli_user_agent() -> str:
21
22
 
22
23
  install_id = ConfigurationManager().get_or_create_installation_id()
23
24
 
24
- return f'{app_name}/{version} (OS: {os}; Arch: {arch}; Python: {python_version}; InstallID: {install_id})'
25
+ return f'{consts.APP_NAME}/{version} (OS: {os}; Arch: {arch}; Python: {python_version}; InstallID: {install_id})'
25
26
 
26
27
 
27
28
  class _CorrelationId:
@@ -40,6 +41,8 @@ class _CorrelationId:
40
41
  self._id = str(uuid4())
41
42
  logger.debug('Correlation ID: %s', self._id)
42
43
 
44
+ add_correlation_id_to_scope(self._id)
45
+
43
46
  return self._id
44
47
 
45
48
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "cycode"
3
- version = "1.10.2.dev1" # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
3
+ version = "1.10.3.dev1" # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
4
4
  description = "Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning."
5
5
  keywords=["secret-scan", "cycode", "devops", "token", "secret", "security", "cycode", "code"]
6
6
  authors = ["Cycode <support@cycode.com>"]
@@ -39,6 +39,8 @@ binaryornot = ">=0.4.4,<0.5.0"
39
39
  texttable = ">=1.6.7,<1.8.0"
40
40
  requests = ">=2.24,<3.0"
41
41
  urllib3 = "1.26.18" # lock v1 to avoid issues with openssl and old Python versions (<3.9.11) on macOS
42
+ sentry-sdk = ">=2.8.0,<3.0"
43
+ pyjwt = ">=2.8.0,<3.0"
42
44
 
43
45
  [tool.poetry.group.test.dependencies]
44
46
  mock = ">=4.0.3,<4.1.0"
@@ -1 +0,0 @@
1
- __version__ = '1.10.2.dev1' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
File without changes