cycode 2.0.1.dev2__tar.gz → 2.0.1.dev4__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 (131) hide show
  1. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/PKG-INFO +3 -1
  2. cycode-2.0.1.dev4/cycode/__init__.py +1 -0
  3. cycode-2.0.1.dev4/cycode/cli/commands/ai_remediation/ai_remediation_command.py +67 -0
  4. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/main_cli.py +2 -0
  5. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/version/version_command.py +1 -1
  6. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/consts.py +4 -0
  7. cycode-2.0.1.dev4/cycode/cli/exceptions/common.py +37 -0
  8. cycode-2.0.1.dev4/cycode/cli/exceptions/handle_ai_remediation_errors.py +22 -0
  9. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/exceptions/handle_report_sbom_errors.py +3 -20
  10. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/exceptions/handle_scan_errors.py +4 -29
  11. cycode-2.0.1.dev4/cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py +25 -0
  12. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/sca_code_scanner.py +2 -0
  13. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/models.py +1 -1
  14. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/printers/console_printer.py +12 -0
  15. cycode-2.0.1.dev4/cycode/cli/user_settings/__init__.py +0 -0
  16. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/user_settings/configuration_manager.py +7 -0
  17. cycode-2.0.1.dev4/cycode/cli/utils/__init__.py +0 -0
  18. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/models.py +3 -0
  19. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/scan_client.py +22 -0
  20. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/pyproject.toml +3 -1
  21. cycode-2.0.1.dev2/cycode/__init__.py +0 -1
  22. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/LICENCE +0 -0
  23. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/README.md +0 -0
  24. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/__init__.py +0 -0
  25. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/__init__.py +0 -0
  26. {cycode-2.0.1.dev2/cycode/cli/commands/auth → cycode-2.0.1.dev4/cycode/cli/commands/ai_remediation}/__init__.py +0 -0
  27. {cycode-2.0.1.dev2/cycode/cli/commands/configure → cycode-2.0.1.dev4/cycode/cli/commands/auth}/__init__.py +0 -0
  28. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/auth/auth_command.py +0 -0
  29. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/auth/auth_manager.py +0 -0
  30. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/auth_common.py +0 -0
  31. {cycode-2.0.1.dev2/cycode/cli/commands/ignore → cycode-2.0.1.dev4/cycode/cli/commands/configure}/__init__.py +0 -0
  32. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/configure/configure_command.py +0 -0
  33. {cycode-2.0.1.dev2/cycode/cli/commands/report → cycode-2.0.1.dev4/cycode/cli/commands/ignore}/__init__.py +0 -0
  34. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/ignore/ignore_command.py +0 -0
  35. {cycode-2.0.1.dev2/cycode/cli/commands/report/sbom → cycode-2.0.1.dev4/cycode/cli/commands/report}/__init__.py +0 -0
  36. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/report/report_command.py +0 -0
  37. {cycode-2.0.1.dev2/cycode/cli/commands/report/sbom/path → cycode-2.0.1.dev4/cycode/cli/commands/report/sbom}/__init__.py +0 -0
  38. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/report/sbom/common.py +0 -0
  39. {cycode-2.0.1.dev2/cycode/cli/commands/report/sbom/repository_url → cycode-2.0.1.dev4/cycode/cli/commands/report/sbom/path}/__init__.py +0 -0
  40. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/report/sbom/path/path_command.py +0 -0
  41. {cycode-2.0.1.dev2/cycode/cli/commands/scan → cycode-2.0.1.dev4/cycode/cli/commands/report/sbom/repository_url}/__init__.py +0 -0
  42. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/report/sbom/repository_url/repository_url_command.py +0 -0
  43. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/report/sbom/sbom_command.py +0 -0
  44. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/report/sbom/sbom_report_file.py +0 -0
  45. {cycode-2.0.1.dev2/cycode/cli/commands/scan/commit_history → cycode-2.0.1.dev4/cycode/cli/commands/scan}/__init__.py +0 -0
  46. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/code_scanner.py +0 -0
  47. {cycode-2.0.1.dev2/cycode/cli/commands/scan/path → cycode-2.0.1.dev4/cycode/cli/commands/scan/commit_history}/__init__.py +0 -0
  48. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/commit_history/commit_history_command.py +0 -0
  49. {cycode-2.0.1.dev2/cycode/cli/commands/scan/pre_commit → cycode-2.0.1.dev4/cycode/cli/commands/scan/path}/__init__.py +0 -0
  50. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/path/path_command.py +0 -0
  51. {cycode-2.0.1.dev2/cycode/cli/commands/scan/pre_receive → cycode-2.0.1.dev4/cycode/cli/commands/scan/pre_commit}/__init__.py +0 -0
  52. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/pre_commit/pre_commit_command.py +0 -0
  53. {cycode-2.0.1.dev2/cycode/cli/commands/scan/repository → cycode-2.0.1.dev4/cycode/cli/commands/scan/pre_receive}/__init__.py +0 -0
  54. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/pre_receive/pre_receive_command.py +0 -0
  55. {cycode-2.0.1.dev2/cycode/cli/commands/scan/scan_ci → cycode-2.0.1.dev4/cycode/cli/commands/scan/repository}/__init__.py +0 -0
  56. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/repository/repository_command.py +0 -0
  57. {cycode-2.0.1.dev2/cycode/cli/commands/status → cycode-2.0.1.dev4/cycode/cli/commands/scan/scan_ci}/__init__.py +0 -0
  58. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/scan_ci/ci_integrations.py +0 -0
  59. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/scan_ci/scan_ci_command.py +0 -0
  60. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/scan_command.py +0 -0
  61. {cycode-2.0.1.dev2/cycode/cli/commands/version → cycode-2.0.1.dev4/cycode/cli/commands/status}/__init__.py +0 -0
  62. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/commands/status/status_command.py +0 -0
  63. {cycode-2.0.1.dev2/cycode/cli/exceptions → cycode-2.0.1.dev4/cycode/cli/commands/version}/__init__.py +0 -0
  64. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/config.py +0 -0
  65. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/config.yaml +0 -0
  66. {cycode-2.0.1.dev2/cycode/cli/files_collector → cycode-2.0.1.dev4/cycode/cli/exceptions}/__init__.py +0 -0
  67. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/exceptions/custom_exceptions.py +0 -0
  68. {cycode-2.0.1.dev2/cycode/cli/files_collector/iac → cycode-2.0.1.dev4/cycode/cli/files_collector}/__init__.py +0 -0
  69. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/excluder.py +0 -0
  70. {cycode-2.0.1.dev2/cycode/cli/files_collector/models → cycode-2.0.1.dev4/cycode/cli/files_collector/iac}/__init__.py +0 -0
  71. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
  72. {cycode-2.0.1.dev2/cycode/cli/files_collector/sca → cycode-2.0.1.dev4/cycode/cli/files_collector/models}/__init__.py +0 -0
  73. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
  74. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/path_documents.py +0 -0
  75. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/repository_documents.py +0 -0
  76. {cycode-2.0.1.dev2/cycode/cli/files_collector/sca/go → cycode-2.0.1.dev4/cycode/cli/files_collector/sca}/__init__.py +0 -0
  77. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/base_restore_dependencies.py +0 -0
  78. {cycode-2.0.1.dev2/cycode/cli/files_collector/sca/maven → cycode-2.0.1.dev4/cycode/cli/files_collector/sca/go}/__init__.py +0 -0
  79. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/go/restore_go_dependencies.py +0 -0
  80. {cycode-2.0.1.dev2/cycode/cli/files_collector/sca/npm → cycode-2.0.1.dev4/cycode/cli/files_collector/sca/maven}/__init__.py +0 -0
  81. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
  82. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
  83. {cycode-2.0.1.dev2/cycode/cli/files_collector/sca/nuget → cycode-2.0.1.dev4/cycode/cli/files_collector/sca/npm}/__init__.py +0 -0
  84. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py +0 -0
  85. {cycode-2.0.1.dev2/cycode/cli/files_collector/sca/sbt → cycode-2.0.1.dev4/cycode/cli/files_collector/sca/nuget}/__init__.py +0 -0
  86. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py +0 -0
  87. {cycode-2.0.1.dev2/cycode/cli/printers/tables → cycode-2.0.1.dev4/cycode/cli/files_collector/sca/ruby}/__init__.py +0 -0
  88. {cycode-2.0.1.dev2/cycode/cli/user_settings → cycode-2.0.1.dev4/cycode/cli/files_collector/sca/sbt}/__init__.py +0 -0
  89. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py +0 -0
  90. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/files_collector/zip_documents.py +0 -0
  91. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/main.py +0 -0
  92. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/printers/__init__.py +0 -0
  93. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/printers/json_printer.py +0 -0
  94. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/printers/printer_base.py +0 -0
  95. {cycode-2.0.1.dev2/cycode/cli/utils → cycode-2.0.1.dev4/cycode/cli/printers/tables}/__init__.py +0 -0
  96. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
  97. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/printers/tables/table.py +0 -0
  98. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/printers/tables/table_models.py +0 -0
  99. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/printers/tables/table_printer.py +0 -0
  100. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/printers/tables/table_printer_base.py +0 -0
  101. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/printers/text_printer.py +0 -0
  102. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/sentry.py +0 -0
  103. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/user_settings/base_file_manager.py +0 -0
  104. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/user_settings/config_file_manager.py +0 -0
  105. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/user_settings/credentials_manager.py +0 -0
  106. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/user_settings/jwt_creator.py +0 -0
  107. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/utils/enum_utils.py +0 -0
  108. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/utils/get_api_client.py +0 -0
  109. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/utils/git_proxy.py +0 -0
  110. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/utils/jwt_utils.py +0 -0
  111. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/utils/path_utils.py +0 -0
  112. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/utils/progress_bar.py +0 -0
  113. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/utils/scan_batch.py +0 -0
  114. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/utils/scan_utils.py +0 -0
  115. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/utils/shell_executor.py +0 -0
  116. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/utils/string_utils.py +0 -0
  117. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/utils/task_timer.py +0 -0
  118. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cli/utils/yaml_utils.py +0 -0
  119. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/__init__.py +0 -0
  120. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/auth_client.py +0 -0
  121. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/client_creator.py +0 -0
  122. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/config.py +0 -0
  123. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/config.yaml +0 -0
  124. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/config_dev.py +0 -0
  125. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/cycode_client.py +0 -0
  126. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/cycode_client_base.py +0 -0
  127. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/cycode_dev_based_client.py +0 -0
  128. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/cycode_token_based_client.py +0 -0
  129. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/headers.py +0 -0
  130. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/report_client.py +0 -0
  131. {cycode-2.0.1.dev2 → cycode-2.0.1.dev4}/cycode/cyclient/scan_config_base.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cycode
3
- Version: 2.0.1.dev2
3
+ Version: 2.0.1.dev4
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
@@ -28,10 +28,12 @@ Requires-Dist: click (>=8.1.0,<8.2.0)
28
28
  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.23.0)
31
+ Requires-Dist: patch-ng (==1.18.1)
31
32
  Requires-Dist: pathspec (>=0.11.1,<0.13.0)
32
33
  Requires-Dist: pyjwt (>=2.8.0,<3.0)
33
34
  Requires-Dist: pyyaml (>=6.0,<7.0)
34
35
  Requires-Dist: requests (>=2.32.2,<3.0)
36
+ Requires-Dist: rich (>=13.9.4,<14)
35
37
  Requires-Dist: sentry-sdk (>=2.8.0,<3.0)
36
38
  Requires-Dist: texttable (>=1.6.7,<1.8.0)
37
39
  Requires-Dist: urllib3 (==1.26.19)
@@ -0,0 +1 @@
1
+ __version__ = '2.0.1.dev4' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
@@ -0,0 +1,67 @@
1
+ import os
2
+
3
+ import click
4
+ from patch_ng import fromstring
5
+ from rich.console import Console
6
+ from rich.markdown import Markdown
7
+
8
+ from cycode.cli.exceptions.handle_ai_remediation_errors import handle_ai_remediation_exception
9
+ from cycode.cli.models import CliResult
10
+ from cycode.cli.printers import ConsolePrinter
11
+ from cycode.cli.utils.get_api_client import get_scan_cycode_client
12
+
13
+
14
+ def _echo_remediation(context: click.Context, remediation_markdown: str, is_fix_available: bool) -> None:
15
+ printer = ConsolePrinter(context)
16
+ if printer.is_json_printer:
17
+ data = {'remediation': remediation_markdown, 'is_fix_available': is_fix_available}
18
+ printer.print_result(CliResult(success=True, message='Remediation fetched successfully', data=data))
19
+ else: # text or table
20
+ Console().print(Markdown(remediation_markdown))
21
+
22
+
23
+ def _apply_fix(context: click.Context, diff: str, is_fix_available: bool) -> None:
24
+ printer = ConsolePrinter(context)
25
+ if not is_fix_available:
26
+ printer.print_result(CliResult(success=False, message='Fix is not available for this violation'))
27
+ return
28
+
29
+ patch = fromstring(diff.encode('UTF-8'))
30
+ if patch is False:
31
+ printer.print_result(CliResult(success=False, message='Failed to parse fix diff'))
32
+ return
33
+
34
+ is_fix_applied = patch.apply(root=os.getcwd(), strip=0)
35
+ if is_fix_applied:
36
+ printer.print_result(CliResult(success=True, message='Fix applied successfully'))
37
+ else:
38
+ printer.print_result(CliResult(success=False, message='Failed to apply fix'))
39
+
40
+
41
+ @click.command(short_help='Get AI remediation (INTERNAL).', hidden=True)
42
+ @click.argument('detection_id', nargs=1, type=click.UUID, required=True)
43
+ @click.option(
44
+ '--fix',
45
+ is_flag=True,
46
+ default=False,
47
+ help='Apply fixes to resolve violations. Fix is not available for all violations.',
48
+ type=click.BOOL,
49
+ required=False,
50
+ )
51
+ @click.pass_context
52
+ def ai_remediation_command(context: click.Context, detection_id: str, fix: bool) -> None:
53
+ client = get_scan_cycode_client()
54
+
55
+ try:
56
+ remediation_markdown = client.get_ai_remediation(detection_id)
57
+ fix_diff = client.get_ai_remediation(detection_id, fix=True)
58
+ is_fix_available = bool(fix_diff) # exclude empty string, None, etc.
59
+
60
+ if fix:
61
+ _apply_fix(context, fix_diff, is_fix_available)
62
+ else:
63
+ _echo_remediation(context, remediation_markdown, is_fix_available)
64
+ except Exception as err:
65
+ handle_ai_remediation_exception(context, err)
66
+
67
+ context.exit()
@@ -3,6 +3,7 @@ from typing import Optional
3
3
 
4
4
  import click
5
5
 
6
+ from cycode.cli.commands.ai_remediation.ai_remediation_command import ai_remediation_command
6
7
  from cycode.cli.commands.auth.auth_command import auth_command
7
8
  from cycode.cli.commands.configure.configure_command import configure_command
8
9
  from cycode.cli.commands.ignore.ignore_command import ignore_command
@@ -30,6 +31,7 @@ from cycode.cyclient.models import UserAgentOptionScheme
30
31
  'auth': auth_command,
31
32
  'version': version_command,
32
33
  'status': status_command,
34
+ 'ai_remediation': ai_remediation_command,
33
35
  },
34
36
  context_settings=CLI_CONTEXT_SETTINGS,
35
37
  )
@@ -6,7 +6,7 @@ from cycode import __version__
6
6
  from cycode.cli.consts import PROGRAM_NAME
7
7
 
8
8
 
9
- @click.command(short_help='Show the CLI version and exit.')
9
+ @click.command(short_help='Show the CLI version and exit. Use `cycode status` instead.', deprecated=True)
10
10
  @click.pass_context
11
11
  def version_command(context: click.Context) -> None:
12
12
  output = context.obj['output']
@@ -159,6 +159,10 @@ SENTRY_MAX_REQUEST_BODY_SIZE = 'never'
159
159
  SYNC_SCAN_TIMEOUT_IN_SECONDS_ENV_VAR_NAME = 'SYNC_SCAN_TIMEOUT_IN_SECONDS'
160
160
  DEFAULT_SYNC_SCAN_TIMEOUT_IN_SECONDS = 180
161
161
 
162
+ # ai remediation
163
+ AI_REMEDIATION_TIMEOUT_IN_SECONDS_ENV_VAR_NAME = 'AI_REMEDIATION_TIMEOUT_IN_SECONDS'
164
+ DEFAULT_AI_REMEDIATION_TIMEOUT_IN_SECONDS = 60
165
+
162
166
  # report with polling
163
167
  REPORT_POLLING_WAIT_INTERVAL_IN_SECONDS = 5
164
168
  DEFAULT_REPORT_POLLING_TIMEOUT_IN_SECONDS = 600
@@ -0,0 +1,37 @@
1
+ from typing import Optional
2
+
3
+ import click
4
+
5
+ from cycode.cli.models import CliError, CliErrors
6
+ from cycode.cli.printers import ConsolePrinter
7
+ from cycode.cli.sentry import capture_exception
8
+
9
+
10
+ def handle_errors(
11
+ context: click.Context, err: BaseException, cli_errors: CliErrors, *, return_exception: bool = False
12
+ ) -> Optional['CliError']:
13
+ ConsolePrinter(context).print_exception(err)
14
+
15
+ if type(err) in cli_errors:
16
+ error = cli_errors[type(err)]
17
+
18
+ if error.soft_fail is True:
19
+ context.obj['soft_fail'] = True
20
+
21
+ if return_exception:
22
+ return error
23
+
24
+ ConsolePrinter(context).print_error(error)
25
+ return None
26
+
27
+ if isinstance(err, click.ClickException):
28
+ raise err
29
+
30
+ capture_exception(err)
31
+
32
+ unknown_error = CliError(code='unknown_error', message=str(err))
33
+ if return_exception:
34
+ return unknown_error
35
+
36
+ ConsolePrinter(context).print_error(unknown_error)
37
+ exit(1)
@@ -0,0 +1,22 @@
1
+ import click
2
+
3
+ from cycode.cli.exceptions.common import handle_errors
4
+ from cycode.cli.exceptions.custom_exceptions import KNOWN_USER_FRIENDLY_REQUEST_ERRORS, RequestHttpError
5
+ from cycode.cli.models import CliError, CliErrors
6
+
7
+
8
+ class AiRemediationNotFoundError(Exception): ...
9
+
10
+
11
+ def handle_ai_remediation_exception(context: click.Context, err: Exception) -> None:
12
+ if isinstance(err, RequestHttpError) and err.status_code == 404:
13
+ err = AiRemediationNotFoundError()
14
+
15
+ errors: CliErrors = {
16
+ **KNOWN_USER_FRIENDLY_REQUEST_ERRORS,
17
+ AiRemediationNotFoundError: CliError(
18
+ code='ai_remediation_not_found',
19
+ message='The AI remediation was not found. Please try different detection ID',
20
+ ),
21
+ }
22
+ handle_errors(context, err, errors)
@@ -1,17 +1,12 @@
1
- from typing import Optional
2
-
3
1
  import click
4
2
 
5
3
  from cycode.cli.exceptions import custom_exceptions
4
+ from cycode.cli.exceptions.common import handle_errors
6
5
  from cycode.cli.exceptions.custom_exceptions import KNOWN_USER_FRIENDLY_REQUEST_ERRORS
7
6
  from cycode.cli.models import CliError, CliErrors
8
- from cycode.cli.printers import ConsolePrinter
9
- from cycode.cli.sentry import capture_exception
10
-
11
7
 
12
- def handle_report_exception(context: click.Context, err: Exception) -> Optional[CliError]:
13
- ConsolePrinter(context).print_exception()
14
8
 
9
+ def handle_report_exception(context: click.Context, err: Exception) -> None:
15
10
  errors: CliErrors = {
16
11
  **KNOWN_USER_FRIENDLY_REQUEST_ERRORS,
17
12
  custom_exceptions.ScanAsyncError: CliError(
@@ -25,16 +20,4 @@ def handle_report_exception(context: click.Context, err: Exception) -> Optional[
25
20
  'Please try again by executing the `cycode report` command',
26
21
  ),
27
22
  }
28
-
29
- if type(err) in errors:
30
- error = errors[type(err)]
31
-
32
- ConsolePrinter(context).print_error(error)
33
- return None
34
-
35
- if isinstance(err, click.ClickException):
36
- raise err
37
-
38
- capture_exception(err)
39
-
40
- raise click.ClickException(str(err))
23
+ handle_errors(context, err, errors)
@@ -3,20 +3,17 @@ from typing import Optional
3
3
  import click
4
4
 
5
5
  from cycode.cli.exceptions import custom_exceptions
6
+ from cycode.cli.exceptions.common import handle_errors
6
7
  from cycode.cli.exceptions.custom_exceptions import KNOWN_USER_FRIENDLY_REQUEST_ERRORS
7
8
  from cycode.cli.models import CliError, CliErrors
8
- from cycode.cli.printers import ConsolePrinter
9
- from cycode.cli.sentry import capture_exception
10
9
  from cycode.cli.utils.git_proxy import git_proxy
11
10
 
12
11
 
13
12
  def handle_scan_exception(
14
- context: click.Context, e: Exception, *, return_exception: bool = False
13
+ context: click.Context, err: Exception, *, return_exception: bool = False
15
14
  ) -> Optional[CliError]:
16
15
  context.obj['did_fail'] = True
17
16
 
18
- ConsolePrinter(context).print_exception(e)
19
-
20
17
  errors: CliErrors = {
21
18
  **KNOWN_USER_FRIENDLY_REQUEST_ERRORS,
22
19
  custom_exceptions.ScanAsyncError: CliError(
@@ -35,7 +32,7 @@ def handle_scan_exception(
35
32
  custom_exceptions.TfplanKeyError: CliError(
36
33
  soft_fail=True,
37
34
  code='key_error',
38
- message=f'\n{e!s}\n'
35
+ message=f'\n{err!s}\n'
39
36
  'A crucial field is missing in your terraform plan file. '
40
37
  'Please make sure that your file is well formed '
41
38
  'and execute the scan again',
@@ -48,26 +45,4 @@ def handle_scan_exception(
48
45
  ),
49
46
  }
50
47
 
51
- if type(e) in errors:
52
- error = errors[type(e)]
53
-
54
- if error.soft_fail is True:
55
- context.obj['soft_fail'] = True
56
-
57
- if return_exception:
58
- return error
59
-
60
- ConsolePrinter(context).print_error(error)
61
- return None
62
-
63
- if isinstance(e, click.ClickException):
64
- raise e
65
-
66
- capture_exception(e)
67
-
68
- unknown_error = CliError(code='unknown_error', message=str(e))
69
- if return_exception:
70
- return unknown_error
71
-
72
- ConsolePrinter(context).print_error(unknown_error)
73
- exit(1)
48
+ return handle_errors(context, err, errors, return_exception=return_exception)
@@ -0,0 +1,25 @@
1
+ import os
2
+ from typing import List, Optional
3
+
4
+ from cycode.cli.files_collector.sca.base_restore_dependencies import BaseRestoreDependencies
5
+ from cycode.cli.models import Document
6
+
7
+ RUBY_PROJECT_FILE_EXTENSIONS = ['Gemfile']
8
+ RUBY_LOCK_FILE_NAME = 'Gemfile.lock'
9
+
10
+
11
+ class RestoreRubyDependencies(BaseRestoreDependencies):
12
+ def is_project(self, document: Document) -> bool:
13
+ return any(document.path.endswith(ext) for ext in RUBY_PROJECT_FILE_EXTENSIONS)
14
+
15
+ def get_commands(self, manifest_file_path: str) -> List[List[str]]:
16
+ return [['bundle', '--quiet']]
17
+
18
+ def get_lock_file_name(self) -> str:
19
+ return RUBY_LOCK_FILE_NAME
20
+
21
+ def verify_restore_file_already_exist(self, restore_file_path: str) -> bool:
22
+ return os.path.isfile(restore_file_path)
23
+
24
+ def get_working_directory(self, document: Document) -> Optional[str]:
25
+ return os.path.dirname(document.absolute_path)
@@ -10,6 +10,7 @@ from cycode.cli.files_collector.sca.maven.restore_gradle_dependencies import Res
10
10
  from cycode.cli.files_collector.sca.maven.restore_maven_dependencies import RestoreMavenDependencies
11
11
  from cycode.cli.files_collector.sca.npm.restore_npm_dependencies import RestoreNpmDependencies
12
12
  from cycode.cli.files_collector.sca.nuget.restore_nuget_dependencies import RestoreNugetDependencies
13
+ from cycode.cli.files_collector.sca.ruby.restore_ruby_dependencies import RestoreRubyDependencies
13
14
  from cycode.cli.files_collector.sca.sbt.restore_sbt_dependencies import RestoreSbtDependencies
14
15
  from cycode.cli.models import Document
15
16
  from cycode.cli.utils.git_proxy import git_proxy
@@ -138,6 +139,7 @@ def restore_handlers(context: click.Context, is_git_diff: bool) -> List[BaseRest
138
139
  RestoreGoDependencies(context, is_git_diff, BUILD_DEP_TREE_TIMEOUT),
139
140
  RestoreNugetDependencies(context, is_git_diff, BUILD_DEP_TREE_TIMEOUT),
140
141
  RestoreNpmDependencies(context, is_git_diff, BUILD_DEP_TREE_TIMEOUT),
142
+ RestoreRubyDependencies(context, is_git_diff, BUILD_DEP_TREE_TIMEOUT),
141
143
  ]
142
144
 
143
145
 
@@ -63,7 +63,7 @@ class CliError(NamedTuple):
63
63
  soft_fail: bool = False
64
64
 
65
65
 
66
- CliErrors = Dict[Type[Exception], CliError]
66
+ CliErrors = Dict[Type[BaseException], CliError]
67
67
 
68
68
 
69
69
  class CliResult(NamedTuple):
@@ -60,3 +60,15 @@ class ConsolePrinter:
60
60
  """Print traceback message in stderr if verbose mode is set."""
61
61
  if force_print or self.context.obj.get('verbose', False):
62
62
  self._printer_class(self.context).print_exception(e)
63
+
64
+ @property
65
+ def is_json_printer(self) -> bool:
66
+ return self._printer_class == JsonPrinter
67
+
68
+ @property
69
+ def is_table_printer(self) -> bool:
70
+ return self._printer_class == TablePrinter
71
+
72
+ @property
73
+ def is_text_printer(self) -> bool:
74
+ return self._printer_class == TextPrinter
File without changes
@@ -113,6 +113,13 @@ class ConfigurationManager:
113
113
  )
114
114
  )
115
115
 
116
+ def get_ai_remediation_timeout_in_seconds(self) -> int:
117
+ return int(
118
+ self._get_value_from_environment_variables(
119
+ consts.AI_REMEDIATION_TIMEOUT_IN_SECONDS_ENV_VAR_NAME, consts.DEFAULT_AI_REMEDIATION_TIMEOUT_IN_SECONDS
120
+ )
121
+ )
122
+
116
123
  def get_report_polling_timeout_in_seconds(self) -> int:
117
124
  return int(
118
125
  self._get_value_from_environment_variables(
File without changes
@@ -13,8 +13,10 @@ class Detection(Schema):
13
13
  detection_details: dict,
14
14
  detection_rule_id: str,
15
15
  severity: Optional[str] = None,
16
+ id: Optional[str] = None,
16
17
  ) -> None:
17
18
  super().__init__()
19
+ self.id = id
18
20
  self.message = message
19
21
  self.type = type
20
22
  self.severity = severity
@@ -36,6 +38,7 @@ class DetectionSchema(Schema):
36
38
  class Meta:
37
39
  unknown = EXCLUDE
38
40
 
41
+ id = fields.String(missing=None)
39
42
  message = fields.String()
40
43
  type = fields.String()
41
44
  severity = fields.String(missing=None)
@@ -206,6 +206,28 @@ class ScanClient:
206
206
  response = self.scan_cycode_client.get(url_path='preferences/api/v1/supportedmodules')
207
207
  return models.SupportedModulesPreferencesSchema().load(response.json())
208
208
 
209
+ @staticmethod
210
+ def get_ai_remediation_path(detection_id: str) -> str:
211
+ return f'scm-remediator/api/v1/ContentRemediation/preview/{detection_id}'
212
+
213
+ def get_ai_remediation(self, detection_id: str, *, fix: bool = False) -> str:
214
+ path = self.get_ai_remediation_path(detection_id)
215
+
216
+ data = {
217
+ 'resolving_parameters': {
218
+ 'get_diff': True,
219
+ 'use_code_snippet': True,
220
+ 'add_diff_header': True,
221
+ }
222
+ }
223
+ if not fix:
224
+ data['resolving_parameters']['remediation_action'] = 'ReplyWithRemediationDetails'
225
+
226
+ response = self.scan_cycode_client.get(
227
+ url_path=path, json=data, timeout=configuration_manager.get_ai_remediation_timeout_in_seconds()
228
+ )
229
+ return response.text.strip()
230
+
209
231
  @staticmethod
210
232
  def _get_policy_type_by_scan_type(scan_type: str) -> str:
211
233
  scan_type_to_policy_type = {
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "cycode"
3
- version = "2.0.1.dev2" # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
3
+ version = "2.0.1.dev4" # 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>"]
@@ -41,6 +41,8 @@ requests = ">=2.32.2,<3.0"
41
41
  urllib3 = "1.26.19" # lock v1 to avoid issues with openssl and old Python versions (<3.9.11) on macOS
42
42
  sentry-sdk = ">=2.8.0,<3.0"
43
43
  pyjwt = ">=2.8.0,<3.0"
44
+ rich = ">=13.9.4, <14"
45
+ patch-ng = "1.18.1"
44
46
 
45
47
  [tool.poetry.group.test.dependencies]
46
48
  mock = ">=4.0.3,<4.1.0"
@@ -1 +0,0 @@
1
- __version__ = '2.0.1.dev2' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
File without changes
File without changes