cycode 3.23.0__tar.gz → 3.23.1.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. {cycode-3.23.0 → cycode-3.23.1.dev1}/PKG-INFO +1 -1
  2. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/__init__.py +1 -1
  3. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/consts.py +12 -0
  4. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/hooks_manager.py +7 -8
  5. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/install_command.py +15 -14
  6. cycode-3.23.1.dev1/cycode/cli/apps/ai_guardrails/scan/consts.py +37 -0
  7. cycode-3.23.1.dev1/cycode/cli/apps/ai_guardrails/scan/guardrail_config.py +165 -0
  8. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/scan/handlers.py +47 -64
  9. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/scan/policy.py +37 -5
  10. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/scan/scan_command.py +42 -22
  11. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/session_start_command.py +21 -0
  12. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/path_utils.py +27 -0
  13. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/yaml_utils.py +3 -24
  14. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/ai_security_manager_client.py +10 -0
  15. {cycode-3.23.0 → cycode-3.23.1.dev1}/pyproject.toml +1 -1
  16. cycode-3.23.0/cycode/cli/apps/ai_guardrails/scan/consts.py +0 -48
  17. {cycode-3.23.0 → cycode-3.23.1.dev1}/LICENCE +0 -0
  18. {cycode-3.23.0 → cycode-3.23.1.dev1}/README.md +0 -0
  19. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/__main__.py +0 -0
  20. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/__init__.py +0 -0
  21. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/app.py +0 -0
  22. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/__init__.py +0 -0
  23. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/activation_manager.py +0 -0
  24. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/__init__.py +0 -0
  25. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/command_utils.py +0 -0
  26. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/ides/__init__.py +0 -0
  27. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py +0 -0
  28. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/ides/_skill_utils.py +0 -0
  29. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/ides/base.py +0 -0
  30. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/ides/claude_code.py +0 -0
  31. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/ides/codex.py +0 -0
  32. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/ides/copilot.py +0 -0
  33. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/ides/cursor.py +0 -0
  34. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/scan/__init__.py +0 -0
  35. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/scan/detach.py +0 -0
  36. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/scan/payload.py +0 -0
  37. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/scan/types.py +0 -0
  38. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/scan/utils.py +0 -0
  39. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/status_command.py +0 -0
  40. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_guardrails/uninstall_command.py +0 -0
  41. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_remediation/__init__.py +0 -0
  42. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_remediation/ai_remediation_command.py +0 -0
  43. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_remediation/apply_fix.py +0 -0
  44. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ai_remediation/print_remediation.py +0 -0
  45. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/api/__init__.py +0 -0
  46. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/api/api_command.py +0 -0
  47. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/api/openapi_spec.py +0 -0
  48. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/auth/__init__.py +0 -0
  49. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/auth/auth_command.py +0 -0
  50. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/auth/auth_common.py +0 -0
  51. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/auth/auth_manager.py +0 -0
  52. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/auth/models.py +0 -0
  53. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/configure/__init__.py +0 -0
  54. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/configure/configure_command.py +0 -0
  55. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/configure/consts.py +0 -0
  56. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/configure/messages.py +0 -0
  57. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/configure/prompts.py +0 -0
  58. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ignore/__init__.py +0 -0
  59. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/ignore/ignore_command.py +0 -0
  60. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/mcp/__init__.py +0 -0
  61. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/mcp/mcp_command.py +0 -0
  62. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report/__init__.py +0 -0
  63. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report/report_command.py +0 -0
  64. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report/sbom/__init__.py +0 -0
  65. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report/sbom/common.py +0 -0
  66. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report/sbom/path/__init__.py +0 -0
  67. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report/sbom/path/path_command.py +0 -0
  68. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report/sbom/repository_url/__init__.py +0 -0
  69. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report/sbom/repository_url/repository_url_command.py +0 -0
  70. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report/sbom/sbom_command.py +0 -0
  71. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report/sbom/sbom_report_file.py +0 -0
  72. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report_import/__init__.py +0 -0
  73. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report_import/report_import_command.py +0 -0
  74. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report_import/sbom/__init__.py +0 -0
  75. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/report_import/sbom/sbom_command.py +0 -0
  76. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/sca_options.py +0 -0
  77. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/__init__.py +0 -0
  78. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/aggregation_report.py +0 -0
  79. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/code_scanner.py +0 -0
  80. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/commit_history/__init__.py +0 -0
  81. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/commit_history/commit_history_command.py +0 -0
  82. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/commit_range_scanner.py +0 -0
  83. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/detection_excluder.py +0 -0
  84. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/path/__init__.py +0 -0
  85. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/path/path_command.py +0 -0
  86. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/pre_commit/__init__.py +0 -0
  87. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/pre_commit/pre_commit_command.py +0 -0
  88. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/pre_push/__init__.py +0 -0
  89. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/pre_push/pre_push_command.py +0 -0
  90. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/pre_receive/__init__.py +0 -0
  91. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/pre_receive/pre_receive_command.py +0 -0
  92. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/remote_url_resolver.py +0 -0
  93. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/repository/__init__.py +0 -0
  94. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/repository/repository_command.py +0 -0
  95. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/scan_ci/__init__.py +0 -0
  96. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/scan_ci/ci_integrations.py +0 -0
  97. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/scan_ci/scan_ci_command.py +0 -0
  98. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/scan_command.py +0 -0
  99. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/scan_parameters.py +0 -0
  100. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/scan/scan_result.py +0 -0
  101. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/status/__init__.py +0 -0
  102. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/status/get_cli_status.py +0 -0
  103. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/status/models.py +0 -0
  104. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/status/status_command.py +0 -0
  105. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/apps/status/version_command.py +0 -0
  106. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/cli_types.py +0 -0
  107. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/config.py +0 -0
  108. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/console.py +0 -0
  109. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/consts.py +0 -0
  110. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/exceptions/__init__.py +0 -0
  111. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/exceptions/custom_exceptions.py +0 -0
  112. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/exceptions/handle_ai_remediation_errors.py +0 -0
  113. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/exceptions/handle_auth_errors.py +0 -0
  114. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/exceptions/handle_errors.py +0 -0
  115. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/exceptions/handle_report_sbom_errors.py +0 -0
  116. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/exceptions/handle_scan_errors.py +0 -0
  117. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/__init__.py +0 -0
  118. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/commit_range_documents.py +0 -0
  119. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/documents_walk_ignore.py +0 -0
  120. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/file_excluder.py +0 -0
  121. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/iac/__init__.py +0 -0
  122. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
  123. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/models/__init__.py +0 -0
  124. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
  125. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/path_documents.py +0 -0
  126. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/repository_documents.py +0 -0
  127. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/__init__.py +0 -0
  128. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/base_restore_dependencies.py +0 -0
  129. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/go/__init__.py +0 -0
  130. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/go/restore_go_dependencies.py +0 -0
  131. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/maven/__init__.py +0 -0
  132. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
  133. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
  134. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/npm/__init__.py +0 -0
  135. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/npm/restore_bun_dependencies.py +0 -0
  136. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/npm/restore_deno_dependencies.py +0 -0
  137. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py +0 -0
  138. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/npm/restore_pnpm_dependencies.py +0 -0
  139. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/npm/restore_yarn_dependencies.py +0 -0
  140. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/nuget/__init__.py +0 -0
  141. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py +0 -0
  142. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/php/__init__.py +0 -0
  143. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/php/restore_composer_dependencies.py +0 -0
  144. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/python/__init__.py +0 -0
  145. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/python/restore_pip_dependencies.py +0 -0
  146. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/python/restore_pipenv_dependencies.py +0 -0
  147. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/python/restore_poetry_dependencies.py +0 -0
  148. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/python/restore_uv_dependencies.py +0 -0
  149. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/ruby/__init__.py +0 -0
  150. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py +0 -0
  151. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/sbt/__init__.py +0 -0
  152. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py +0 -0
  153. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/sca/sca_file_collector.py +0 -0
  154. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/walk_ignore.py +0 -0
  155. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/files_collector/zip_documents.py +0 -0
  156. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/logger.py +0 -0
  157. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/main.py +0 -0
  158. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/models.py +0 -0
  159. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/__init__.py +0 -0
  160. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/console_printer.py +0 -0
  161. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/json_printer.py +0 -0
  162. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/printer_base.py +0 -0
  163. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/rich_printer.py +0 -0
  164. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/tables/__init__.py +0 -0
  165. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
  166. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/tables/table.py +0 -0
  167. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/tables/table_models.py +0 -0
  168. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/tables/table_printer.py +0 -0
  169. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/tables/table_printer_base.py +0 -0
  170. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/text_printer.py +0 -0
  171. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/utils/__init__.py +0 -0
  172. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/utils/code_snippet_syntax.py +0 -0
  173. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/utils/detection_data.py +0 -0
  174. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/utils/detection_ordering/__init__.py +0 -0
  175. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/utils/detection_ordering/common_ordering.py +0 -0
  176. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/utils/detection_ordering/sca_ordering.py +0 -0
  177. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/utils/rich_helpers.py +0 -0
  178. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/utils/sca_ossf.py +0 -0
  179. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/printers/utils/sca_policy_details.py +0 -0
  180. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/user_settings/__init__.py +0 -0
  181. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/user_settings/base_file_manager.py +0 -0
  182. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/user_settings/config_file_manager.py +0 -0
  183. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/user_settings/configuration_manager.py +0 -0
  184. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/user_settings/credentials_manager.py +0 -0
  185. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/user_settings/jwt_creator.py +0 -0
  186. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/__init__.py +0 -0
  187. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/binary_utils.py +0 -0
  188. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/enum_utils.py +0 -0
  189. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/get_api_client.py +0 -0
  190. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/git_proxy.py +0 -0
  191. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/host_info.py +0 -0
  192. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/ignore_utils.py +0 -0
  193. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/jwt_utils.py +0 -0
  194. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/progress_bar.py +0 -0
  195. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/scan_batch.py +0 -0
  196. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/scan_utils.py +0 -0
  197. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/shell_executor.py +0 -0
  198. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/string_utils.py +0 -0
  199. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/task_timer.py +0 -0
  200. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/trust_store.py +0 -0
  201. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/url_utils.py +0 -0
  202. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cli/utils/version_checker.py +0 -0
  203. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/config.py +0 -0
  204. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/__init__.py +0 -0
  205. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/ai_security_manager_service_config.py +0 -0
  206. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/auth_client.py +0 -0
  207. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/base_token_auth_client.py +0 -0
  208. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/cli_activation_client.py +0 -0
  209. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/client_creator.py +0 -0
  210. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/config.py +0 -0
  211. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/config_dev.py +0 -0
  212. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/cycode_client.py +0 -0
  213. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/cycode_client_base.py +0 -0
  214. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/cycode_dev_based_client.py +0 -0
  215. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/cycode_oidc_based_client.py +0 -0
  216. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/cycode_token_based_client.py +0 -0
  217. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/headers.py +0 -0
  218. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/import_sbom_client.py +0 -0
  219. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/logger.py +0 -0
  220. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/models.py +0 -0
  221. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/report_client.py +0 -0
  222. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/scan_client.py +0 -0
  223. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/cyclient/scan_config_base.py +0 -0
  224. {cycode-3.23.0 → cycode-3.23.1.dev1}/cycode/logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycode
3
- Version: 3.23.0
3
+ Version: 3.23.1.dev1
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
@@ -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.23.0' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
8
+ __version__ = '3.23.1.dev1' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
@@ -22,6 +22,18 @@ class GuardrailsMode(str, Enum):
22
22
  BLOCK = 'block'
23
23
 
24
24
 
25
+ class GuardrailCellMode(str, Enum):
26
+ """A guardrail x agent cell in the platform-resolved matrix.
27
+
28
+ Separate from GuardrailsMode because Off is a platform-only state: it is not an
29
+ install `--mode` choice, and an off guardrail reports no mode to the server.
30
+ """
31
+
32
+ OFF = 'off'
33
+ REPORT = GuardrailsMode.REPORT.value
34
+ BLOCK = GuardrailsMode.BLOCK.value
35
+
36
+
25
37
  # Base CLI commands invoked from installed hooks. IDE classes append --ide flags
26
38
  # (and any other suffix) on top of these.
27
39
  CYCODE_SCAN_PROMPT_COMMAND = 'cycode ai-guardrails scan'
@@ -12,9 +12,9 @@ from typing import Optional
12
12
 
13
13
  import yaml
14
14
 
15
- from cycode.cli.apps.ai_guardrails.consts import PolicyMode
16
15
  from cycode.cli.apps.ai_guardrails.ides.base import IDE
17
16
  from cycode.cli.apps.ai_guardrails.scan.consts import DEFAULT_POLICY, POLICY_FILE_NAME
17
+ from cycode.cli.apps.ai_guardrails.scan.policy import strip_platform_managed_keys
18
18
  from cycode.logger import get_logger
19
19
 
20
20
  logger = get_logger('AI Guardrails Hooks')
@@ -102,22 +102,21 @@ def _load_policy_dict(policy_path: Path) -> dict:
102
102
  return {**copy.deepcopy(DEFAULT_POLICY), **existing}
103
103
 
104
104
 
105
- def create_policy_file(scope: str, mode: PolicyMode, repo_path: Optional[Path] = None) -> tuple[bool, str]:
106
- """Create or update the ai-guardrails.yaml policy file.
105
+ def create_policy_file(scope: str, repo_path: Optional[Path] = None) -> tuple[bool, str]:
106
+ """Create or update the ai-guardrails.yaml policy file (operational knobs only).
107
107
 
108
- If the file already exists, only the mode field is updated; otherwise a new
109
- file is created from the default policy.
108
+ Enforcement mode and sensitive-path globs are platform-managed; those keys are stripped
109
+ (including ones an older CLI wrote), everything else the user customized is preserved.
110
110
  """
111
111
  config_dir = repo_path / '.cycode' if scope == 'repo' and repo_path else Path.home() / '.cycode'
112
112
  policy_path = config_dir / POLICY_FILE_NAME
113
113
 
114
- policy = _load_policy_dict(policy_path)
115
- policy['mode'] = mode.value
114
+ policy = strip_platform_managed_keys(_load_policy_dict(policy_path))
116
115
 
117
116
  try:
118
117
  config_dir.mkdir(parents=True, exist_ok=True)
119
118
  policy_path.write_text(yaml.dump(policy, default_flow_style=False, sort_keys=False), encoding='utf-8')
120
- return True, f'AI guardrails policy ({mode.value} mode) set: {policy_path}'
119
+ return True, f'AI guardrails policy file set: {policy_path}'
121
120
  except Exception as e:
122
121
  logger.error('Failed to create policy file', exc_info=e)
123
122
  return False, f'Failed to create policy file: {policy_path}'
@@ -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 GuardrailsMode, PolicyMode
9
+ from cycode.cli.apps.ai_guardrails.consts import GuardrailsMode
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
 
@@ -44,8 +44,7 @@ def install_command(
44
44
  typer.Option(
45
45
  '--mode',
46
46
  '-m',
47
- help='Installation mode: "report" for async non-blocking hooks with warn policy, '
48
- '"block" for sync blocking hooks.',
47
+ help='[Deprecated] Enforcement mode is platform-managed; configure guardrails in the Cycode platform.',
49
48
  ),
50
49
  ] = GuardrailsMode.REPORT,
51
50
  ) -> None:
@@ -80,32 +79,34 @@ def install_command(
80
79
  console.print(f'[red]✗[/] {message}', style='bold red')
81
80
  all_success = False
82
81
 
82
+ if mode == GuardrailsMode.BLOCK:
83
+ console.print(
84
+ '[yellow]--mode is deprecated:[/] enforcement mode is platform-managed; '
85
+ 'configure guardrails in the Cycode platform.'
86
+ )
87
+
83
88
  if any_success:
84
- policy_mode = PolicyMode.WARN if mode == GuardrailsMode.REPORT else PolicyMode.BLOCK
85
- _install_policy(scope, repo_path, policy_mode)
86
- _print_next_steps(results, mode)
89
+ _install_policy(scope, repo_path)
90
+ _print_next_steps(results)
87
91
 
88
92
  if not all_success:
89
93
  raise typer.Exit(1)
90
94
 
91
95
 
92
- def _install_policy(scope: str, repo_path: Optional[Path], policy_mode: PolicyMode) -> None:
93
- policy_success, policy_message = create_policy_file(scope, policy_mode, repo_path)
96
+ def _install_policy(scope: str, repo_path: Optional[Path]) -> None:
97
+ policy_success, policy_message = create_policy_file(scope, repo_path)
94
98
  if policy_success:
95
99
  console.print(f'[green]✓[/] {policy_message}')
96
100
  else:
97
101
  console.print(f'[red]✗[/] {policy_message}', style='bold red')
98
102
 
99
103
 
100
- def _print_next_steps(results: list[tuple[str, bool, str]], mode: GuardrailsMode) -> None:
104
+ def _print_next_steps(results: list[tuple[str, bool, str]]) -> None:
101
105
  console.print()
102
106
  console.print('[bold]Next steps:[/]')
103
107
  successful_ides = [name for name, success, _ in results if success]
104
108
  ide_list = ', '.join(successful_ides)
105
109
  console.print(f'1. Restart {ide_list} to activate the hooks')
106
- console.print('2. (Optional) Customize policy in ~/.cycode/ai-guardrails.yaml')
110
+ console.print('2. Configure guardrail enforcement in the Cycode platform')
107
111
  console.print()
108
- if mode == GuardrailsMode.REPORT:
109
- console.print('[dim]Report mode: policy is set to warn.[/]')
110
- else:
111
- console.print('[dim]The hooks will scan prompts, file reads, and MCP tool calls for secrets.[/]')
112
+ console.print('[dim]The hooks will scan prompts, file reads, and MCP tool calls for secrets.[/]')
@@ -0,0 +1,37 @@
1
+ """
2
+ Constants and default configuration for AI guardrails.
3
+
4
+ Enforcement (which guardrails run, in which mode, over which paths) is platform-owned and
5
+ resolved per scan; see scan/guardrail_config.py. What is left here is the operational knobs
6
+ a local file may override - user-level ~/.cycode/ai-guardrails.yaml, then repo-level
7
+ <workspace>/.cycode/ai-guardrails.yaml.
8
+ """
9
+
10
+ # Policy file name
11
+ POLICY_FILE_NAME = 'ai-guardrails.yaml'
12
+
13
+ # Sensitive-path globs used until the platform's own list is cached (cold start, or a tenant
14
+ # that never customized them). Not a local knob: apply_platform_config always overwrites it.
15
+ DEFAULT_SENSITIVE_PATH_GLOBS = [
16
+ '.env',
17
+ '.env.*',
18
+ '*.pem',
19
+ '*.p12',
20
+ '*.key',
21
+ '.aws/**',
22
+ '.ssh/**',
23
+ '*kubeconfig*',
24
+ '.npmrc',
25
+ '.netrc',
26
+ ]
27
+
28
+ # Default policy configuration: operational knobs only.
29
+ DEFAULT_POLICY = {
30
+ 'version': 1,
31
+ 'fail_open': True, # allow if scan fails/timeouts
32
+ 'secrets': {
33
+ 'scan_type': 'secret',
34
+ 'timeout_ms': 30000,
35
+ 'max_bytes': 200000,
36
+ },
37
+ }
@@ -0,0 +1,165 @@
1
+ """Platform guardrail config cache.
2
+
3
+ session-start fetches the tenant's resolved guardrail config from the platform and writes it
4
+ here; scans only read. Per-agent modes and sensitive-path globs are platform-owned - local
5
+ policy files never carry them. An absent or corrupt cache means built-in defaults (Report
6
+ everywhere + the default globs), always synchronous.
7
+ """
8
+
9
+ import json
10
+ import time
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from typing import Optional
14
+
15
+ from cycode.cli.apps.ai_guardrails.consts import GuardrailCellMode, PolicyMode
16
+ from cycode.cli.apps.ai_guardrails.scan.consts import DEFAULT_SENSITIVE_PATH_GLOBS
17
+ from cycode.cli.apps.ai_guardrails.scan.types import BlockReason
18
+ from cycode.cli.consts import CYCODE_CONFIGURATION_DIRECTORY
19
+ from cycode.cli.utils.path_utils import atomic_write_text, quarantine_corrupt_file
20
+ from cycode.logger import get_logger
21
+
22
+ logger = get_logger('AI Guardrails')
23
+
24
+ GUARDRAILS_CONFIG_FILE_NAME = 'ai-guardrails-config.json'
25
+
26
+ _DEFAULT_TTL_SECONDS = 900
27
+
28
+ # Guardrail keys are the CLI's block-reason vocabulary. Anything else in the payload (a future
29
+ # guardrail this CLI doesn't implement) is ignored - unknown config must never fail closed.
30
+ _KNOWN_GUARDRAIL_KEYS = frozenset(
31
+ reason.value
32
+ for reason in (
33
+ BlockReason.SECRETS_IN_PROMPT,
34
+ BlockReason.SECRETS_IN_FILE,
35
+ BlockReason.SENSITIVE_PATH,
36
+ BlockReason.SECRETS_IN_MCP_ARGS,
37
+ )
38
+ )
39
+
40
+ # CLI --ide names to matrix column names; identity for names not listed.
41
+ _AGENT_BY_IDE_NAME = {'claude-code': 'claude'}
42
+
43
+
44
+ def get_config_cache_path() -> Path:
45
+ return Path.home() / CYCODE_CONFIGURATION_DIRECTORY / GUARDRAILS_CONFIG_FILE_NAME
46
+
47
+
48
+ def agent_for_ide(ide_name: Optional[str]) -> str:
49
+ ide_name = (ide_name or '').lower()
50
+ return _AGENT_BY_IDE_NAME.get(ide_name, ide_name)
51
+
52
+
53
+ def _default_sensitive_globs() -> list:
54
+ return list(DEFAULT_SENSITIVE_PATH_GLOBS)
55
+
56
+
57
+ @dataclass
58
+ class GuardrailConfig:
59
+ payload: dict
60
+ fetched_at: float
61
+ tenant_id: Optional[str] = None
62
+ _guardrails: dict = field(init=False, repr=False)
63
+
64
+ def __post_init__(self) -> None:
65
+ self._guardrails = {
66
+ guardrail.get('key'): guardrail
67
+ for guardrail in self.payload.get('guardrails') or []
68
+ if guardrail.get('key') in _KNOWN_GUARDRAIL_KEYS
69
+ }
70
+
71
+ def mode_for(self, guardrail_key: str, ide_name: Optional[str]) -> str:
72
+ agents = (self._guardrails.get(guardrail_key) or {}).get('agents') or {}
73
+ return str(agents.get(agent_for_ide(ide_name), GuardrailCellMode.REPORT.value)).lower()
74
+
75
+ def _modes_for_event(self, event_name: str, ide_name: Optional[str]) -> list:
76
+ return [
77
+ self.mode_for(key, ide_name)
78
+ for key, guardrail in self._guardrails.items()
79
+ if str(guardrail.get('event_type', '')).lower() == str(event_name).lower()
80
+ ]
81
+
82
+ def is_event_off(self, event_name: str, ide_name: Optional[str]) -> bool:
83
+ """Every guardrail for this event is Off - skip the scan entirely."""
84
+ modes = self._modes_for_event(event_name, ide_name)
85
+ return bool(modes) and all(mode == GuardrailCellMode.OFF for mode in modes)
86
+
87
+ def can_event_block(self, event_name: str, ide_name: Optional[str]) -> bool:
88
+ """At least one guardrail for this event is in Block mode - the scan must stay synchronous."""
89
+ return GuardrailCellMode.BLOCK in self._modes_for_event(event_name, ide_name)
90
+
91
+ def sensitive_globs(self) -> list:
92
+ settings = (self._guardrails.get(BlockReason.SENSITIVE_PATH) or {}).get('settings') or {}
93
+ globs = settings.get('globs')
94
+ return globs if isinstance(globs, list) and globs else _default_sensitive_globs()
95
+
96
+ def is_expired(self) -> bool:
97
+ ttl = self.payload.get('ttl_seconds') or _DEFAULT_TTL_SECONDS
98
+ return time.time() - self.fetched_at > ttl
99
+
100
+ def needs_refresh(self, tenant_id: Optional[str]) -> bool:
101
+ """Expired, or fetched for another tenant (the user switched tenants since)."""
102
+ return self.is_expired() or self.tenant_id != tenant_id
103
+
104
+
105
+ def apply_platform_config(policy: dict, config: Optional[GuardrailConfig], ide_name: Optional[str]) -> None:
106
+ """Overlay the platform-owned enforcement config onto the local knobs-only policy.
107
+
108
+ The platform is the only mode source: no cache (cold start) means the built-in defaults -
109
+ Report everywhere with the default globs - which equal an unconfigured tenant's platform
110
+ config, so behaviour is uniform either way. Each matrix cell lands on its own per-feature
111
+ action, so the two FileRead guardrails (content scan vs. sensitive path) keep independent modes.
112
+ An all-Off event never reaches here at all: scan_command skips it.
113
+ """
114
+
115
+ def cell(guardrail_key: str) -> str:
116
+ return config.mode_for(guardrail_key, ide_name) if config is not None else GuardrailCellMode.REPORT.value
117
+
118
+ def action(guardrail_key: str) -> str:
119
+ return PolicyMode.BLOCK.value if cell(guardrail_key) == GuardrailCellMode.BLOCK else PolicyMode.WARN.value
120
+
121
+ policy.setdefault('prompt', {})['action'] = action(BlockReason.SECRETS_IN_PROMPT)
122
+
123
+ file_read = policy.setdefault('file_read', {})
124
+ file_read['scan_content'] = cell(BlockReason.SECRETS_IN_FILE) != GuardrailCellMode.OFF
125
+ file_read['action'] = action(BlockReason.SECRETS_IN_FILE)
126
+ file_read['deny_globs'] = (
127
+ (config.sensitive_globs() if config is not None else _default_sensitive_globs())
128
+ if cell(BlockReason.SENSITIVE_PATH) != GuardrailCellMode.OFF
129
+ else []
130
+ )
131
+ file_read['path_action'] = action(BlockReason.SENSITIVE_PATH)
132
+
133
+ policy.setdefault('mcp', {})['action'] = action(BlockReason.SECRETS_IN_MCP_ARGS)
134
+
135
+
136
+ def save_guardrail_config(payload: dict, tenant_id: Optional[str]) -> None:
137
+ """Persist a fetched resolved config; a failed write just leaves the previous cache in place."""
138
+ path = get_config_cache_path()
139
+ content = {'fetched_at': time.time(), 'tenant_id': tenant_id, 'payload': payload}
140
+ try:
141
+ path.parent.mkdir(parents=True, exist_ok=True)
142
+ atomic_write_text(str(path), json.dumps(content))
143
+ except Exception as e:
144
+ logger.debug('Failed to save guardrail config cache', exc_info=e)
145
+
146
+
147
+ def load_guardrail_config() -> Optional[GuardrailConfig]:
148
+ """The cached platform config, or None when it is absent or corrupt (quarantined)."""
149
+ path = get_config_cache_path()
150
+ if not path.exists():
151
+ return None
152
+
153
+ try:
154
+ with open(path, encoding='UTF-8') as file:
155
+ content = json.load(file)
156
+ payload = content['payload']
157
+ if not isinstance(payload, dict):
158
+ raise ValueError('payload is not an object')
159
+ return GuardrailConfig(
160
+ payload=payload, fetched_at=float(content['fetched_at']), tenant_id=content.get('tenant_id')
161
+ )
162
+ except Exception as e:
163
+ logger.warning('Guardrail config cache is corrupt and will be moved aside', exc_info=e)
164
+ quarantine_corrupt_file(str(path))
165
+ return None
@@ -13,10 +13,13 @@ import os
13
13
  from dataclasses import dataclass
14
14
  from multiprocessing.pool import ThreadPool
15
15
  from multiprocessing.pool import TimeoutError as PoolTimeoutError
16
- from typing import Callable, Optional
16
+ from typing import TYPE_CHECKING, Callable, Optional
17
17
 
18
18
  import typer
19
19
 
20
+ if TYPE_CHECKING:
21
+ from cycode.cli.apps.ai_guardrails.scan.guardrail_config import GuardrailConfig
22
+
20
23
  from cycode.cli.apps.ai_guardrails.consts import GuardrailsMode, PolicyMode
21
24
  from cycode.cli.apps.ai_guardrails.ides.base import HookDecision
22
25
  from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
@@ -48,11 +51,7 @@ def handle_before_submit_prompt(ctx: typer.Context, payload: AIHookPayload, poli
48
51
  ai_client = ctx.obj['ai_security_client']
49
52
 
50
53
  prompt_config = get_policy_value(policy, 'prompt', default={})
51
- if not get_policy_value(prompt_config, 'enabled', default=True):
52
- ai_client.create_event(payload, AiHookEventType.PROMPT, AIHookOutcome.ALLOWED)
53
- return HookDecision.allow(AiHookEventType.PROMPT)
54
-
55
- effective_mode = get_effective_mode(policy, prompt_config)
54
+ effective_mode = get_effective_mode(prompt_config)
56
55
  prompt = payload.prompt or ''
57
56
  max_bytes = get_policy_value(policy, 'secrets', 'max_bytes', default=200000)
58
57
  timeout_ms = get_policy_value(policy, 'secrets', 'timeout_ms', default=30000)
@@ -104,12 +103,9 @@ def handle_before_read_file(ctx: typer.Context, payload: AIHookPayload, policy:
104
103
  ai_client = ctx.obj['ai_security_client']
105
104
 
106
105
  file_read_config = get_policy_value(policy, 'file_read', default={})
107
- if not get_policy_value(file_read_config, 'enabled', default=True):
108
- ai_client.create_event(payload, AiHookEventType.FILE_READ, AIHookOutcome.ALLOWED)
109
- return HookDecision.allow(AiHookEventType.FILE_READ)
110
-
111
106
  file_path = payload.file_path or ''
112
- effective_mode = get_effective_mode(policy, file_read_config)
107
+ path_mode = get_effective_mode(file_read_config, action_key='path_action')
108
+ content_mode = get_effective_mode(file_read_config)
113
109
 
114
110
  scan_id = None
115
111
  block_reason = None
@@ -120,7 +116,7 @@ def handle_before_read_file(ctx: typer.Context, payload: AIHookPayload, policy:
120
116
  is_sensitive_path = is_denied_path(file_path, policy)
121
117
  if is_sensitive_path:
122
118
  block_reason = BlockReason.SENSITIVE_PATH
123
- if effective_mode == GuardrailsMode.BLOCK:
119
+ if path_mode == GuardrailsMode.BLOCK:
124
120
  outcome = AIHookOutcome.BLOCKED
125
121
  user_message = f'Cycode blocked sending {file_path} to the AI (sensitive path policy).'
126
122
  return HookDecision.deny(
@@ -144,11 +140,11 @@ def handle_before_read_file(ctx: typer.Context, payload: AIHookPayload, policy:
144
140
 
145
141
  if get_policy_value(file_read_config, 'scan_content', default=True):
146
142
  violation_summary, scan_id = _scan_path_for_secrets(
147
- ctx, file_path, policy, payload=payload, effective_mode=effective_mode
143
+ ctx, file_path, policy, payload=payload, effective_mode=content_mode
148
144
  )
149
145
  if violation_summary:
150
146
  block_reason = SECRETS_BLOCK_REASON_BY_EVENT_TYPE[AiHookEventType.FILE_READ]
151
- if effective_mode == GuardrailsMode.BLOCK:
147
+ if content_mode == GuardrailsMode.BLOCK:
152
148
  outcome = AIHookOutcome.BLOCKED
153
149
  user_message = f'Cycode blocked reading {file_path}. {violation_summary}'
154
150
  return HookDecision.deny(
@@ -201,7 +197,6 @@ class _ArgScanFeature:
201
197
  """
202
198
 
203
199
  policy_key: str # 'mcp' or 'command_exec'
204
- scan_key: str # 'scan_arguments' or 'scan_command'
205
200
  event_type: AiHookEventType
206
201
  deny_message: Callable[[str], str]
207
202
  deny_agent_message: str
@@ -220,14 +215,10 @@ def _handle_arg_scan(
220
215
  ai_client = ctx.obj['ai_security_client']
221
216
 
222
217
  feature_config = get_policy_value(policy, feature.policy_key, default={})
223
- if not get_policy_value(feature_config, 'enabled', default=True):
224
- ai_client.create_event(payload, feature.event_type, AIHookOutcome.ALLOWED)
225
- return HookDecision.allow(feature.event_type)
226
-
227
218
  max_bytes = get_policy_value(policy, 'secrets', 'max_bytes', default=200000)
228
219
  timeout_ms = get_policy_value(policy, 'secrets', 'timeout_ms', default=30000)
229
220
  clipped = truncate_utf8(scan_text, max_bytes)
230
- effective_mode = get_effective_mode(policy, feature_config)
221
+ effective_mode = get_effective_mode(feature_config)
231
222
 
232
223
  scan_id = None
233
224
  block_reason = None
@@ -235,30 +226,29 @@ def _handle_arg_scan(
235
226
  error_message = None
236
227
 
237
228
  try:
238
- if get_policy_value(feature_config, feature.scan_key, default=True):
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
- )
247
- if violation_summary:
248
- block_reason = SECRETS_BLOCK_REASON_BY_EVENT_TYPE[feature.event_type]
249
- if effective_mode == GuardrailsMode.BLOCK:
250
- outcome = AIHookOutcome.BLOCKED
251
- return HookDecision.deny(
252
- feature.event_type,
253
- feature.deny_message(violation_summary),
254
- feature.deny_agent_message,
255
- )
256
- outcome = AIHookOutcome.WARNED
257
- return HookDecision.ask(
229
+ violation_summary, scan_id = _scan_text_for_secrets(
230
+ ctx,
231
+ clipped,
232
+ timeout_ms,
233
+ payload=payload,
234
+ event_type=feature.event_type,
235
+ effective_mode=effective_mode,
236
+ )
237
+ if violation_summary:
238
+ block_reason = SECRETS_BLOCK_REASON_BY_EVENT_TYPE[feature.event_type]
239
+ if effective_mode == GuardrailsMode.BLOCK:
240
+ outcome = AIHookOutcome.BLOCKED
241
+ return HookDecision.deny(
258
242
  feature.event_type,
259
- feature.ask_message(violation_summary),
260
- feature.ask_agent_message,
243
+ feature.deny_message(violation_summary),
244
+ feature.deny_agent_message,
261
245
  )
246
+ outcome = AIHookOutcome.WARNED
247
+ return HookDecision.ask(
248
+ feature.event_type,
249
+ feature.ask_message(violation_summary),
250
+ feature.ask_agent_message,
251
+ )
262
252
 
263
253
  return HookDecision.allow(feature.event_type)
264
254
  except Exception as e:
@@ -290,7 +280,6 @@ def handle_before_mcp_execution(ctx: typer.Context, payload: AIHookPayload, poli
290
280
  policy,
291
281
  _ArgScanFeature(
292
282
  policy_key='mcp',
293
- scan_key='scan_arguments',
294
283
  event_type=AiHookEventType.MCP_EXECUTION,
295
284
  deny_message=lambda v: f'Cycode blocked MCP tool call "{tool}". {v}',
296
285
  deny_agent_message='Do not pass secrets to tools. Use secret references (name/id) instead.',
@@ -311,35 +300,29 @@ def get_handler_for_event(event_type: str) -> Optional[HandlerFn]:
311
300
  return handlers.get(event_type)
312
301
 
313
302
 
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
-
303
+ def get_effective_mode(feature_config: dict, action_key: str = 'action') -> GuardrailsMode:
304
+ """A guardrail's action is its matrix cell: block, or warn (report) for everything else."""
305
+ action = get_policy_value(feature_config, action_key, default=PolicyMode.BLOCK)
306
+ return GuardrailsMode.BLOCK if action == PolicyMode.BLOCK else GuardrailsMode.REPORT
320
307
 
321
- # The policy section each event's handler reads its feature config from.
322
- _FEATURE_KEY_BY_EVENT_TYPE: dict[str, str] = {
323
- AiHookEventType.PROMPT.value: 'prompt',
324
- AiHookEventType.FILE_READ.value: 'file_read',
325
- AiHookEventType.MCP_EXECUTION.value: 'mcp',
326
- }
327
308
 
328
-
329
- def should_detach_scan(policy: dict, event_name: str) -> bool:
309
+ def should_detach_scan(
310
+ config: Optional['GuardrailConfig'],
311
+ policy: dict,
312
+ event_name: str,
313
+ ide_name: Optional[str],
314
+ ) -> bool:
330
315
  """Whether this event's scan is safe to run detached.
331
316
 
332
- Report mode never blocks, so nobody consumes the verdict. Fail-closed
333
- configs stay synchronous even in report mode: their deny on scan failure
334
- must reach the IDE. Unknown events stay synchronous - they exit fast anyway.
317
+ Report mode never blocks, so nobody consumes the verdict. The platform config is the only
318
+ mode source: without a cache the scan stays synchronous (never detach on an assumption).
319
+ Fail-closed configs also stay synchronous: their deny on scan failure must reach the IDE.
335
320
  """
336
- feature_key = _FEATURE_KEY_BY_EVENT_TYPE.get(event_name)
337
- if feature_key is None:
321
+ if config is None:
338
322
  return False
339
323
  if not get_policy_value(policy, 'fail_open', default=True):
340
324
  return False
341
- feature_config = get_policy_value(policy, feature_key, default={})
342
- return get_effective_mode(policy, feature_config) == GuardrailsMode.REPORT
325
+ return not config.can_event_block(event_name, ide_name)
343
326
 
344
327
 
345
328
  def build_ai_guardrails_scan_parameters(
@@ -1,11 +1,14 @@
1
1
  """
2
2
  Policy loading and configuration management for AI guardrails.
3
3
 
4
- Policies are loaded and merged in order (later overrides earlier):
4
+ Operational knobs are loaded and merged in order (later overrides earlier):
5
5
  1. Built-in defaults (consts.DEFAULT_POLICY)
6
6
  2. Machine-wide config (admin/MDM-provisioned; see get_machine_policy_path)
7
7
  3. User-level config (~/.cycode/ai-guardrails.yaml)
8
8
  4. Repo-level config (<workspace>/.cycode/ai-guardrails.yaml)
9
+
10
+ Enforcement is not part of that merge: the platform resolves it and apply_platform_config
11
+ overlays it on top of the result (see scan/guardrail_config.py).
9
12
  """
10
13
 
11
14
  import json
@@ -17,6 +20,34 @@ from typing import Any, Optional
17
20
  import yaml
18
21
 
19
22
  from cycode.cli.apps.ai_guardrails.scan.consts import DEFAULT_POLICY, POLICY_FILE_NAME
23
+ from cycode.logger import get_logger
24
+
25
+ logger = get_logger('AI Guardrails')
26
+
27
+ # A local policy file may only contribute operational knobs; enforcement comes from the
28
+ # platform. Read as a whitelist rather than a strip-list, so a key we forget to enumerate
29
+ # is ignored by default instead of silently weakening enforcement.
30
+ _LOCAL_POLICY_KEYS = ('version', 'fail_open', 'secrets')
31
+
32
+ # The sections the platform resolves. Only used when rewriting a user's file: keys an older
33
+ # CLI wrote there still read as enforcement to a human, so drop them on the way out.
34
+ _PLATFORM_OWNED_KEYS = ('mode', 'prompt', 'file_read', 'mcp')
35
+
36
+
37
+ def pick_local_knobs(config: dict, filename: str) -> dict:
38
+ """Keep only the keys a local policy file is allowed to contribute."""
39
+ ignored = [key for key in config if key not in _LOCAL_POLICY_KEYS]
40
+ if ignored:
41
+ logger.debug(
42
+ 'Ignoring non-knob keys in local policy file, %s',
43
+ {'filename': filename, 'keys': ignored},
44
+ )
45
+ return {key: value for key, value in config.items() if key in _LOCAL_POLICY_KEYS}
46
+
47
+
48
+ def strip_platform_managed_keys(config: dict) -> dict:
49
+ """Drop the enforcement sections a local file may carry (older CLIs wrote them)."""
50
+ return {key: value for key, value in config.items() if key not in _PLATFORM_OWNED_KEYS}
20
51
 
21
52
 
22
53
  def get_machine_policy_path() -> Path:
@@ -83,21 +114,22 @@ def load_policy(workspace_root: Optional[str] = None) -> dict:
83
114
  policy = load_defaults()
84
115
 
85
116
  # Merge machine-wide config (admin/MDM-provisioned) - overrides defaults, below user/repo.
86
- machine_config = load_yaml_file(get_machine_policy_path())
117
+ machine_policy_path = get_machine_policy_path()
118
+ machine_config = load_yaml_file(machine_policy_path)
87
119
  if machine_config:
88
- policy = deep_merge(policy, machine_config)
120
+ policy = deep_merge(policy, pick_local_knobs(machine_config, str(machine_policy_path)))
89
121
 
90
122
  # Merge user-level config (if exists)
91
123
  user_policy_path = Path.home() / '.cycode' / POLICY_FILE_NAME
92
124
  user_config = load_yaml_file(user_policy_path)
93
125
  if user_config:
94
- policy = deep_merge(policy, user_config)
126
+ policy = deep_merge(policy, pick_local_knobs(user_config, str(user_policy_path)))
95
127
 
96
128
  # Merge repo-level config (if exists) - highest precedence
97
129
  if workspace_root:
98
130
  repo_policy_path = Path(workspace_root) / '.cycode' / POLICY_FILE_NAME
99
131
  repo_config = load_yaml_file(repo_policy_path)
100
132
  if repo_config:
101
- policy = deep_merge(policy, repo_config)
133
+ policy = deep_merge(policy, pick_local_knobs(repo_config, str(repo_policy_path)))
102
134
 
103
135
  return policy