cycode 2.0.1.dev1__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.dev1 → cycode-2.0.1.dev4}/PKG-INFO +4 -2
  2. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/README.md +1 -1
  3. cycode-2.0.1.dev4/cycode/__init__.py +1 -0
  4. cycode-2.0.1.dev4/cycode/cli/commands/ai_remediation/ai_remediation_command.py +67 -0
  5. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/main_cli.py +2 -0
  6. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/code_scanner.py +14 -7
  7. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/scan_command.py +1 -1
  8. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/version/version_command.py +1 -1
  9. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/consts.py +4 -0
  10. cycode-2.0.1.dev4/cycode/cli/exceptions/common.py +37 -0
  11. cycode-2.0.1.dev4/cycode/cli/exceptions/handle_ai_remediation_errors.py +22 -0
  12. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/exceptions/handle_report_sbom_errors.py +3 -20
  13. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/exceptions/handle_scan_errors.py +4 -29
  14. cycode-2.0.1.dev4/cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py +25 -0
  15. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/sca_code_scanner.py +2 -0
  16. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/models.py +2 -1
  17. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/printers/console_printer.py +12 -0
  18. cycode-2.0.1.dev4/cycode/cli/user_settings/__init__.py +0 -0
  19. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/user_settings/configuration_manager.py +7 -0
  20. cycode-2.0.1.dev4/cycode/cli/utils/__init__.py +0 -0
  21. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/models.py +3 -0
  22. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/scan_client.py +22 -0
  23. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/pyproject.toml +3 -1
  24. cycode-2.0.1.dev1/cycode/__init__.py +0 -1
  25. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/LICENCE +0 -0
  26. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/__init__.py +0 -0
  27. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/__init__.py +0 -0
  28. {cycode-2.0.1.dev1/cycode/cli/commands/auth → cycode-2.0.1.dev4/cycode/cli/commands/ai_remediation}/__init__.py +0 -0
  29. {cycode-2.0.1.dev1/cycode/cli/commands/configure → cycode-2.0.1.dev4/cycode/cli/commands/auth}/__init__.py +0 -0
  30. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/auth/auth_command.py +0 -0
  31. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/auth/auth_manager.py +0 -0
  32. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/auth_common.py +0 -0
  33. {cycode-2.0.1.dev1/cycode/cli/commands/ignore → cycode-2.0.1.dev4/cycode/cli/commands/configure}/__init__.py +0 -0
  34. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/configure/configure_command.py +0 -0
  35. {cycode-2.0.1.dev1/cycode/cli/commands/report → cycode-2.0.1.dev4/cycode/cli/commands/ignore}/__init__.py +0 -0
  36. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/ignore/ignore_command.py +0 -0
  37. {cycode-2.0.1.dev1/cycode/cli/commands/report/sbom → cycode-2.0.1.dev4/cycode/cli/commands/report}/__init__.py +0 -0
  38. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/report/report_command.py +0 -0
  39. {cycode-2.0.1.dev1/cycode/cli/commands/report/sbom/path → cycode-2.0.1.dev4/cycode/cli/commands/report/sbom}/__init__.py +0 -0
  40. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/report/sbom/common.py +0 -0
  41. {cycode-2.0.1.dev1/cycode/cli/commands/report/sbom/repository_url → cycode-2.0.1.dev4/cycode/cli/commands/report/sbom/path}/__init__.py +0 -0
  42. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/report/sbom/path/path_command.py +0 -0
  43. {cycode-2.0.1.dev1/cycode/cli/commands/scan → cycode-2.0.1.dev4/cycode/cli/commands/report/sbom/repository_url}/__init__.py +0 -0
  44. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/report/sbom/repository_url/repository_url_command.py +0 -0
  45. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/report/sbom/sbom_command.py +0 -0
  46. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/report/sbom/sbom_report_file.py +0 -0
  47. {cycode-2.0.1.dev1/cycode/cli/commands/scan/commit_history → cycode-2.0.1.dev4/cycode/cli/commands/scan}/__init__.py +0 -0
  48. {cycode-2.0.1.dev1/cycode/cli/commands/scan/path → cycode-2.0.1.dev4/cycode/cli/commands/scan/commit_history}/__init__.py +0 -0
  49. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/commit_history/commit_history_command.py +0 -0
  50. {cycode-2.0.1.dev1/cycode/cli/commands/scan/pre_commit → cycode-2.0.1.dev4/cycode/cli/commands/scan/path}/__init__.py +0 -0
  51. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/path/path_command.py +0 -0
  52. {cycode-2.0.1.dev1/cycode/cli/commands/scan/pre_receive → cycode-2.0.1.dev4/cycode/cli/commands/scan/pre_commit}/__init__.py +0 -0
  53. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/pre_commit/pre_commit_command.py +0 -0
  54. {cycode-2.0.1.dev1/cycode/cli/commands/scan/repository → cycode-2.0.1.dev4/cycode/cli/commands/scan/pre_receive}/__init__.py +0 -0
  55. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/pre_receive/pre_receive_command.py +0 -0
  56. {cycode-2.0.1.dev1/cycode/cli/commands/scan/scan_ci → cycode-2.0.1.dev4/cycode/cli/commands/scan/repository}/__init__.py +0 -0
  57. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/repository/repository_command.py +0 -0
  58. {cycode-2.0.1.dev1/cycode/cli/commands/status → cycode-2.0.1.dev4/cycode/cli/commands/scan/scan_ci}/__init__.py +0 -0
  59. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/scan_ci/ci_integrations.py +0 -0
  60. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/scan/scan_ci/scan_ci_command.py +0 -0
  61. {cycode-2.0.1.dev1/cycode/cli/commands/version → cycode-2.0.1.dev4/cycode/cli/commands/status}/__init__.py +0 -0
  62. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/commands/status/status_command.py +0 -0
  63. {cycode-2.0.1.dev1/cycode/cli/exceptions → cycode-2.0.1.dev4/cycode/cli/commands/version}/__init__.py +0 -0
  64. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/config.py +0 -0
  65. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/config.yaml +0 -0
  66. {cycode-2.0.1.dev1/cycode/cli/files_collector → cycode-2.0.1.dev4/cycode/cli/exceptions}/__init__.py +0 -0
  67. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/exceptions/custom_exceptions.py +0 -0
  68. {cycode-2.0.1.dev1/cycode/cli/files_collector/iac → cycode-2.0.1.dev4/cycode/cli/files_collector}/__init__.py +0 -0
  69. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/excluder.py +0 -0
  70. {cycode-2.0.1.dev1/cycode/cli/files_collector/models → cycode-2.0.1.dev4/cycode/cli/files_collector/iac}/__init__.py +0 -0
  71. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
  72. {cycode-2.0.1.dev1/cycode/cli/files_collector/sca → cycode-2.0.1.dev4/cycode/cli/files_collector/models}/__init__.py +0 -0
  73. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
  74. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/path_documents.py +0 -0
  75. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/repository_documents.py +0 -0
  76. {cycode-2.0.1.dev1/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.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/base_restore_dependencies.py +0 -0
  78. {cycode-2.0.1.dev1/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.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/go/restore_go_dependencies.py +0 -0
  80. {cycode-2.0.1.dev1/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.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
  82. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
  83. {cycode-2.0.1.dev1/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.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py +0 -0
  85. {cycode-2.0.1.dev1/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.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py +0 -0
  87. {cycode-2.0.1.dev1/cycode/cli/printers/tables → cycode-2.0.1.dev4/cycode/cli/files_collector/sca/ruby}/__init__.py +0 -0
  88. {cycode-2.0.1.dev1/cycode/cli/user_settings → cycode-2.0.1.dev4/cycode/cli/files_collector/sca/sbt}/__init__.py +0 -0
  89. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py +0 -0
  90. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/files_collector/zip_documents.py +0 -0
  91. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/main.py +0 -0
  92. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/printers/__init__.py +0 -0
  93. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/printers/json_printer.py +0 -0
  94. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/printers/printer_base.py +0 -0
  95. {cycode-2.0.1.dev1/cycode/cli/utils → cycode-2.0.1.dev4/cycode/cli/printers/tables}/__init__.py +0 -0
  96. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
  97. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/printers/tables/table.py +0 -0
  98. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/printers/tables/table_models.py +0 -0
  99. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/printers/tables/table_printer.py +0 -0
  100. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/printers/tables/table_printer_base.py +0 -0
  101. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/printers/text_printer.py +0 -0
  102. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/sentry.py +0 -0
  103. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/user_settings/base_file_manager.py +0 -0
  104. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/user_settings/config_file_manager.py +0 -0
  105. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/user_settings/credentials_manager.py +0 -0
  106. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/user_settings/jwt_creator.py +0 -0
  107. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/utils/enum_utils.py +0 -0
  108. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/utils/get_api_client.py +0 -0
  109. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/utils/git_proxy.py +0 -0
  110. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/utils/jwt_utils.py +0 -0
  111. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/utils/path_utils.py +0 -0
  112. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/utils/progress_bar.py +0 -0
  113. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/utils/scan_batch.py +0 -0
  114. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/utils/scan_utils.py +0 -0
  115. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/utils/shell_executor.py +0 -0
  116. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/utils/string_utils.py +0 -0
  117. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/utils/task_timer.py +0 -0
  118. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cli/utils/yaml_utils.py +0 -0
  119. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/__init__.py +0 -0
  120. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/auth_client.py +0 -0
  121. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/client_creator.py +0 -0
  122. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/config.py +0 -0
  123. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/config.yaml +0 -0
  124. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/config_dev.py +0 -0
  125. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/cycode_client.py +0 -0
  126. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/cycode_client_base.py +0 -0
  127. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/cycode_dev_based_client.py +0 -0
  128. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/cycode_token_based_client.py +0 -0
  129. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/headers.py +0 -0
  130. {cycode-2.0.1.dev1 → cycode-2.0.1.dev4}/cycode/cyclient/report_client.py +0 -0
  131. {cycode-2.0.1.dev1 → 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.dev1
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)
@@ -327,7 +329,7 @@ The Cycode CLI application offers several types of scans so that you can choose
327
329
  | `--client-id TEXT` | Specify a Cycode client ID for this specific scan execution |
328
330
  | `--show-secret BOOLEAN` | Show secrets in plain text. See [Show/Hide Secrets](#showhide-secrets) section for more details. |
329
331
  | `--soft-fail BOOLEAN` | Run scan without failing, always return a non-error status code. See [Soft Fail](#soft-fail) section for more details. |
330
- | `--severity-threshold [INFO\|LOW\|MEDIUM\|HIGH\|CRITICAL]` | Show only violations at the specified level or higher (supported for the SCA scan type only). |
332
+ | `--severity-threshold [INFO\|LOW\|MEDIUM\|HIGH\|CRITICAL]` | Show only violations at the specified level or higher. |
331
333
  | `--sca-scan` | Specify the SCA scan you wish to execute (`package-vulnerabilities`/`license-compliance`). The default is both |
332
334
  | `--monitor` | When specified, the scan results will be recorded in the knowledge graph. Please note that when working in `monitor` mode, the knowledge graph will not be updated as a result of SCM events (Push, Repo creation). (Supported for SCA scan type only). |
333
335
  | `--report` | When specified, a violations report will be generated. A URL link to the report will be printed as an output to the command execution |
@@ -287,7 +287,7 @@ The Cycode CLI application offers several types of scans so that you can choose
287
287
  | `--client-id TEXT` | Specify a Cycode client ID for this specific scan execution |
288
288
  | `--show-secret BOOLEAN` | Show secrets in plain text. See [Show/Hide Secrets](#showhide-secrets) section for more details. |
289
289
  | `--soft-fail BOOLEAN` | Run scan without failing, always return a non-error status code. See [Soft Fail](#soft-fail) section for more details. |
290
- | `--severity-threshold [INFO\|LOW\|MEDIUM\|HIGH\|CRITICAL]` | Show only violations at the specified level or higher (supported for the SCA scan type only). |
290
+ | `--severity-threshold [INFO\|LOW\|MEDIUM\|HIGH\|CRITICAL]` | Show only violations at the specified level or higher. |
291
291
  | `--sca-scan` | Specify the SCA scan you wish to execute (`package-vulnerabilities`/`license-compliance`). The default is both |
292
292
  | `--monitor` | When specified, the scan results will be recorded in the knowledge graph. Please note that when working in `monitor` mode, the knowledge graph will not be updated as a result of SCM events (Push, Repo creation). (Supported for SCA scan type only). |
293
293
  | `--report` | When specified, a violations report will be generated. A URL link to the report will be printed as an output to the command execution |
@@ -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
  )
@@ -713,20 +713,26 @@ def exclude_irrelevant_detections(
713
713
  ) -> List[Detection]:
714
714
  relevant_detections = _exclude_detections_by_exclusions_configuration(detections, scan_type)
715
715
  relevant_detections = _exclude_detections_by_scan_type(relevant_detections, scan_type, command_scan_type)
716
- return _exclude_detections_by_severity(relevant_detections, scan_type, severity_threshold)
716
+ return _exclude_detections_by_severity(relevant_detections, severity_threshold)
717
717
 
718
718
 
719
- def _exclude_detections_by_severity(
720
- detections: List[Detection], scan_type: str, severity_threshold: str
721
- ) -> List[Detection]:
722
- if scan_type != consts.SCA_SCAN_TYPE or severity_threshold is None:
719
+ def _exclude_detections_by_severity(detections: List[Detection], severity_threshold: str) -> List[Detection]:
720
+ if severity_threshold is None:
723
721
  return detections
724
722
 
725
723
  relevant_detections = []
726
724
  for detection in detections:
727
725
  severity = detection.detection_details.get('advisory_severity')
726
+ if not severity:
727
+ severity = detection.severity
728
+
728
729
  if _does_severity_match_severity_threshold(severity, severity_threshold):
729
730
  relevant_detections.append(detection)
731
+ else:
732
+ logger.debug(
733
+ 'Going to ignore violations because they are below the severity threshold, %s',
734
+ {'severity': severity, 'severity_threshold': severity_threshold},
735
+ )
730
736
 
731
737
  return relevant_detections
732
738
 
@@ -861,10 +867,11 @@ def _generate_unique_id() -> UUID:
861
867
 
862
868
  def _does_severity_match_severity_threshold(severity: str, severity_threshold: str) -> bool:
863
869
  detection_severity_value = Severity.try_get_value(severity)
864
- if detection_severity_value is None:
870
+ severity_threshold_value = Severity.try_get_value(severity_threshold)
871
+ if detection_severity_value is None or severity_threshold_value is None:
865
872
  return True
866
873
 
867
- return detection_severity_value >= Severity.try_get_value(severity_threshold)
874
+ return detection_severity_value >= severity_threshold_value
868
875
 
869
876
 
870
877
  def _get_scan_result(
@@ -66,7 +66,7 @@ from cycode.cli.utils.get_api_client import get_scan_cycode_client
66
66
  @click.option(
67
67
  '--severity-threshold',
68
68
  default=None,
69
- help='Show violations only for the specified level or higher (supported for SCA scan types only).',
69
+ help='Show violations only for the specified level or higher.',
70
70
  type=click.Choice([e.name for e in Severity]),
71
71
  required=False,
72
72
  )
@@ -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
 
@@ -43,6 +43,7 @@ class Severity(Enum):
43
43
 
44
44
  @staticmethod
45
45
  def try_get_value(name: str) -> any:
46
+ name = name.upper()
46
47
  if name not in Severity.__members__:
47
48
  return None
48
49
 
@@ -62,7 +63,7 @@ class CliError(NamedTuple):
62
63
  soft_fail: bool = False
63
64
 
64
65
 
65
- CliErrors = Dict[Type[Exception], CliError]
66
+ CliErrors = Dict[Type[BaseException], CliError]
66
67
 
67
68
 
68
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.dev1" # 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.dev1' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
File without changes