cycode 3.18.1.dev2__tar.gz → 3.18.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 (217) hide show
  1. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/PKG-INFO +30 -2
  2. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/README.md +29 -1
  3. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/__init__.py +1 -1
  4. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/consts.py +7 -2
  5. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/ides/copilot.py +1 -3
  6. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/install_command.py +7 -7
  7. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/scan/handlers.py +88 -25
  8. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/scan/scan_command.py +4 -0
  9. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/scan/types.py +9 -0
  10. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/host_info.py +52 -0
  11. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/pyproject.toml +1 -1
  12. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/LICENCE +0 -0
  13. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/__main__.py +0 -0
  14. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/__init__.py +0 -0
  15. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/app.py +0 -0
  16. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/__init__.py +0 -0
  17. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/activation_manager.py +0 -0
  18. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/__init__.py +0 -0
  19. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/command_utils.py +0 -0
  20. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/hooks_manager.py +0 -0
  21. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/ides/__init__.py +0 -0
  22. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py +0 -0
  23. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/ides/base.py +0 -0
  24. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/ides/claude_code.py +0 -0
  25. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/ides/codex.py +0 -0
  26. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/ides/cursor.py +0 -0
  27. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/scan/__init__.py +0 -0
  28. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/scan/consts.py +0 -0
  29. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/scan/payload.py +0 -0
  30. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/scan/policy.py +0 -0
  31. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/scan/utils.py +0 -0
  32. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/session_start_command.py +0 -0
  33. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/status_command.py +0 -0
  34. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_guardrails/uninstall_command.py +0 -0
  35. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_remediation/__init__.py +0 -0
  36. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_remediation/ai_remediation_command.py +0 -0
  37. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_remediation/apply_fix.py +0 -0
  38. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ai_remediation/print_remediation.py +0 -0
  39. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/api/__init__.py +0 -0
  40. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/api/api_command.py +0 -0
  41. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/api/openapi_spec.py +0 -0
  42. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/auth/__init__.py +0 -0
  43. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/auth/auth_command.py +0 -0
  44. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/auth/auth_common.py +0 -0
  45. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/auth/auth_manager.py +0 -0
  46. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/auth/models.py +0 -0
  47. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/configure/__init__.py +0 -0
  48. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/configure/configure_command.py +0 -0
  49. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/configure/consts.py +0 -0
  50. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/configure/messages.py +0 -0
  51. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/configure/prompts.py +0 -0
  52. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ignore/__init__.py +0 -0
  53. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/ignore/ignore_command.py +0 -0
  54. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/mcp/__init__.py +0 -0
  55. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/mcp/mcp_command.py +0 -0
  56. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report/__init__.py +0 -0
  57. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report/report_command.py +0 -0
  58. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report/sbom/__init__.py +0 -0
  59. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report/sbom/common.py +0 -0
  60. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report/sbom/path/__init__.py +0 -0
  61. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report/sbom/path/path_command.py +0 -0
  62. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report/sbom/repository_url/__init__.py +0 -0
  63. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report/sbom/repository_url/repository_url_command.py +0 -0
  64. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report/sbom/sbom_command.py +0 -0
  65. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report/sbom/sbom_report_file.py +0 -0
  66. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report_import/__init__.py +0 -0
  67. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report_import/report_import_command.py +0 -0
  68. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report_import/sbom/__init__.py +0 -0
  69. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/report_import/sbom/sbom_command.py +0 -0
  70. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/sca_options.py +0 -0
  71. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/__init__.py +0 -0
  72. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/aggregation_report.py +0 -0
  73. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/code_scanner.py +0 -0
  74. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/commit_history/__init__.py +0 -0
  75. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/commit_history/commit_history_command.py +0 -0
  76. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/commit_range_scanner.py +0 -0
  77. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/detection_excluder.py +0 -0
  78. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/path/__init__.py +0 -0
  79. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/path/path_command.py +0 -0
  80. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/pre_commit/__init__.py +0 -0
  81. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/pre_commit/pre_commit_command.py +0 -0
  82. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/pre_push/__init__.py +0 -0
  83. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/pre_push/pre_push_command.py +0 -0
  84. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/pre_receive/__init__.py +0 -0
  85. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/pre_receive/pre_receive_command.py +0 -0
  86. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/remote_url_resolver.py +0 -0
  87. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/repository/__init__.py +0 -0
  88. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/repository/repository_command.py +0 -0
  89. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/scan_ci/__init__.py +0 -0
  90. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/scan_ci/ci_integrations.py +0 -0
  91. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/scan_ci/scan_ci_command.py +0 -0
  92. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/scan_command.py +0 -0
  93. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/scan_parameters.py +0 -0
  94. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/scan/scan_result.py +0 -0
  95. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/status/__init__.py +0 -0
  96. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/status/get_cli_status.py +0 -0
  97. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/status/models.py +0 -0
  98. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/status/status_command.py +0 -0
  99. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/apps/status/version_command.py +0 -0
  100. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/cli_types.py +0 -0
  101. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/config.py +0 -0
  102. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/console.py +0 -0
  103. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/consts.py +0 -0
  104. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/exceptions/__init__.py +0 -0
  105. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/exceptions/custom_exceptions.py +0 -0
  106. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/exceptions/handle_ai_remediation_errors.py +0 -0
  107. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/exceptions/handle_auth_errors.py +0 -0
  108. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/exceptions/handle_errors.py +0 -0
  109. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/exceptions/handle_report_sbom_errors.py +0 -0
  110. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/exceptions/handle_scan_errors.py +0 -0
  111. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/__init__.py +0 -0
  112. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/commit_range_documents.py +0 -0
  113. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/documents_walk_ignore.py +0 -0
  114. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/file_excluder.py +0 -0
  115. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/iac/__init__.py +0 -0
  116. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
  117. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/models/__init__.py +0 -0
  118. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
  119. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/path_documents.py +0 -0
  120. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/repository_documents.py +0 -0
  121. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/__init__.py +0 -0
  122. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/base_restore_dependencies.py +0 -0
  123. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/go/__init__.py +0 -0
  124. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/go/restore_go_dependencies.py +0 -0
  125. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/maven/__init__.py +0 -0
  126. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
  127. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
  128. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/npm/__init__.py +0 -0
  129. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/npm/restore_bun_dependencies.py +0 -0
  130. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/npm/restore_deno_dependencies.py +0 -0
  131. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py +0 -0
  132. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/npm/restore_pnpm_dependencies.py +0 -0
  133. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/npm/restore_yarn_dependencies.py +0 -0
  134. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/nuget/__init__.py +0 -0
  135. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py +0 -0
  136. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/php/__init__.py +0 -0
  137. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/php/restore_composer_dependencies.py +0 -0
  138. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/python/__init__.py +0 -0
  139. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/python/restore_pip_dependencies.py +0 -0
  140. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/python/restore_pipenv_dependencies.py +0 -0
  141. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/python/restore_poetry_dependencies.py +0 -0
  142. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/python/restore_uv_dependencies.py +0 -0
  143. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/ruby/__init__.py +0 -0
  144. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py +0 -0
  145. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/sbt/__init__.py +0 -0
  146. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py +0 -0
  147. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/sca/sca_file_collector.py +0 -0
  148. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/walk_ignore.py +0 -0
  149. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/files_collector/zip_documents.py +0 -0
  150. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/logger.py +0 -0
  151. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/main.py +0 -0
  152. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/models.py +0 -0
  153. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/__init__.py +0 -0
  154. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/console_printer.py +0 -0
  155. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/json_printer.py +0 -0
  156. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/printer_base.py +0 -0
  157. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/rich_printer.py +0 -0
  158. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/tables/__init__.py +0 -0
  159. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
  160. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/tables/table.py +0 -0
  161. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/tables/table_models.py +0 -0
  162. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/tables/table_printer.py +0 -0
  163. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/tables/table_printer_base.py +0 -0
  164. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/text_printer.py +0 -0
  165. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/utils/__init__.py +0 -0
  166. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/utils/code_snippet_syntax.py +0 -0
  167. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/utils/detection_data.py +0 -0
  168. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/utils/detection_ordering/__init__.py +0 -0
  169. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/utils/detection_ordering/common_ordering.py +0 -0
  170. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/utils/detection_ordering/sca_ordering.py +0 -0
  171. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/printers/utils/rich_helpers.py +0 -0
  172. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/user_settings/__init__.py +0 -0
  173. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/user_settings/base_file_manager.py +0 -0
  174. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/user_settings/config_file_manager.py +0 -0
  175. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/user_settings/configuration_manager.py +0 -0
  176. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/user_settings/credentials_manager.py +0 -0
  177. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/user_settings/jwt_creator.py +0 -0
  178. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/__init__.py +0 -0
  179. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/binary_utils.py +0 -0
  180. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/enum_utils.py +0 -0
  181. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/get_api_client.py +0 -0
  182. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/git_proxy.py +0 -0
  183. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/ignore_utils.py +0 -0
  184. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/jwt_utils.py +0 -0
  185. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/path_utils.py +0 -0
  186. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/progress_bar.py +0 -0
  187. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/scan_batch.py +0 -0
  188. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/scan_utils.py +0 -0
  189. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/shell_executor.py +0 -0
  190. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/string_utils.py +0 -0
  191. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/task_timer.py +0 -0
  192. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/url_utils.py +0 -0
  193. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/version_checker.py +0 -0
  194. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cli/utils/yaml_utils.py +0 -0
  195. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/config.py +0 -0
  196. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/__init__.py +0 -0
  197. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/ai_security_manager_client.py +0 -0
  198. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/ai_security_manager_service_config.py +0 -0
  199. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/auth_client.py +0 -0
  200. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/base_token_auth_client.py +0 -0
  201. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/cli_activation_client.py +0 -0
  202. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/client_creator.py +0 -0
  203. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/config.py +0 -0
  204. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/config_dev.py +0 -0
  205. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/cycode_client.py +0 -0
  206. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/cycode_client_base.py +0 -0
  207. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/cycode_dev_based_client.py +0 -0
  208. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/cycode_oidc_based_client.py +0 -0
  209. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/cycode_token_based_client.py +0 -0
  210. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/headers.py +0 -0
  211. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/import_sbom_client.py +0 -0
  212. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/logger.py +0 -0
  213. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/models.py +0 -0
  214. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/report_client.py +0 -0
  215. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/scan_client.py +0 -0
  216. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/cyclient/scan_config_base.py +0 -0
  217. {cycode-3.18.1.dev2 → cycode-3.18.1.dev4}/cycode/logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycode
3
- Version: 3.18.1.dev2
3
+ Version: 3.18.1.dev4
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
@@ -72,7 +72,9 @@ This guide walks you through both installation and usage.
72
72
  1. [Discovering Commands](#discovering-commands)
73
73
  2. [Examples](#platform-examples)
74
74
  3. [Notes & Limitations](#platform-notes--limitations)
75
- 6. [Scan Command](#scan-command)
75
+ 6. [AI Guardrails](#ai-guardrails-beta)
76
+ 1. [Data Collected by AI Guardrails](#data-collected-by-ai-guardrails)
77
+ 7. [Scan Command](#scan-command)
76
78
  1. [Running a Scan](#running-a-scan)
77
79
  1. [Options](#options)
78
80
  1. [Severity Threshold](#severity-option)
@@ -748,6 +750,32 @@ cycode platform projects list --page-size 100 | jq '.items[].name'
748
750
  - **Override the cache TTL** with `CYCODE_SPEC_CACHE_TTL=<seconds>`.
749
751
 
750
752
 
753
+ # AI Guardrails \[BETA\]
754
+
755
+ AI Guardrails installs hooks into supported AI coding agents (Claude Code, Cursor, Copilot, Codex) so that
756
+ prompts, files the agent reads, and MCP tool arguments are scanned for secrets before they reach the model.
757
+
758
+ ## Data Collected by AI Guardrails
759
+
760
+ Scanning happens server-side, so the scanned content leaves the machine: the prompt text, the contents of
761
+ files the agent reads, and MCP tool arguments are sent to your Cycode tenant to be checked for secrets.
762
+
763
+ Each event is also reported with context about the developer and the machine, so a finding can be attributed
764
+ to the device and user it came from. Some of this is personal data:
765
+
766
+ - **Device identifiers** — the machine's hostname and hardware serial number.
767
+ - **User identifiers** — the email address of the user signed in to the AI coding agent, and the local
768
+ operating-system username.
769
+ - **Environment details** — operating system and version, the AI agent, its version and the model in use,
770
+ the contents of the agent's MCP configuration files, and its enabled plugins.
771
+
772
+ The hardware serial number is cached in a local temporary file, readable only by the user who ran the
773
+ command, so repeated hook invocations don't re-query the hardware.
774
+
775
+ If collecting this data is not acceptable in your environment, do not install the guardrails hooks
776
+ (`cycode ai-guardrails uninstall` removes hooks that are already installed).
777
+
778
+
751
779
  # Scan Command
752
780
 
753
781
  ## Running a Scan
@@ -28,7 +28,9 @@ This guide walks you through both installation and usage.
28
28
  1. [Discovering Commands](#discovering-commands)
29
29
  2. [Examples](#platform-examples)
30
30
  3. [Notes & Limitations](#platform-notes--limitations)
31
- 6. [Scan Command](#scan-command)
31
+ 6. [AI Guardrails](#ai-guardrails-beta)
32
+ 1. [Data Collected by AI Guardrails](#data-collected-by-ai-guardrails)
33
+ 7. [Scan Command](#scan-command)
32
34
  1. [Running a Scan](#running-a-scan)
33
35
  1. [Options](#options)
34
36
  1. [Severity Threshold](#severity-option)
@@ -704,6 +706,32 @@ cycode platform projects list --page-size 100 | jq '.items[].name'
704
706
  - **Override the cache TTL** with `CYCODE_SPEC_CACHE_TTL=<seconds>`.
705
707
 
706
708
 
709
+ # AI Guardrails \[BETA\]
710
+
711
+ AI Guardrails installs hooks into supported AI coding agents (Claude Code, Cursor, Copilot, Codex) so that
712
+ prompts, files the agent reads, and MCP tool arguments are scanned for secrets before they reach the model.
713
+
714
+ ## Data Collected by AI Guardrails
715
+
716
+ Scanning happens server-side, so the scanned content leaves the machine: the prompt text, the contents of
717
+ files the agent reads, and MCP tool arguments are sent to your Cycode tenant to be checked for secrets.
718
+
719
+ Each event is also reported with context about the developer and the machine, so a finding can be attributed
720
+ to the device and user it came from. Some of this is personal data:
721
+
722
+ - **Device identifiers** — the machine's hostname and hardware serial number.
723
+ - **User identifiers** — the email address of the user signed in to the AI coding agent, and the local
724
+ operating-system username.
725
+ - **Environment details** — operating system and version, the AI agent, its version and the model in use,
726
+ the contents of the agent's MCP configuration files, and its enabled plugins.
727
+
728
+ The hardware serial number is cached in a local temporary file, readable only by the user who ran the
729
+ command, so repeated hook invocations don't re-query the hardware.
730
+
731
+ If collecting this data is not acceptable in your environment, do not install the guardrails hooks
732
+ (`cycode ai-guardrails uninstall` removes hooks that are already installed).
733
+
734
+
707
735
  # Scan Command
708
736
 
709
737
  ## Running a Scan
@@ -5,4 +5,4 @@ import time as _time
5
5
  # end-to-end scan duration from the moment the user actually triggered it.
6
6
  _BOOT_WALL: float = _time.time()
7
7
 
8
- __version__ = '3.18.1.dev2' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
8
+ __version__ = '3.18.1.dev4' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
@@ -10,8 +10,13 @@ class PolicyMode(str, Enum):
10
10
  WARN = 'warn'
11
11
 
12
12
 
13
- class InstallMode(str, Enum):
14
- """Installation mode for ai-guardrails install command."""
13
+ class GuardrailsMode(str, Enum):
14
+ """Guardrails enforcement mode.
15
+
16
+ Used both as the ai-guardrails install-command mode and as the per-event
17
+ effective mode reported to the server (the ai_guardrails scan parameter's
18
+ `mode` field)
19
+ """
15
20
 
16
21
  REPORT = 'report'
17
22
  BLOCK = 'block'
@@ -333,9 +333,7 @@ class Copilot(IDE):
333
333
  return {
334
334
  'version': 1,
335
335
  'hooks': {
336
- 'sessionStart': [
337
- {'type': 'command', 'command': _SESSION_START_COMMAND, 'timeoutSec': _HOOK_TIMEOUT_SEC}
338
- ],
336
+ 'sessionStart': [{'type': 'command', 'command': _SESSION_START_COMMAND}],
339
337
  'userPromptSubmitted': [entry(_SCAN_PROMPT_COMMAND)],
340
338
  'preToolUse': [entry(_SCAN_TOOL_COMMAND)],
341
339
  },
@@ -6,7 +6,7 @@ from typing import Annotated, Optional
6
6
  import typer
7
7
 
8
8
  from cycode.cli.apps.ai_guardrails.command_utils import console, resolve_repo_path, validate_scope
9
- from cycode.cli.apps.ai_guardrails.consts import InstallMode, PolicyMode
9
+ from cycode.cli.apps.ai_guardrails.consts import GuardrailsMode, PolicyMode
10
10
  from cycode.cli.apps.ai_guardrails.hooks_manager import create_policy_file, install_hooks
11
11
  from cycode.cli.apps.ai_guardrails.ides import DEFAULT_IDE_NAME, IDES, resolve_ides
12
12
 
@@ -40,14 +40,14 @@ def install_command(
40
40
  ),
41
41
  ] = None,
42
42
  mode: Annotated[
43
- InstallMode,
43
+ GuardrailsMode,
44
44
  typer.Option(
45
45
  '--mode',
46
46
  '-m',
47
47
  help='Installation mode: "report" for async non-blocking hooks with warn policy, '
48
48
  '"block" for sync blocking hooks.',
49
49
  ),
50
- ] = InstallMode.REPORT,
50
+ ] = GuardrailsMode.REPORT,
51
51
  ) -> None:
52
52
  """Install AI guardrails hooks for supported IDEs.
53
53
 
@@ -65,7 +65,7 @@ def install_command(
65
65
  repo_path = resolve_repo_path(scope, repo_path)
66
66
  ides_to_install = resolve_ides(ide)
67
67
 
68
- report_mode = mode == InstallMode.REPORT
68
+ report_mode = mode == GuardrailsMode.REPORT
69
69
 
70
70
  results: list[tuple[str, bool, str]] = []
71
71
  for current_ide in ides_to_install:
@@ -83,7 +83,7 @@ def install_command(
83
83
  all_success = False
84
84
 
85
85
  if any_success:
86
- policy_mode = PolicyMode.WARN if mode == InstallMode.REPORT else PolicyMode.BLOCK
86
+ policy_mode = PolicyMode.WARN if mode == GuardrailsMode.REPORT else PolicyMode.BLOCK
87
87
  _install_policy(scope, repo_path, policy_mode)
88
88
  _print_next_steps(results, mode)
89
89
 
@@ -99,7 +99,7 @@ def _install_policy(scope: str, repo_path: Optional[Path], policy_mode: PolicyMo
99
99
  console.print(f'[red]✗[/] {policy_message}', style='bold red')
100
100
 
101
101
 
102
- def _print_next_steps(results: list[tuple[str, bool, str]], mode: InstallMode) -> None:
102
+ def _print_next_steps(results: list[tuple[str, bool, str]], mode: GuardrailsMode) -> None:
103
103
  console.print()
104
104
  console.print('[bold]Next steps:[/]')
105
105
  successful_ides = [name for name, success, _ in results if success]
@@ -107,7 +107,7 @@ def _print_next_steps(results: list[tuple[str, bool, str]], mode: InstallMode) -
107
107
  console.print(f'1. Restart {ide_list} to activate the hooks')
108
108
  console.print('2. (Optional) Customize policy in ~/.cycode/ai-guardrails.yaml')
109
109
  console.print()
110
- if mode == InstallMode.REPORT:
110
+ if mode == GuardrailsMode.REPORT:
111
111
  console.print('[dim]Report mode: hooks run async (non-blocking) and policy is set to warn.[/]')
112
112
  else:
113
113
  console.print('[dim]The hooks will scan prompts, file reads, and MCP tool calls for secrets.[/]')
@@ -17,24 +17,29 @@ from typing import Callable, Optional
17
17
 
18
18
  import typer
19
19
 
20
- from cycode.cli.apps.ai_guardrails.consts import PolicyMode
20
+ from cycode.cli.apps.ai_guardrails.consts import GuardrailsMode, PolicyMode
21
21
  from cycode.cli.apps.ai_guardrails.ides.base import HookDecision
22
22
  from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
23
23
  from cycode.cli.apps.ai_guardrails.scan.policy import get_policy_value
24
- from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType, AIHookOutcome, BlockReason
24
+ from cycode.cli.apps.ai_guardrails.scan.types import (
25
+ SECRETS_BLOCK_REASON_BY_EVENT_TYPE,
26
+ AiHookEventType,
27
+ AIHookOutcome,
28
+ BlockReason,
29
+ )
25
30
  from cycode.cli.apps.ai_guardrails.scan.utils import is_denied_path, truncate_utf8
26
31
  from cycode.cli.apps.scan.code_scanner import _get_scan_documents_thread_func
27
32
  from cycode.cli.apps.scan.scan_parameters import get_scan_parameters
28
33
  from cycode.cli.cli_types import ScanTypeOption, SeverityOption
29
34
  from cycode.cli.files_collector.file_excluder import is_path_configured_in_exclusions
30
35
  from cycode.cli.models import Document
36
+ from cycode.cli.utils.host_info import get_hostname, get_serial_number
31
37
  from cycode.cli.utils.progress_bar import DummyProgressBar, ScanProgressBarSection
32
38
  from cycode.cli.utils.scan_utils import build_violation_summary
33
39
  from cycode.logger import get_logger
34
40
 
35
41
  logger = get_logger('AI Guardrails')
36
42
 
37
-
38
43
  HandlerFn = Callable[[typer.Context, AIHookPayload, dict], HookDecision]
39
44
 
40
45
 
@@ -47,7 +52,7 @@ def handle_before_submit_prompt(ctx: typer.Context, payload: AIHookPayload, poli
47
52
  ai_client.create_event(payload, AiHookEventType.PROMPT, AIHookOutcome.ALLOWED)
48
53
  return HookDecision.allow(AiHookEventType.PROMPT)
49
54
 
50
- mode = get_policy_value(policy, 'mode', default=PolicyMode.BLOCK)
55
+ effective_mode = get_effective_mode(policy, prompt_config)
51
56
  prompt = payload.prompt or ''
52
57
  max_bytes = get_policy_value(policy, 'secrets', 'max_bytes', default=200000)
53
58
  timeout_ms = get_policy_value(policy, 'secrets', 'timeout_ms', default=30000)
@@ -59,12 +64,18 @@ def handle_before_submit_prompt(ctx: typer.Context, payload: AIHookPayload, poli
59
64
  error_message = None
60
65
 
61
66
  try:
62
- violation_summary, scan_id = _scan_text_for_secrets(ctx, clipped, timeout_ms)
67
+ violation_summary, scan_id = _scan_text_for_secrets(
68
+ ctx,
69
+ clipped,
70
+ timeout_ms,
71
+ payload=payload,
72
+ event_type=AiHookEventType.PROMPT,
73
+ effective_mode=effective_mode,
74
+ )
63
75
 
64
76
  if violation_summary:
65
- block_reason = BlockReason.SECRETS_IN_PROMPT
66
- action = get_policy_value(prompt_config, 'action', default=PolicyMode.BLOCK)
67
- if action == PolicyMode.BLOCK and mode == PolicyMode.BLOCK:
77
+ block_reason = SECRETS_BLOCK_REASON_BY_EVENT_TYPE[AiHookEventType.PROMPT]
78
+ if effective_mode == GuardrailsMode.BLOCK:
68
79
  outcome = AIHookOutcome.BLOCKED
69
80
  user_message = f'{violation_summary}. Remove secrets before sending.'
70
81
  return HookDecision.deny(AiHookEventType.PROMPT, user_message)
@@ -97,9 +108,8 @@ def handle_before_read_file(ctx: typer.Context, payload: AIHookPayload, policy:
97
108
  ai_client.create_event(payload, AiHookEventType.FILE_READ, AIHookOutcome.ALLOWED)
98
109
  return HookDecision.allow(AiHookEventType.FILE_READ)
99
110
 
100
- mode = get_policy_value(policy, 'mode', default=PolicyMode.BLOCK)
101
111
  file_path = payload.file_path or ''
102
- action = get_policy_value(file_read_config, 'action', default=PolicyMode.BLOCK)
112
+ effective_mode = get_effective_mode(policy, file_read_config)
103
113
 
104
114
  scan_id = None
105
115
  block_reason = None
@@ -110,7 +120,7 @@ def handle_before_read_file(ctx: typer.Context, payload: AIHookPayload, policy:
110
120
  is_sensitive_path = is_denied_path(file_path, policy)
111
121
  if is_sensitive_path:
112
122
  block_reason = BlockReason.SENSITIVE_PATH
113
- if mode == PolicyMode.BLOCK and action == PolicyMode.BLOCK:
123
+ if effective_mode == GuardrailsMode.BLOCK:
114
124
  outcome = AIHookOutcome.BLOCKED
115
125
  user_message = f'Cycode blocked sending {file_path} to the AI (sensitive path policy).'
116
126
  return HookDecision.deny(
@@ -133,10 +143,12 @@ def handle_before_read_file(ctx: typer.Context, payload: AIHookPayload, policy:
133
143
  outcome = AIHookOutcome.ALLOWED
134
144
 
135
145
  if get_policy_value(file_read_config, 'scan_content', default=True):
136
- violation_summary, scan_id = _scan_path_for_secrets(ctx, file_path, policy)
146
+ violation_summary, scan_id = _scan_path_for_secrets(
147
+ ctx, file_path, policy, payload=payload, effective_mode=effective_mode
148
+ )
137
149
  if violation_summary:
138
- block_reason = BlockReason.SECRETS_IN_FILE
139
- if mode == PolicyMode.BLOCK and action == PolicyMode.BLOCK:
150
+ block_reason = SECRETS_BLOCK_REASON_BY_EVENT_TYPE[AiHookEventType.FILE_READ]
151
+ if effective_mode == GuardrailsMode.BLOCK:
140
152
  outcome = AIHookOutcome.BLOCKED
141
153
  user_message = f'Cycode blocked reading {file_path}. {violation_summary}'
142
154
  return HookDecision.deny(
@@ -191,7 +203,6 @@ class _ArgScanFeature:
191
203
  policy_key: str # 'mcp' or 'command_exec'
192
204
  scan_key: str # 'scan_arguments' or 'scan_command'
193
205
  event_type: AiHookEventType
194
- block_reason: BlockReason
195
206
  deny_message: Callable[[str], str]
196
207
  deny_agent_message: str
197
208
  ask_message: Callable[[str], str]
@@ -213,11 +224,10 @@ def _handle_arg_scan(
213
224
  ai_client.create_event(payload, feature.event_type, AIHookOutcome.ALLOWED)
214
225
  return HookDecision.allow(feature.event_type)
215
226
 
216
- mode = get_policy_value(policy, 'mode', default=PolicyMode.BLOCK)
217
227
  max_bytes = get_policy_value(policy, 'secrets', 'max_bytes', default=200000)
218
228
  timeout_ms = get_policy_value(policy, 'secrets', 'timeout_ms', default=30000)
219
229
  clipped = truncate_utf8(scan_text, max_bytes)
220
- action = get_policy_value(feature_config, 'action', default=PolicyMode.BLOCK)
230
+ effective_mode = get_effective_mode(policy, feature_config)
221
231
 
222
232
  scan_id = None
223
233
  block_reason = None
@@ -226,10 +236,17 @@ def _handle_arg_scan(
226
236
 
227
237
  try:
228
238
  if get_policy_value(feature_config, feature.scan_key, default=True):
229
- violation_summary, scan_id = _scan_text_for_secrets(ctx, clipped, timeout_ms)
239
+ violation_summary, scan_id = _scan_text_for_secrets(
240
+ ctx,
241
+ clipped,
242
+ timeout_ms,
243
+ payload=payload,
244
+ event_type=feature.event_type,
245
+ effective_mode=effective_mode,
246
+ )
230
247
  if violation_summary:
231
- block_reason = feature.block_reason
232
- if mode == PolicyMode.BLOCK and action == PolicyMode.BLOCK:
248
+ block_reason = SECRETS_BLOCK_REASON_BY_EVENT_TYPE[feature.event_type]
249
+ if effective_mode == GuardrailsMode.BLOCK:
233
250
  outcome = AIHookOutcome.BLOCKED
234
251
  return HookDecision.deny(
235
252
  feature.event_type,
@@ -275,7 +292,6 @@ def handle_before_mcp_execution(ctx: typer.Context, payload: AIHookPayload, poli
275
292
  policy_key='mcp',
276
293
  scan_key='scan_arguments',
277
294
  event_type=AiHookEventType.MCP_EXECUTION,
278
- block_reason=BlockReason.SECRETS_IN_MCP_ARGS,
279
295
  deny_message=lambda v: f'Cycode blocked MCP tool call "{tool}". {v}',
280
296
  deny_agent_message='Do not pass secrets to tools. Use secret references (name/id) instead.',
281
297
  ask_message=lambda v: f'{v} in MCP tool call "{tool}". Allow execution?',
@@ -295,6 +311,36 @@ def get_handler_for_event(event_type: str) -> Optional[HandlerFn]:
295
311
  return handlers.get(event_type)
296
312
 
297
313
 
314
+ def get_effective_mode(policy: dict, feature_config: dict) -> GuardrailsMode:
315
+ """The event only blocks when both the global mode and the per-guardrail action are block."""
316
+ mode = get_policy_value(policy, 'mode', default=PolicyMode.BLOCK)
317
+ action = get_policy_value(feature_config, 'action', default=PolicyMode.BLOCK)
318
+ return GuardrailsMode.BLOCK if (mode == PolicyMode.BLOCK and action == PolicyMode.BLOCK) else GuardrailsMode.REPORT
319
+
320
+
321
+ def build_ai_guardrails_scan_parameters(
322
+ ctx: typer.Context,
323
+ paths: Optional[tuple[str, ...]],
324
+ payload: AIHookPayload,
325
+ event_type: AiHookEventType,
326
+ effective_mode: GuardrailsMode,
327
+ ) -> dict:
328
+ scan_parameters = get_scan_parameters(ctx, paths)
329
+ scan_parameters.setdefault('metadata', {})['ai_guardrails'] = {
330
+ 'mode': effective_mode.value,
331
+ 'ide_provider': payload.ide_provider,
332
+ 'detection_source': SECRETS_BLOCK_REASON_BY_EVENT_TYPE[event_type].value,
333
+ 'device_id': get_serial_number(),
334
+ 'device_hostname': get_hostname(),
335
+ 'conversation_id': payload.conversation_id,
336
+ 'generation_id': payload.generation_id,
337
+ 'ide_user_email': payload.ide_user_email,
338
+ 'mcp_server_name': payload.mcp_server_name,
339
+ 'mcp_tool_name': payload.mcp_tool_name,
340
+ }
341
+ return scan_parameters
342
+
343
+
298
344
  def _setup_scan_context(ctx: typer.Context) -> typer.Context:
299
345
  """Set up minimal context for scan_documents without progress bars or printing."""
300
346
  ctx.obj['progress_bar'] = DummyProgressBar([ScanProgressBarSection])
@@ -345,7 +391,14 @@ def _perform_scan(
345
391
  return None, scan_id
346
392
 
347
393
 
348
- def _scan_text_for_secrets(ctx: typer.Context, text: str, timeout_ms: int) -> tuple[Optional[str], Optional[str]]:
394
+ def _scan_text_for_secrets(
395
+ ctx: typer.Context,
396
+ text: str,
397
+ timeout_ms: int,
398
+ payload: AIHookPayload,
399
+ event_type: AiHookEventType,
400
+ effective_mode: GuardrailsMode,
401
+ ) -> tuple[Optional[str], Optional[str]]:
349
402
  """Scan text content for secrets using Cycode CLI."""
350
403
  if not text:
351
404
  return None, None
@@ -353,10 +406,17 @@ def _scan_text_for_secrets(ctx: typer.Context, text: str, timeout_ms: int) -> tu
353
406
  document = Document(path='prompt-content.txt', content=text, is_git_diff_format=False)
354
407
  scan_ctx = _setup_scan_context(ctx)
355
408
  timeout_seconds = timeout_ms / 1000.0
356
- return _perform_scan(scan_ctx, [document], get_scan_parameters(scan_ctx, None), timeout_seconds)
409
+ scan_parameters = build_ai_guardrails_scan_parameters(scan_ctx, None, payload, event_type, effective_mode)
410
+ return _perform_scan(scan_ctx, [document], scan_parameters, timeout_seconds)
357
411
 
358
412
 
359
- def _scan_path_for_secrets(ctx: typer.Context, file_path: str, policy: dict) -> tuple[Optional[str], Optional[str]]:
413
+ def _scan_path_for_secrets(
414
+ ctx: typer.Context,
415
+ file_path: str,
416
+ policy: dict,
417
+ payload: AIHookPayload,
418
+ effective_mode: GuardrailsMode,
419
+ ) -> tuple[Optional[str], Optional[str]]:
360
420
  """Scan a file path for secrets."""
361
421
  if not file_path or not os.path.isfile(file_path):
362
422
  return None, None
@@ -375,4 +435,7 @@ def _scan_path_for_secrets(ctx: typer.Context, file_path: str, policy: dict) ->
375
435
 
376
436
  document = Document(path=os.path.basename(file_path), content=content, is_git_diff_format=False)
377
437
  scan_ctx = _setup_scan_context(ctx)
378
- return _perform_scan(scan_ctx, [document], get_scan_parameters(scan_ctx, (file_path,)), timeout_seconds)
438
+ scan_parameters = build_ai_guardrails_scan_parameters(
439
+ scan_ctx, (file_path,), payload, AiHookEventType.FILE_READ, effective_mode
440
+ )
441
+ return _perform_scan(scan_ctx, [document], scan_parameters, timeout_seconds)
@@ -8,6 +8,7 @@ The handlers in ``handlers.py`` are agent-agnostic (they return
8
8
  """
9
9
 
10
10
  from typing import Annotated, Optional, Union
11
+ from uuid import uuid4
11
12
 
12
13
  import click
13
14
  import typer
@@ -125,6 +126,9 @@ def scan_command(
125
126
  return
126
127
 
127
128
  unified_payload = ide_integration.parse_hook_payload(payload)
129
+ if not unified_payload.generation_id:
130
+ # Not every IDE dialect provides a generation id (e.g. Copilot)
131
+ unified_payload.generation_id = str(uuid4())
128
132
  event_name = unified_payload.event_name
129
133
  logger.debug(
130
134
  'Processing AI guardrails hook',
@@ -41,3 +41,12 @@ class BlockReason(StrEnum):
41
41
  SECRETS_IN_MCP_ARGS = 'secrets_in_mcp_args'
42
42
  SENSITIVE_PATH = 'sensitive_path'
43
43
  SCAN_FAILURE = 'scan_failure'
44
+
45
+
46
+ # The reason each event type yields when a secret is found in it. Also travels with the scan as
47
+ # `detection_source`, so the violation and the hook event are labelled from the same vocabulary.
48
+ SECRETS_BLOCK_REASON_BY_EVENT_TYPE: dict[AiHookEventType, BlockReason] = {
49
+ AiHookEventType.PROMPT: BlockReason.SECRETS_IN_PROMPT,
50
+ AiHookEventType.FILE_READ: BlockReason.SECRETS_IN_FILE,
51
+ AiHookEventType.MCP_EXECUTION: BlockReason.SECRETS_IN_MCP_ARGS,
52
+ }
@@ -1,8 +1,11 @@
1
1
  import getpass
2
+ import os
2
3
  import platform
3
4
  import re
4
5
  import socket
5
6
  import subprocess
7
+ import tempfile
8
+ from pathlib import Path
6
9
  from typing import Optional
7
10
 
8
11
  from cycode.logger import get_logger
@@ -11,6 +14,8 @@ logger = get_logger('HOST INFO')
11
14
 
12
15
  _SUBPROCESS_TIMEOUT_SEC = 5
13
16
 
17
+ _SERIAL_NUMBER_CACHE_FILE_NAME = '.cycode-device-serial'
18
+
14
19
  _PLATFORM_NAMES = {'Darwin': 'macOS', 'Windows': 'Windows', 'Linux': 'Linux'}
15
20
 
16
21
 
@@ -93,6 +98,19 @@ def get_last_login_user() -> Optional[str]:
93
98
 
94
99
 
95
100
  def get_serial_number() -> Optional[str]:
101
+ # The serial is immutable hardware info, but resolving it shells out (ioreg/WMI)
102
+ # and this runs in a fresh process per AI hook event - cache it on disk.
103
+ cached = _read_serial_number_cache()
104
+ if cached:
105
+ return cached
106
+
107
+ serial = _resolve_serial_number()
108
+ if serial:
109
+ _write_serial_number_cache(serial)
110
+ return serial
111
+
112
+
113
+ def _resolve_serial_number() -> Optional[str]:
96
114
  try:
97
115
  system = platform.system()
98
116
  if system == 'Darwin':
@@ -104,6 +122,40 @@ def get_serial_number() -> Optional[str]:
104
122
  return None
105
123
 
106
124
 
125
+ def _serial_number_cache_path() -> Path:
126
+ # The username suffix avoids collisions on OSes with a shared temp dir
127
+ return Path(tempfile.gettempdir()) / f'.cycode-device-serial-{getpass.getuser()}'
128
+
129
+
130
+ def _read_serial_number_cache() -> Optional[str]:
131
+ try:
132
+ return _serial_number_cache_path().read_text(encoding='utf-8').strip() or None
133
+ except Exception:
134
+ return None
135
+
136
+
137
+ def _write_serial_number_cache(serial: str) -> None:
138
+ try:
139
+ cache_path = _serial_number_cache_path()
140
+
141
+ # The serial identifies the machine, and the temp dir is shared, so the cache is created
142
+ # readable by its owner alone (what mkstemp does) and moved into place atomically - a hook
143
+ # racing another one never reads a half-written cache, and the rename can't be redirected
144
+ # by a symlink planted at the destination the way an in-place write could.
145
+ file_descriptor, temp_path = tempfile.mkstemp(
146
+ dir=cache_path.parent, prefix=f'{_SERIAL_NUMBER_CACHE_FILE_NAME}.'
147
+ )
148
+ try:
149
+ with os.fdopen(file_descriptor, 'w', encoding='utf-8') as temp_file:
150
+ temp_file.write(serial)
151
+ os.replace(temp_path, cache_path)
152
+ except Exception:
153
+ Path(temp_path).unlink(missing_ok=True)
154
+ raise
155
+ except Exception as e:
156
+ logger.debug('Failed to cache serial number', exc_info=e)
157
+
158
+
107
159
  def _get_macos_serial_number() -> Optional[str]:
108
160
  output = _run(['ioreg', '-c', 'IOPlatformExpertDevice', '-d', '2'])
109
161
  if not output:
@@ -21,7 +21,7 @@ classifiers = [
21
21
  "Programming Language :: Python :: 3.14",
22
22
  ]
23
23
  dynamic = ["dependencies"]
24
- version = "3.18.1.dev2"
24
+ version = "3.18.1.dev4"
25
25
 
26
26
  [project.scripts]
27
27
  cycode = "cycode.cli.app:app"
File without changes