cycode 3.16.1.dev7__tar.gz → 3.16.2__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 (214) hide show
  1. {cycode-3.16.1.dev7 → cycode-3.16.2}/PKG-INFO +1 -1
  2. cycode-3.16.2/cycode/__init__.py +8 -0
  3. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/app.py +88 -15
  4. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py +22 -10
  5. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/ides/base.py +10 -4
  6. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/ides/claude_code.py +14 -11
  7. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/ides/codex.py +19 -11
  8. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/ides/cursor.py +13 -4
  9. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/session_start_command.py +17 -3
  10. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/code_scanner.py +15 -12
  11. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/scan_parameters.py +2 -0
  12. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/scan_result.py +4 -0
  13. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/consts.py +24 -0
  14. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/file_excluder.py +8 -0
  15. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/ai_security_manager_client.py +9 -3
  16. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/base_token_auth_client.py +27 -14
  17. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/cycode_client_base.py +24 -21
  18. {cycode-3.16.1.dev7 → cycode-3.16.2}/pyproject.toml +1 -1
  19. cycode-3.16.1.dev7/cycode/__init__.py +0 -1
  20. {cycode-3.16.1.dev7 → cycode-3.16.2}/LICENCE +0 -0
  21. {cycode-3.16.1.dev7 → cycode-3.16.2}/README.md +0 -0
  22. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/__main__.py +0 -0
  23. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/__init__.py +0 -0
  24. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/__init__.py +0 -0
  25. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/activation_manager.py +0 -0
  26. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/__init__.py +0 -0
  27. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/command_utils.py +0 -0
  28. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/consts.py +0 -0
  29. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/hooks_manager.py +0 -0
  30. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/ides/__init__.py +0 -0
  31. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/install_command.py +0 -0
  32. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/scan/__init__.py +0 -0
  33. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/scan/consts.py +0 -0
  34. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/scan/handlers.py +0 -0
  35. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/scan/payload.py +0 -0
  36. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/scan/policy.py +0 -0
  37. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/scan/scan_command.py +0 -0
  38. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/scan/types.py +0 -0
  39. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/scan/utils.py +0 -0
  40. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/status_command.py +0 -0
  41. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_guardrails/uninstall_command.py +0 -0
  42. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_remediation/__init__.py +0 -0
  43. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_remediation/ai_remediation_command.py +0 -0
  44. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_remediation/apply_fix.py +0 -0
  45. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ai_remediation/print_remediation.py +0 -0
  46. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/api/__init__.py +0 -0
  47. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/api/api_command.py +0 -0
  48. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/api/openapi_spec.py +0 -0
  49. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/auth/__init__.py +0 -0
  50. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/auth/auth_command.py +0 -0
  51. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/auth/auth_common.py +0 -0
  52. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/auth/auth_manager.py +0 -0
  53. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/auth/models.py +0 -0
  54. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/configure/__init__.py +0 -0
  55. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/configure/configure_command.py +0 -0
  56. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/configure/consts.py +0 -0
  57. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/configure/messages.py +0 -0
  58. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/configure/prompts.py +0 -0
  59. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ignore/__init__.py +0 -0
  60. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/ignore/ignore_command.py +0 -0
  61. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/mcp/__init__.py +0 -0
  62. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/mcp/mcp_command.py +0 -0
  63. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report/__init__.py +0 -0
  64. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report/report_command.py +0 -0
  65. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report/sbom/__init__.py +0 -0
  66. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report/sbom/common.py +0 -0
  67. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report/sbom/path/__init__.py +0 -0
  68. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report/sbom/path/path_command.py +0 -0
  69. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report/sbom/repository_url/__init__.py +0 -0
  70. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report/sbom/repository_url/repository_url_command.py +0 -0
  71. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report/sbom/sbom_command.py +0 -0
  72. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report/sbom/sbom_report_file.py +0 -0
  73. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report_import/__init__.py +0 -0
  74. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report_import/report_import_command.py +0 -0
  75. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report_import/sbom/__init__.py +0 -0
  76. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/report_import/sbom/sbom_command.py +0 -0
  77. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/sca_options.py +0 -0
  78. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/__init__.py +0 -0
  79. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/aggregation_report.py +0 -0
  80. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/commit_history/__init__.py +0 -0
  81. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/commit_history/commit_history_command.py +0 -0
  82. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/commit_range_scanner.py +0 -0
  83. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/detection_excluder.py +0 -0
  84. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/path/__init__.py +0 -0
  85. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/path/path_command.py +0 -0
  86. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/pre_commit/__init__.py +0 -0
  87. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/pre_commit/pre_commit_command.py +0 -0
  88. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/pre_push/__init__.py +0 -0
  89. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/pre_push/pre_push_command.py +0 -0
  90. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/pre_receive/__init__.py +0 -0
  91. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/pre_receive/pre_receive_command.py +0 -0
  92. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/remote_url_resolver.py +0 -0
  93. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/repository/__init__.py +0 -0
  94. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/repository/repository_command.py +0 -0
  95. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/scan_ci/__init__.py +0 -0
  96. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/scan_ci/ci_integrations.py +0 -0
  97. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/scan_ci/scan_ci_command.py +0 -0
  98. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/scan/scan_command.py +0 -0
  99. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/status/__init__.py +0 -0
  100. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/status/get_cli_status.py +0 -0
  101. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/status/models.py +0 -0
  102. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/status/status_command.py +0 -0
  103. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/apps/status/version_command.py +0 -0
  104. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/cli_types.py +0 -0
  105. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/config.py +0 -0
  106. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/console.py +0 -0
  107. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/exceptions/__init__.py +0 -0
  108. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/exceptions/custom_exceptions.py +0 -0
  109. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/exceptions/handle_ai_remediation_errors.py +0 -0
  110. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/exceptions/handle_auth_errors.py +0 -0
  111. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/exceptions/handle_errors.py +0 -0
  112. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/exceptions/handle_report_sbom_errors.py +0 -0
  113. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/exceptions/handle_scan_errors.py +0 -0
  114. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/__init__.py +0 -0
  115. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/commit_range_documents.py +0 -0
  116. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/documents_walk_ignore.py +0 -0
  117. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/iac/__init__.py +0 -0
  118. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
  119. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/models/__init__.py +0 -0
  120. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
  121. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/path_documents.py +0 -0
  122. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/repository_documents.py +0 -0
  123. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/__init__.py +0 -0
  124. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/base_restore_dependencies.py +0 -0
  125. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/go/__init__.py +0 -0
  126. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/go/restore_go_dependencies.py +0 -0
  127. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/maven/__init__.py +0 -0
  128. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
  129. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
  130. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/npm/__init__.py +0 -0
  131. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/npm/restore_deno_dependencies.py +0 -0
  132. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py +0 -0
  133. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/npm/restore_pnpm_dependencies.py +0 -0
  134. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/npm/restore_yarn_dependencies.py +0 -0
  135. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/nuget/__init__.py +0 -0
  136. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py +0 -0
  137. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/php/__init__.py +0 -0
  138. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/php/restore_composer_dependencies.py +0 -0
  139. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/python/__init__.py +0 -0
  140. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/python/restore_pipenv_dependencies.py +0 -0
  141. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/python/restore_poetry_dependencies.py +0 -0
  142. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/python/restore_uv_dependencies.py +0 -0
  143. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/ruby/__init__.py +0 -0
  144. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py +0 -0
  145. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/sbt/__init__.py +0 -0
  146. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py +0 -0
  147. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/sca/sca_file_collector.py +0 -0
  148. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/walk_ignore.py +0 -0
  149. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/files_collector/zip_documents.py +0 -0
  150. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/logger.py +0 -0
  151. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/main.py +0 -0
  152. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/models.py +0 -0
  153. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/__init__.py +0 -0
  154. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/console_printer.py +0 -0
  155. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/json_printer.py +0 -0
  156. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/printer_base.py +0 -0
  157. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/rich_printer.py +0 -0
  158. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/tables/__init__.py +0 -0
  159. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
  160. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/tables/table.py +0 -0
  161. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/tables/table_models.py +0 -0
  162. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/tables/table_printer.py +0 -0
  163. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/tables/table_printer_base.py +0 -0
  164. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/text_printer.py +0 -0
  165. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/utils/__init__.py +0 -0
  166. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/utils/code_snippet_syntax.py +0 -0
  167. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/utils/detection_data.py +0 -0
  168. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/utils/detection_ordering/__init__.py +0 -0
  169. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/utils/detection_ordering/common_ordering.py +0 -0
  170. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/utils/detection_ordering/sca_ordering.py +0 -0
  171. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/printers/utils/rich_helpers.py +0 -0
  172. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/user_settings/__init__.py +0 -0
  173. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/user_settings/base_file_manager.py +0 -0
  174. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/user_settings/config_file_manager.py +0 -0
  175. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/user_settings/configuration_manager.py +0 -0
  176. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/user_settings/credentials_manager.py +0 -0
  177. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/user_settings/jwt_creator.py +0 -0
  178. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/__init__.py +0 -0
  179. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/binary_utils.py +0 -0
  180. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/enum_utils.py +0 -0
  181. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/get_api_client.py +0 -0
  182. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/git_proxy.py +0 -0
  183. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/ignore_utils.py +0 -0
  184. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/jwt_utils.py +0 -0
  185. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/path_utils.py +0 -0
  186. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/progress_bar.py +0 -0
  187. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/scan_batch.py +0 -0
  188. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/scan_utils.py +0 -0
  189. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/shell_executor.py +0 -0
  190. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/string_utils.py +0 -0
  191. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/task_timer.py +0 -0
  192. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/url_utils.py +0 -0
  193. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/version_checker.py +0 -0
  194. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cli/utils/yaml_utils.py +0 -0
  195. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/config.py +0 -0
  196. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/__init__.py +0 -0
  197. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/ai_security_manager_service_config.py +0 -0
  198. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/auth_client.py +0 -0
  199. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/cli_activation_client.py +0 -0
  200. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/client_creator.py +0 -0
  201. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/config.py +0 -0
  202. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/config_dev.py +0 -0
  203. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/cycode_client.py +0 -0
  204. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/cycode_dev_based_client.py +0 -0
  205. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/cycode_oidc_based_client.py +0 -0
  206. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/cycode_token_based_client.py +0 -0
  207. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/headers.py +0 -0
  208. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/import_sbom_client.py +0 -0
  209. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/logger.py +0 -0
  210. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/models.py +0 -0
  211. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/report_client.py +0 -0
  212. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/scan_client.py +0 -0
  213. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/cyclient/scan_config_base.py +0 -0
  214. {cycode-3.16.1.dev7 → cycode-3.16.2}/cycode/logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycode
3
- Version: 3.16.1.dev7
3
+ Version: 3.16.2
4
4
  Summary: Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning.
5
5
  License-Expression: MIT
6
6
  License-File: LICENCE
@@ -0,0 +1,8 @@
1
+ import time as _time
2
+
3
+ # Unix-epoch wall clock captured at the earliest possible moment of CLI
4
+ # startup. Sent as `scan_parameters.cli_start_time` so the server can compute
5
+ # end-to-end scan duration from the moment the user actually triggered it.
6
+ _BOOT_WALL: float = _time.time()
7
+
8
+ __version__ = '3.16.2' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
@@ -1,3 +1,4 @@
1
+ import importlib
1
2
  import logging
2
3
  import sys
3
4
  from typing import Annotated, Optional
@@ -10,12 +11,7 @@ from typer._completion_shared import Shells
10
11
  from typer.completion import install_callback, show_callback
11
12
 
12
13
  from cycode import __version__
13
- from cycode.cli.apps import ai_guardrails, ai_remediation, auth, configure, ignore, report, report_import, scan, status
14
14
  from cycode.cli.apps.api import get_platform_group
15
-
16
- if sys.version_info >= (3, 10):
17
- from cycode.cli.apps import mcp
18
-
19
15
  from cycode.cli.cli_types import OutputTypeOption
20
16
  from cycode.cli.consts import CLI_CONTEXT_SETTINGS
21
17
  from cycode.cli.printers import ConsolePrinter
@@ -46,17 +42,88 @@ app = typer.Typer(
46
42
  add_completion=False, # we add it manually to control the rich help panel
47
43
  )
48
44
 
49
- app.add_typer(ai_guardrails.app)
50
- app.add_typer(ai_remediation.app)
51
- app.add_typer(auth.app)
52
- app.add_typer(configure.app)
53
- app.add_typer(ignore.app)
54
- app.add_typer(report.app)
55
- app.add_typer(report_import.app)
56
- app.add_typer(scan.app)
57
- app.add_typer(status.app)
45
+ # Top-level subcommand → module providing its Typer app. Peeking at sys.argv
46
+ # lets us import only the invoked subapp on the hot path (e.g.
47
+ # `cycode ai-guardrails scan`), skipping ~300ms of unrelated imports.
48
+ _SUBAPP_MODULES: dict[str, str] = {
49
+ 'ai-guardrails': 'cycode.cli.apps.ai_guardrails',
50
+ 'ai-remediation': 'cycode.cli.apps.ai_remediation',
51
+ 'auth': 'cycode.cli.apps.auth',
52
+ 'configure': 'cycode.cli.apps.configure',
53
+ 'ignore': 'cycode.cli.apps.ignore',
54
+ 'report': 'cycode.cli.apps.report',
55
+ 'import': 'cycode.cli.apps.report_import',
56
+ 'scan': 'cycode.cli.apps.scan',
57
+ 'status': 'cycode.cli.apps.status',
58
+ }
58
59
  if sys.version_info >= (3, 10):
59
- app.add_typer(mcp.app)
60
+ _SUBAPP_MODULES['mcp'] = 'cycode.cli.apps.mcp'
61
+
62
+ # Aliases: alternate spellings that resolve to a primary subcommand key.
63
+ _SUBAPP_ALIASES: dict[str, str] = {
64
+ 'ai_remediation': 'ai-remediation', # backward-compat underscore form
65
+ 'version': 'status',
66
+ }
67
+
68
+ # Root-level options that consume a following value; argv-peek must skip past
69
+ # both the option and its value when scanning for the first positional arg.
70
+ _ROOT_OPTS_WITH_VALUE = frozenset(
71
+ {
72
+ '--output',
73
+ '-o',
74
+ '--user-agent',
75
+ '--client-secret',
76
+ '--client-id',
77
+ '--id-token',
78
+ '--show-completion',
79
+ }
80
+ )
81
+
82
+
83
+ def _detect_invocation() -> tuple[Optional[str], Optional[str]]:
84
+ """Return (top-level-subapp, second-level-subcommand) parsed from sys.argv.
85
+
86
+ Both values may be None: when no positional arg matches a known subapp,
87
+ or when the user only provided a top-level subcommand.
88
+ """
89
+ positionals = []
90
+ args = sys.argv[1:]
91
+ i = 0
92
+ while i < len(args):
93
+ arg = args[i]
94
+ if arg in _ROOT_OPTS_WITH_VALUE:
95
+ i += 2
96
+ elif arg.startswith('-'):
97
+ # Any flag form: short, long, --key=value, or '--' marker. Skip the token only.
98
+ i += 1
99
+ else:
100
+ positionals.append(arg)
101
+ if len(positionals) >= 2:
102
+ break
103
+ i += 1
104
+ subapp = positionals[0] if positionals else None
105
+ subapp = _SUBAPP_ALIASES.get(subapp, subapp)
106
+ if subapp not in _SUBAPP_MODULES:
107
+ return None, None
108
+ subcommand = positionals[1] if len(positionals) >= 2 else None
109
+ return subapp, subcommand
110
+
111
+
112
+ # Computed once at import; reused by lazy registration and the version-checker skip.
113
+ _INVOKED_SUBAPP, _INVOKED_SUBCOMMAND = _detect_invocation()
114
+
115
+
116
+ def _register_subapps(only: Optional[str]) -> None:
117
+ if only is not None:
118
+ app.add_typer(importlib.import_module(_SUBAPP_MODULES[only]).app)
119
+ return
120
+ # Cold path (--help, completion, unknown subcommand): load all modules so
121
+ # root help lists everything. Deduplicate since aliases share modules.
122
+ for module_path in dict.fromkeys(_SUBAPP_MODULES.values()):
123
+ app.add_typer(importlib.import_module(module_path).app)
124
+
125
+
126
+ _register_subapps(_INVOKED_SUBAPP)
60
127
 
61
128
  # Register the `platform` command group (dynamically built from the OpenAPI spec).
62
129
  # The group itself is constructed cheaply at import time; the spec is only fetched
@@ -81,6 +148,12 @@ typer.main.get_group = _get_group_with_platform
81
148
 
82
149
 
83
150
  def check_latest_version_on_close(ctx: typer.Context) -> None:
151
+ # Skip on `cycode ai-guardrails scan` — it emits JSON to stdout, so an
152
+ # upgrade notice would corrupt the response. Human-driven sibling commands
153
+ # (install, uninstall, status, session-start) still get the notice.
154
+ if (_INVOKED_SUBAPP, _INVOKED_SUBCOMMAND) == ('ai-guardrails', 'scan'):
155
+ return
156
+
84
157
  output = ctx.obj.get('output')
85
158
  # don't print anything if the output is JSON
86
159
  if output == OutputTypeOption.JSON:
@@ -26,13 +26,26 @@ def load_plugin_json(path: Path) -> Optional[dict]:
26
26
  return None
27
27
 
28
28
 
29
+ def build_global_config_file(path: Path, mcp_servers: Optional[dict]) -> Optional[dict]:
30
+ """Wrap a global (non-plugin) MCP config into the session-context file shape.
31
+
32
+ Returns ``{"path": <full path>, "content": <{"mcpServers": ...} JSON>}`` when
33
+ there are servers, else ``None``. ``content`` is normalized to the canonical
34
+ ``{"mcpServers": {...}}`` shape, dropping everything else in the source file.
35
+ """
36
+ servers = mcp_servers or {}
37
+ if not servers:
38
+ return None
39
+ return {'path': str(path), 'content': json.dumps({'mcpServers': servers})}
40
+
41
+
29
42
  def walk_enabled_plugins(
30
43
  plugin_entries: dict[str, Any],
31
44
  is_enabled: Callable[[Any], bool],
32
45
  locate_dir: Callable[[str, str], Optional[Path]],
33
46
  read_plugin: Callable[[Path], tuple[dict, dict]],
34
- ) -> tuple[dict, dict]:
35
- """Iterate enabled plugins; merge their MCP servers and metadata.
47
+ ) -> dict:
48
+ """Iterate enabled plugins and build their inventory metadata.
36
49
 
37
50
  Args:
38
51
  plugin_entries: ``{<plugin>@<marketplace>: settings}`` map from the IDE config.
@@ -42,13 +55,13 @@ def walk_enabled_plugins(
42
55
  filesystem path or None if it can't be resolved.
43
56
  read_plugin: given the plugin path, returns ``(entry_fields, servers)``:
44
57
  ``entry_fields`` are extra metadata to attach to the inventory entry
45
- (name/version/description/...), ``servers`` are MCP servers contributed.
58
+ (name/version/description/...); ``servers`` are the plugin's MCP
59
+ servers, which ``read_plugin`` uses to derive that metadata.
46
60
 
47
- Returns ``(merged_mcp_servers, enriched_plugins)``. Plugin keys without
48
- ``@`` (or that fail to resolve to a directory) still appear in the
49
- inventory with just ``{'enabled': True}`` so we don't silently drop them.
61
+ Returns ``enriched_plugins``. Plugin keys without ``@`` (or that fail to
62
+ resolve to a directory) still appear in the inventory with just
63
+ ``{'enabled': True}`` so we don't silently drop them.
50
64
  """
51
- merged_mcp: dict = {}
52
65
  enriched: dict = {}
53
66
 
54
67
  for plugin_key, settings in plugin_entries.items():
@@ -66,8 +79,7 @@ def walk_enabled_plugins(
66
79
  if plugin_dir is None:
67
80
  continue
68
81
 
69
- plugin_fields, servers = read_plugin(plugin_dir)
82
+ plugin_fields, _ = read_plugin(plugin_dir)
70
83
  entry.update(plugin_fields)
71
- merged_mcp.update(servers)
72
84
 
73
- return merged_mcp, enriched
85
+ return enriched
@@ -167,10 +167,16 @@ class IDE(ABC):
167
167
  """
168
168
  return None
169
169
 
170
- def get_session_context(self) -> tuple[dict, dict]:
171
- """Return ``(mcp_servers, enabled_plugins)`` for session-context reporting.
170
+ def get_session_context(self) -> tuple[Optional[dict], dict]:
171
+ """Return ``(global_config_file, enabled_plugins)`` for session-context reporting.
172
172
 
173
- Default: empty dicts (no plugin system, no discoverable MCP config).
173
+ ``global_config_file`` is the IDE's global (non-plugin) MCP config as
174
+ ``{"path": <full path>, "content": <normalized {"mcpServers": ...} JSON>}``,
175
+ or ``None`` when there is no global MCP config. ``enabled_plugins`` maps each
176
+ enabled plugin key to its metadata (including its own ``mcp_config_file``
177
+ content and ``mcp_config_file_path``).
178
+
179
+ Default: ``(None, {})`` (no plugin system, no discoverable MCP config).
174
180
  Override to surface MCP/plugin inventory.
175
181
  """
176
- return {}, {}
182
+ return None, {}
@@ -7,7 +7,11 @@ from pathlib import Path
7
7
  from typing import ClassVar, Optional
8
8
 
9
9
  from cycode.cli.apps.ai_guardrails.consts import CYCODE_SCAN_PROMPT_COMMAND, CYCODE_SESSION_START_COMMAND
10
- from cycode.cli.apps.ai_guardrails.ides._plugin_utils import load_plugin_json, walk_enabled_plugins
10
+ from cycode.cli.apps.ai_guardrails.ides._plugin_utils import (
11
+ build_global_config_file,
12
+ load_plugin_json,
13
+ walk_enabled_plugins,
14
+ )
11
15
  from cycode.cli.apps.ai_guardrails.ides.base import IDE, DecisionAction, HookDecision
12
16
  from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
13
17
  from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
@@ -184,14 +188,17 @@ def _read_claude_plugin(plugin_dir: Path) -> tuple[dict, dict]:
184
188
  if field in manifest:
185
189
  entry[field] = manifest[field]
186
190
 
187
- mcp_config = load_plugin_json(plugin_dir / '.mcp.json') or {}
191
+ mcp_config_path = plugin_dir / '.mcp.json'
192
+ mcp_config = load_plugin_json(mcp_config_path) or {}
188
193
  servers: dict = mcp_config.get('mcpServers') or {}
189
194
  if servers:
190
195
  entry['mcp_server_names'] = list(servers.keys())
196
+ entry['mcp_config_file_path'] = str(mcp_config_path)
197
+ entry['mcp_config_file'] = json.dumps(mcp_config)
191
198
  return entry, servers
192
199
 
193
200
 
194
- def resolve_plugins(settings: dict) -> tuple[dict, dict]:
201
+ def resolve_plugins(settings: dict) -> dict:
195
202
  """Walk Claude Code's ``enabledPlugins`` via the shared plugin walker.
196
203
 
197
204
  Each enabled plugin's marketplace is resolved through
@@ -354,15 +361,11 @@ class ClaudeCode(IDE):
354
361
  config = load_claude_config()
355
362
  return _email_from_config(config) if config else None
356
363
 
357
- def get_session_context(self) -> tuple[dict, dict]:
364
+ def get_session_context(self) -> tuple[Optional[dict], dict]:
358
365
  config = load_claude_config()
359
- mcp_servers: dict = dict(get_mcp_servers(config) or {}) if config else {}
366
+ global_config_file = build_global_config_file(_CLAUDE_CONFIG_PATH, get_mcp_servers(config)) if config else None
360
367
 
361
368
  settings = load_claude_settings()
362
- if settings:
363
- plugin_mcp, enriched_plugins = resolve_plugins(settings)
364
- mcp_servers.update(plugin_mcp)
365
- else:
366
- enriched_plugins = {}
369
+ enriched_plugins = resolve_plugins(settings) if settings else {}
367
370
 
368
- return mcp_servers, enriched_plugins
371
+ return global_config_file, enriched_plugins
@@ -14,7 +14,11 @@ else: # pragma: no cover - py<3.11 fallback
14
14
  import tomli as tomllib
15
15
 
16
16
  from cycode.cli.apps.ai_guardrails.consts import CYCODE_SCAN_PROMPT_COMMAND, CYCODE_SESSION_START_COMMAND
17
- from cycode.cli.apps.ai_guardrails.ides._plugin_utils import load_plugin_json, walk_enabled_plugins
17
+ from cycode.cli.apps.ai_guardrails.ides._plugin_utils import (
18
+ build_global_config_file,
19
+ load_plugin_json,
20
+ walk_enabled_plugins,
21
+ )
18
22
  from cycode.cli.apps.ai_guardrails.ides.base import IDE, DecisionAction, HookDecision
19
23
  from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
20
24
  from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
@@ -129,16 +133,19 @@ def _read_codex_plugin(plugin_dir: Path) -> tuple[dict, dict]:
129
133
  mcp_ref = manifest.get('mcpServers')
130
134
  if not mcp_ref:
131
135
  return entry, {}
132
- mcp_doc = load_plugin_json(plugin_dir / mcp_ref) or {}
136
+ mcp_config_path = plugin_dir / mcp_ref
137
+ mcp_doc = load_plugin_json(mcp_config_path) or {}
133
138
  servers = mcp_doc.get('mcpServers', mcp_doc)
134
139
  if not isinstance(servers, dict):
135
140
  servers = {}
136
141
  if servers:
137
142
  entry['mcp_server_names'] = list(servers.keys())
143
+ entry['mcp_config_file_path'] = str(mcp_config_path)
144
+ entry['mcp_config_file'] = json.dumps(mcp_doc)
138
145
  return entry, servers
139
146
 
140
147
 
141
- def _resolve_codex_plugins(config: dict) -> tuple[dict, dict]:
148
+ def _resolve_codex_plugins(config: dict) -> dict:
142
149
  """Walk enabled ``[plugins."<plugin>@<marketplace>"]`` entries."""
143
150
  return walk_enabled_plugins(
144
151
  plugin_entries=config.get('plugins') or {},
@@ -297,13 +304,14 @@ class Codex(IDE):
297
304
  def get_user_email(self) -> Optional[str]:
298
305
  return _email_from_auth()
299
306
 
300
- def get_session_context(self) -> tuple[dict, dict]:
307
+ def get_session_context(self) -> tuple[Optional[dict], dict]:
301
308
  config = _load_codex_config()
302
309
  if not config:
303
- return {}, {}
304
- # Codex stores MCP servers under `[mcp_servers.<name>]`. Plugin-contributed
305
- # servers (via `[plugins."<plugin>@<marketplace>"]`) merge on top.
306
- mcp_servers: dict = dict(config.get('mcp_servers') or {})
307
- plugin_mcp, enriched_plugins = _resolve_codex_plugins(config)
308
- mcp_servers.update(plugin_mcp)
309
- return mcp_servers, enriched_plugins
310
+ return None, {}
311
+ # Codex stores MCP servers under `[mcp_servers.<name>]`; the global config
312
+ # file becomes its own session-context file. Plugins (via
313
+ # `[plugins."<plugin>@<marketplace>"]`) carry their own config files.
314
+ config_path = _codex_config_toml_path('user')
315
+ global_config_file = build_global_config_file(config_path, config.get('mcp_servers'))
316
+ enriched_plugins = _resolve_codex_plugins(config)
317
+ return global_config_file, enriched_plugins
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
  from typing import ClassVar, Optional
7
7
 
8
8
  from cycode.cli.apps.ai_guardrails.consts import CYCODE_SCAN_PROMPT_COMMAND, CYCODE_SESSION_START_COMMAND
9
+ from cycode.cli.apps.ai_guardrails.ides._plugin_utils import build_global_config_file
9
10
  from cycode.cli.apps.ai_guardrails.ides.base import IDE, DecisionAction, HookDecision
10
11
  from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
11
12
  from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
@@ -39,9 +40,14 @@ def _user_hooks_dir() -> Path:
39
40
  return Path.home() / '.config' / 'Cursor'
40
41
 
41
42
 
43
+ def _cursor_mcp_config_path() -> Path:
44
+ """User-scope Cursor MCP config path (``~/.cursor/mcp.json``, all platforms)."""
45
+ return Path.home() / '.cursor' / _MCP_CONFIG_FILENAME
46
+
47
+
42
48
  def _load_cursor_mcp_config(config_path: Optional[Path] = None) -> Optional[dict]:
43
49
  """Load and parse `~/.cursor/mcp.json`. Returns None if missing/invalid."""
44
- path = config_path or (Path.home() / '.cursor' / _MCP_CONFIG_FILENAME)
50
+ path = config_path or _cursor_mcp_config_path()
45
51
  if not path.exists():
46
52
  logger.debug('Cursor MCP config file not found, %s', {'path': str(path)})
47
53
  return None
@@ -113,7 +119,10 @@ class Cursor(IDE):
113
119
  ide_version=raw_payload.get('cursor_version'),
114
120
  )
115
121
 
116
- def get_session_context(self) -> tuple[dict, dict]:
122
+ def get_session_context(self) -> tuple[Optional[dict], dict]:
117
123
  config = _load_cursor_mcp_config()
118
- mcp_servers = dict((config or {}).get('mcpServers') or {}) if config else {}
119
- return mcp_servers, {}
124
+ if not config:
125
+ return None, {}
126
+ config_path = _cursor_mcp_config_path()
127
+ global_config_file = build_global_config_file(config_path, config.get('mcpServers'))
128
+ return global_config_file, {}
@@ -1,5 +1,8 @@
1
1
  """Handle AI guardrails session start: auth, conversation creation, session context."""
2
2
 
3
+ import os
4
+ import platform
5
+ import socket
3
6
  import sys
4
7
  from typing import TYPE_CHECKING, Annotated, Optional
5
8
 
@@ -20,14 +23,25 @@ if TYPE_CHECKING:
20
23
  logger = get_logger('AI Guardrails')
21
24
 
22
25
 
26
+ def _get_logged_in_user() -> Optional[str]:
27
+ """Best-effort OS account name (whoami). None if it can't be resolved."""
28
+ try:
29
+ return os.getlogin()
30
+ except Exception:
31
+ return None
32
+
33
+
23
34
  def _report_session_context(ai_client: 'AISecurityManagerClient', ide: IDE, user_email: Optional[str]) -> None:
24
35
  """Report IDE session context to the AI security manager. Never raises."""
25
36
  try:
26
- mcp_servers, enabled_plugins = ide.get_session_context()
27
- if not mcp_servers and not enabled_plugins:
37
+ global_config_file, enabled_plugins = ide.get_session_context()
38
+ if not global_config_file and not enabled_plugins:
28
39
  return
29
40
  ai_client.report_session_context(
30
- mcp_servers=mcp_servers,
41
+ hostname=socket.gethostname(),
42
+ platform=platform.system(),
43
+ logged_in_user=_get_logged_in_user(),
44
+ global_config_file=global_config_file,
31
45
  enabled_plugins=enabled_plugins,
32
46
  user_email=user_email,
33
47
  )
@@ -204,18 +204,21 @@ def _get_scan_documents_thread_func(
204
204
  'zip_file_size': zip_file_size,
205
205
  },
206
206
  )
207
- report_scan_status(
208
- cycode_client,
209
- scan_type,
210
- scan_id,
211
- scan_completed,
212
- relevant_detections_count,
213
- detections_count,
214
- len(batch),
215
- zip_file_size,
216
- command_scan_type,
217
- error_message,
218
- )
207
+ # Sync flows already received the full result inline; only async flows
208
+ # need a separate status report to signal polling completion.
209
+ if not should_use_sync_flow:
210
+ report_scan_status(
211
+ cycode_client,
212
+ scan_type,
213
+ scan_id,
214
+ scan_completed,
215
+ relevant_detections_count,
216
+ detections_count,
217
+ len(batch),
218
+ zip_file_size,
219
+ command_scan_type,
220
+ error_message,
221
+ )
219
222
 
220
223
  return scan_id, error, local_scan_result
221
224
 
@@ -2,6 +2,7 @@ from typing import Optional
2
2
 
3
3
  import typer
4
4
 
5
+ from cycode import _BOOT_WALL
5
6
  from cycode.cli.apps.scan.remote_url_resolver import get_remote_url_scan_parameter
6
7
  from cycode.cli.utils.scan_utils import generate_unique_scan_id
7
8
  from cycode.logger import get_logger
@@ -17,6 +18,7 @@ def _get_default_scan_parameters(ctx: typer.Context) -> dict:
17
18
  'license_compliance': ctx.obj.get('license-compliance'),
18
19
  'command_type': ctx.info_name.replace('-', '_'), # save backward compatibility
19
20
  'aggregation_id': str(generate_unique_scan_id()),
21
+ 'cli_start_time': _BOOT_WALL,
20
22
  }
21
23
 
22
24
 
@@ -189,6 +189,10 @@ def enrich_scan_result_with_data_from_detection_rules(
189
189
  for detection in detections_per_file.detections:
190
190
  detection_rule_ids.add(detection.detection_rule_id)
191
191
 
192
+ if not detection_rule_ids:
193
+ logger.debug('No detections to enrich, skipping detection_rules fetch')
194
+ return
195
+
192
196
  detection_rules = cycode_client.get_detection_rules(detection_rule_ids)
193
197
  detection_rules_by_id = {detection_rule.detection_rule_id: detection_rule for detection_rule in detection_rules}
194
198
 
@@ -53,6 +53,30 @@ SECRET_SCAN_FILE_EXTENSIONS_TO_IGNORE = (
53
53
  '.iso',
54
54
  )
55
55
 
56
+ # Fallback block-list used for SAST only when the server does not return scannable extensions
57
+ # (e.g. when the customer has custom rules, any text file is scannable). These are non-source
58
+ # data formats that can slip past binary detection (the EICAR test file and ClamAV signature
59
+ # databases are plain ASCII) and may be quarantined by object-storage antivirus after upload.
60
+ SAST_SCAN_FILE_EXTENSIONS_TO_IGNORE = (
61
+ '.bin',
62
+ '.cvd',
63
+ '.cld',
64
+ '.cud',
65
+ '.hdb',
66
+ '.hsb',
67
+ '.mdb',
68
+ '.msb',
69
+ '.ndb',
70
+ '.ndu',
71
+ '.ldb',
72
+ '.ldu',
73
+ '.idb',
74
+ '.fp',
75
+ '.sfp',
76
+ '.ign',
77
+ '.ign2',
78
+ )
79
+
56
80
  SCA_CONFIGURATION_SCAN_SUPPORTED_FILES = ( # keep in lowercase
57
81
  'cargo.lock',
58
82
  'cargo.toml',
@@ -63,7 +63,10 @@ class Excluder:
63
63
  }
64
64
  self._non_scannable_extensions: dict[str, tuple[str, ...]] = {
65
65
  consts.SECRET_SCAN_TYPE: consts.SECRET_SCAN_FILE_EXTENSIONS_TO_IGNORE,
66
+ consts.SAST_SCAN_TYPE: consts.SAST_SCAN_FILE_EXTENSIONS_TO_IGNORE,
66
67
  }
68
+ # Tracks scan types for which the SAST fallback log has already been emitted (log once, not per file)
69
+ self._logged_sast_fallback = False
67
70
 
68
71
  def apply_scan_config(self, scan_type: str, scan_config: 'models.ScanConfiguration') -> None:
69
72
  if scan_config.scannable_extensions:
@@ -86,6 +89,11 @@ class Excluder:
86
89
 
87
90
  non_scannable_extensions = self._non_scannable_extensions.get(scan_type)
88
91
  if non_scannable_extensions:
92
+ # For SAST, reaching the block-list means the server returned no scannable extensions
93
+ # (e.g. custom rules, or no remote config). Log once so this is diagnosable.
94
+ if scan_type == consts.SAST_SCAN_TYPE and not self._logged_sast_fallback:
95
+ self._logged_sast_fallback = True
96
+ logger.debug('No scannable extensions provided for SAST; falling back to the built-in ignore list')
89
97
  return not filename.endswith(non_scannable_extensions)
90
98
 
91
99
  return True
@@ -93,15 +93,21 @@ class AISecurityManagerClient:
93
93
 
94
94
  def report_session_context(
95
95
  self,
96
- mcp_servers: Optional[dict] = None,
96
+ hostname: Optional[str] = None,
97
+ platform: Optional[str] = None,
98
+ logged_in_user: Optional[str] = None,
99
+ global_config_file: Optional[dict] = None,
97
100
  enabled_plugins: Optional[dict] = None,
98
101
  user_email: Optional[str] = None,
99
102
  ) -> None:
100
103
  """Report session context to the backend."""
101
104
  body: dict = {
102
- 'mcp_servers': mcp_servers,
103
- 'enabled_plugins': enabled_plugins,
105
+ 'hostname': hostname,
106
+ 'platform': platform,
107
+ 'logged_in_user': logged_in_user,
104
108
  'user_email': user_email,
109
+ 'global_config_file': global_config_file,
110
+ 'enabled_plugins': enabled_plugins,
105
111
  }
106
112
 
107
113
  try:
@@ -24,19 +24,10 @@ class BaseTokenAuthClient(CycodeClient, ABC):
24
24
  self.client_id = client_id
25
25
 
26
26
  self._credentials_manager = CredentialsManager()
27
- # load cached access token
28
- access_token, expires_in, creator = self._credentials_manager.get_access_token()
29
-
30
- self._access_token = self._expires_in = None
31
- expected_creator = self._create_jwt_creator()
32
- if creator == expected_creator:
33
- # we must be sure that cached access token is created using the same client id and client secret.
34
- # because client id and client secret could be passed via command, via env vars or via config file.
35
- # we must not use cached access token if client id or client secret was changed.
36
- self._access_token = access_token
37
- self._expires_in = arrow.get(expires_in) if expires_in else None
38
-
27
+ self._access_token = None
28
+ self._expires_in = None
39
29
  self._lock = Lock()
30
+ self._load_token_from_disk()
40
31
 
41
32
  def get_access_token(self) -> str:
42
33
  with self._lock:
@@ -51,8 +42,30 @@ class BaseTokenAuthClient(CycodeClient, ABC):
51
42
  self._credentials_manager.update_access_token(None, None, None)
52
43
 
53
44
  def refresh_access_token_if_needed(self) -> None:
54
- if self._access_token is None or self._expires_in is None or arrow.utcnow() >= self._expires_in:
55
- self.refresh_access_token()
45
+ if self._has_valid_token():
46
+ return
47
+ # Re-check disk before doing the network refresh: another client instance
48
+ # in this process may have already refreshed and persisted a fresh token.
49
+ self._load_token_from_disk()
50
+ if self._has_valid_token():
51
+ return
52
+ self.refresh_access_token()
53
+
54
+ def _has_valid_token(self) -> bool:
55
+ return self._access_token is not None and self._expires_in is not None and arrow.utcnow() < self._expires_in
56
+
57
+ def _load_token_from_disk(self) -> None:
58
+ access_token, expires_in, creator = self._credentials_manager.get_access_token()
59
+ expected_creator = self._create_jwt_creator()
60
+ # We must be sure that cached access token is created using the same client id and client secret.
61
+ # Because client id and client secret could be passed via command, via env vars or via config file.
62
+ # We must not use cached access token if client id or client secret was changed.
63
+ if creator == expected_creator and access_token:
64
+ self._access_token = access_token
65
+ self._expires_in = arrow.get(expires_in) if expires_in else None
66
+ else:
67
+ self._access_token = None
68
+ self._expires_in = None
56
69
 
57
70
  def refresh_access_token(self) -> None:
58
71
  auth_response = self._request_new_access_token()