cycode 3.25.1.dev5__tar.gz → 3.25.2.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.

Potentially problematic release.


This version of cycode might be problematic. Click here for more details.

Files changed (230) hide show
  1. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/PKG-INFO +1 -1
  2. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/__init__.py +1 -1
  3. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/__init__.py +8 -9
  4. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py +6 -6
  5. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/base.py +10 -10
  6. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/claude_code.py +42 -4
  7. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/codex.py +45 -11
  8. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/copilot.py +16 -18
  9. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/cursor.py +45 -7
  10. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/guardrail_config.py +34 -11
  11. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/handlers.py +86 -14
  12. cycode-3.25.2.dev1/cycode/cli/apps/ai_guardrails/scan/mcp_server_status.py +103 -0
  13. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/types.py +1 -0
  14. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/session_start_command.py +42 -17
  15. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/git_proxy.py +30 -16
  16. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/ai_security_manager_client.py +11 -5
  17. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/models.py +59 -1
  18. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/pyproject.toml +1 -1
  19. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/LICENCE +0 -0
  20. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/README.md +0 -0
  21. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/__main__.py +0 -0
  22. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/__init__.py +0 -0
  23. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/app.py +0 -0
  24. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/__init__.py +0 -0
  25. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/activation_manager.py +0 -0
  26. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/__init__.py +0 -0
  27. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/command_utils.py +0 -0
  28. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/consts.py +0 -0
  29. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/hooks_manager.py +0 -0
  30. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/_skill_utils.py +0 -0
  31. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/install_command.py +0 -0
  32. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/__init__.py +0 -0
  33. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/consts.py +0 -0
  34. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/detach.py +0 -0
  35. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/payload.py +0 -0
  36. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/policy.py +0 -0
  37. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/scan_command.py +0 -0
  38. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/utils.py +0 -0
  39. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/status_command.py +0 -0
  40. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/uninstall_command.py +0 -0
  41. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_remediation/__init__.py +0 -0
  42. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_remediation/ai_remediation_command.py +0 -0
  43. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_remediation/apply_fix.py +0 -0
  44. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_remediation/print_remediation.py +0 -0
  45. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/api/__init__.py +0 -0
  46. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/api/api_command.py +0 -0
  47. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/api/openapi_spec.py +0 -0
  48. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/auth/__init__.py +0 -0
  49. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/auth/auth_command.py +0 -0
  50. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/auth/auth_common.py +0 -0
  51. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/auth/auth_manager.py +0 -0
  52. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/auth/models.py +0 -0
  53. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/configure/__init__.py +0 -0
  54. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/configure/configure_command.py +0 -0
  55. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/configure/consts.py +0 -0
  56. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/configure/messages.py +0 -0
  57. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/configure/prompts.py +0 -0
  58. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ignore/__init__.py +0 -0
  59. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/ignore/ignore_command.py +0 -0
  60. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/mcp/__init__.py +0 -0
  61. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/mcp/mcp_command.py +0 -0
  62. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report/__init__.py +0 -0
  63. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report/report_command.py +0 -0
  64. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/__init__.py +0 -0
  65. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/common.py +0 -0
  66. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/path/__init__.py +0 -0
  67. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/path/path_command.py +0 -0
  68. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/repository_url/__init__.py +0 -0
  69. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/repository_url/repository_url_command.py +0 -0
  70. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/sbom_command.py +0 -0
  71. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/sbom_report_file.py +0 -0
  72. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report_import/__init__.py +0 -0
  73. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report_import/report_import_command.py +0 -0
  74. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report_import/sbom/__init__.py +0 -0
  75. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/report_import/sbom/sbom_command.py +0 -0
  76. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/sca_options.py +0 -0
  77. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/__init__.py +0 -0
  78. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/aggregation_report.py +0 -0
  79. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/code_scanner.py +0 -0
  80. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/commit_history/__init__.py +0 -0
  81. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/commit_history/commit_history_command.py +0 -0
  82. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/commit_range_scanner.py +0 -0
  83. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/detection_excluder.py +0 -0
  84. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/path/__init__.py +0 -0
  85. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/path/path_command.py +0 -0
  86. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/pre_commit/__init__.py +0 -0
  87. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/pre_commit/pre_commit_command.py +0 -0
  88. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/pre_push/__init__.py +0 -0
  89. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/pre_push/pre_push_command.py +0 -0
  90. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/pre_receive/__init__.py +0 -0
  91. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/pre_receive/pre_receive_command.py +0 -0
  92. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/remote_url_resolver.py +0 -0
  93. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/repository/__init__.py +0 -0
  94. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/repository/repository_command.py +0 -0
  95. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/scan_ci/__init__.py +0 -0
  96. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/scan_ci/ci_integrations.py +0 -0
  97. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/scan_ci/scan_ci_command.py +0 -0
  98. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/scan_command.py +0 -0
  99. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/scan_parameters.py +0 -0
  100. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/scan_result.py +0 -0
  101. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/status/__init__.py +0 -0
  102. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/status/get_cli_status.py +0 -0
  103. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/status/models.py +0 -0
  104. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/status/status_command.py +0 -0
  105. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/apps/status/version_command.py +0 -0
  106. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/cli_types.py +0 -0
  107. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/config.py +0 -0
  108. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/console.py +0 -0
  109. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/consts.py +0 -0
  110. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/exceptions/__init__.py +0 -0
  111. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/exceptions/custom_exceptions.py +0 -0
  112. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/exceptions/handle_ai_remediation_errors.py +0 -0
  113. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/exceptions/handle_auth_errors.py +0 -0
  114. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/exceptions/handle_errors.py +0 -0
  115. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/exceptions/handle_report_sbom_errors.py +0 -0
  116. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/exceptions/handle_scan_errors.py +0 -0
  117. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/__init__.py +0 -0
  118. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/commit_range_documents.py +0 -0
  119. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/documents_walk_ignore.py +0 -0
  120. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/file_excluder.py +0 -0
  121. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/iac/__init__.py +0 -0
  122. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
  123. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/models/__init__.py +0 -0
  124. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
  125. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/path_documents.py +0 -0
  126. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/repository_documents.py +0 -0
  127. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/__init__.py +0 -0
  128. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/base_restore_dependencies.py +0 -0
  129. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/go/__init__.py +0 -0
  130. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/go/restore_go_dependencies.py +0 -0
  131. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/maven/__init__.py +0 -0
  132. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
  133. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
  134. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/__init__.py +0 -0
  135. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/restore_bun_dependencies.py +0 -0
  136. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/restore_deno_dependencies.py +0 -0
  137. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py +0 -0
  138. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/restore_pnpm_dependencies.py +0 -0
  139. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/restore_yarn_dependencies.py +0 -0
  140. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/workspace/__init__.py +0 -0
  141. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/workspace/coverage.py +0 -0
  142. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/workspace/files.py +0 -0
  143. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/workspace/globs.py +0 -0
  144. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/workspace/names.py +0 -0
  145. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/workspace/resolvers.py +0 -0
  146. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/nuget/__init__.py +0 -0
  147. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py +0 -0
  148. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/php/__init__.py +0 -0
  149. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/php/restore_composer_dependencies.py +0 -0
  150. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/python/__init__.py +0 -0
  151. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/python/restore_pip_dependencies.py +0 -0
  152. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/python/restore_pipenv_dependencies.py +0 -0
  153. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/python/restore_poetry_dependencies.py +0 -0
  154. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/python/restore_uv_dependencies.py +0 -0
  155. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/ruby/__init__.py +0 -0
  156. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py +0 -0
  157. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/sbt/__init__.py +0 -0
  158. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py +0 -0
  159. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/sca_file_collector.py +0 -0
  160. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/walk_ignore.py +0 -0
  161. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/files_collector/zip_documents.py +0 -0
  162. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/logger.py +0 -0
  163. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/main.py +0 -0
  164. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/models.py +0 -0
  165. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/__init__.py +0 -0
  166. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/console_printer.py +0 -0
  167. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/json_printer.py +0 -0
  168. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/printer_base.py +0 -0
  169. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/rich_printer.py +0 -0
  170. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/tables/__init__.py +0 -0
  171. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
  172. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/tables/table.py +0 -0
  173. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/tables/table_models.py +0 -0
  174. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/tables/table_printer.py +0 -0
  175. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/tables/table_printer_base.py +0 -0
  176. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/text_printer.py +0 -0
  177. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/__init__.py +0 -0
  178. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/code_snippet_syntax.py +0 -0
  179. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/detection_data.py +0 -0
  180. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/detection_ordering/__init__.py +0 -0
  181. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/detection_ordering/common_ordering.py +0 -0
  182. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/detection_ordering/sca_ordering.py +0 -0
  183. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/rich_helpers.py +0 -0
  184. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/sca_ossf.py +0 -0
  185. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/sca_policy_details.py +0 -0
  186. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/user_settings/__init__.py +0 -0
  187. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/user_settings/base_file_manager.py +0 -0
  188. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/user_settings/config_file_manager.py +0 -0
  189. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/user_settings/configuration_manager.py +0 -0
  190. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/user_settings/credentials_manager.py +0 -0
  191. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/user_settings/jwt_creator.py +0 -0
  192. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/__init__.py +0 -0
  193. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/binary_utils.py +0 -0
  194. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/enum_utils.py +0 -0
  195. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/get_api_client.py +0 -0
  196. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/host_info.py +0 -0
  197. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/ignore_utils.py +0 -0
  198. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/jwt_utils.py +0 -0
  199. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/path_utils.py +0 -0
  200. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/progress_bar.py +0 -0
  201. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/scan_batch.py +0 -0
  202. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/scan_utils.py +0 -0
  203. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/shell_executor.py +0 -0
  204. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/string_utils.py +0 -0
  205. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/task_timer.py +0 -0
  206. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/trust_store.py +0 -0
  207. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/url_utils.py +0 -0
  208. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/version_checker.py +0 -0
  209. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cli/utils/yaml_utils.py +0 -0
  210. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/config.py +0 -0
  211. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/__init__.py +0 -0
  212. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/ai_security_manager_service_config.py +0 -0
  213. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/auth_client.py +0 -0
  214. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/base_token_auth_client.py +0 -0
  215. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/cli_activation_client.py +0 -0
  216. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/client_creator.py +0 -0
  217. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/config.py +0 -0
  218. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/config_dev.py +0 -0
  219. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/cycode_client.py +0 -0
  220. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/cycode_client_base.py +0 -0
  221. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/cycode_dev_based_client.py +0 -0
  222. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/cycode_oidc_based_client.py +0 -0
  223. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/cycode_token_based_client.py +0 -0
  224. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/headers.py +0 -0
  225. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/import_sbom_client.py +0 -0
  226. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/logger.py +0 -0
  227. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/report_client.py +0 -0
  228. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/scan_client.py +0 -0
  229. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/cyclient/scan_config_base.py +0 -0
  230. {cycode-3.25.1.dev5 → cycode-3.25.2.dev1}/cycode/logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycode
3
- Version: 3.25.1.dev5
3
+ Version: 3.25.2.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.25.1.dev5' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
8
+ __version__ = '3.25.2.dev1' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
@@ -35,23 +35,22 @@ def get_ide(name: str) -> IDE:
35
35
  return ide
36
36
 
37
37
 
38
- def collect_all_session_contexts() -> tuple[dict[str, dict], dict]:
38
+ def collect_all_session_contexts() -> tuple[list[dict], dict]:
39
39
  """Sweep every registered IDE's session context, regardless of which IDE triggered the hook.
40
40
 
41
- Returns ``(config_files_by_ide, plugins)``: the global MCP config file of each IDE that has
42
- one (keyed by IDE name), and the enabled plugins merged across IDEs (first registered IDE
43
- wins on a duplicate plugin key - plugins are IDE-agnostic marketplace artifacts).
41
+ Returns ``(config_files, plugins)``: the global MCP config files of every IDE, and the
42
+ enabled plugins merged across IDEs (first registered IDE wins on a duplicate plugin key -
43
+ plugins are IDE-agnostic marketplace artifacts).
44
44
  """
45
- config_files_by_ide: dict[str, dict] = {}
45
+ config_files: list[dict] = []
46
46
  plugins: dict = {}
47
47
  for ide in IDES.values():
48
- global_config_file, enabled_plugins = ide.get_session_context()
49
- if global_config_file:
50
- config_files_by_ide[ide.name] = global_config_file
48
+ global_config_files, enabled_plugins = ide.get_session_context()
49
+ config_files.extend(global_config_files)
51
50
  for plugin_key, plugin in (enabled_plugins or {}).items():
52
51
  plugins.setdefault(plugin_key, plugin)
53
52
 
54
- return config_files_by_ide, plugins
53
+ return config_files, plugins
55
54
 
56
55
 
57
56
  def collect_all_skills() -> list[dict]:
@@ -42,17 +42,17 @@ def load_plugin_json(path: Path) -> Optional[dict]:
42
42
  return None
43
43
 
44
44
 
45
- def build_global_config_file(path: Path, mcp_servers: Optional[dict]) -> Optional[dict]:
45
+ def build_global_config_files(path: Path, mcp_servers: Optional[dict]) -> list[dict]:
46
46
  """Wrap a global (non-plugin) MCP config into the session-context file shape.
47
47
 
48
- Returns ``{"path": <full path>, "content": <{"mcpServers": ...} JSON>}`` when
49
- there are servers, else ``None``. ``content`` is normalized to the canonical
48
+ Returns ``[{"path": <full path>, "content": <{"mcpServers": ...} JSON>}]`` when
49
+ there are servers, else ``[]``. ``content`` is normalized to the canonical
50
50
  ``{"mcpServers": {...}}`` shape, dropping everything else in the source file.
51
51
  """
52
- servers = mcp_servers or {}
52
+ servers = mcp_servers if isinstance(mcp_servers, dict) else {}
53
53
  if not servers:
54
- return None
55
- return {'path': str(path), 'content': json.dumps({'mcpServers': servers})}
54
+ return []
55
+ return [{'path': str(path), 'content': json.dumps({'mcpServers': servers})}]
56
56
 
57
57
 
58
58
  def walk_enabled_plugins(
@@ -176,19 +176,19 @@ class IDE(ABC):
176
176
  """
177
177
  return None
178
178
 
179
- def get_session_context(self) -> tuple[Optional[dict], dict]:
180
- """Return ``(global_config_file, enabled_plugins)`` for session-context reporting.
179
+ def get_session_context(self) -> tuple[list[dict], dict]:
180
+ """Return ``(global_config_files, enabled_plugins)`` for session-context reporting.
181
181
 
182
- ``global_config_file`` is the IDE's global (non-plugin) MCP config as
183
- ``{"path": <full path>, "content": <normalized {"mcpServers": ...} JSON>}``,
184
- or ``None`` when there is no global MCP config. ``enabled_plugins`` maps each
185
- enabled plugin key to its metadata (including its own ``mcp_config_file``
186
- content and ``mcp_config_file_path``).
182
+ ``global_config_files`` are the IDE's global (non-plugin) MCP configs, each as
183
+ ``{"path": <full path>, "content": <normalized {"mcpServers": ...} JSON>}``;
184
+ empty when there is none. ``enabled_plugins`` maps each enabled plugin key to
185
+ its metadata (including its own ``mcp_config_file`` content and
186
+ ``mcp_config_file_path``).
187
187
 
188
- Default: ``(None, {})`` (no plugin system, no discoverable MCP config).
188
+ Default: ``([], {})`` (no plugin system, no discoverable MCP config).
189
189
  Override to surface MCP/plugin inventory.
190
190
  """
191
- return None, {}
191
+ return [], {}
192
192
 
193
193
  def get_skills(self) -> list[dict]:
194
194
  """Return the IDE's user-scope skills as ``[{"path", "content"}]``.
@@ -198,7 +198,7 @@ class IDE(ABC):
198
198
  read these files off endpoints can only ever return raw content.
199
199
 
200
200
  Kept separate from ``get_session_context`` rather than folded into its
201
- ``global_config_file`` slot, which is normalized to an MCP server map.
201
+ ``global_config_files``, which are normalized to an MCP server map.
202
202
 
203
203
  Default: ``[]`` (the IDE has no skill system). Override to surface skills.
204
204
  """
@@ -1,6 +1,7 @@
1
1
  """Claude Code IDE integration for AI guardrails."""
2
2
 
3
3
  import json
4
+ import re
4
5
  from collections.abc import Iterator
5
6
  from copy import deepcopy
6
7
  from pathlib import Path
@@ -8,7 +9,7 @@ from typing import ClassVar, Optional
8
9
 
9
10
  from cycode.cli.apps.ai_guardrails.consts import CYCODE_SCAN_PROMPT_COMMAND, CYCODE_SESSION_START_COMMAND
10
11
  from cycode.cli.apps.ai_guardrails.ides._plugin_utils import (
11
- build_global_config_file,
12
+ build_global_config_files,
12
13
  load_plugin_json,
13
14
  resolve_cached_plugin_dir,
14
15
  walk_enabled_plugins,
@@ -253,6 +254,41 @@ def resolve_plugins(settings: dict) -> dict:
253
254
  )
254
255
 
255
256
 
257
+ def _sanitize_mcp_name(name: str) -> str:
258
+ """Mirrors how Claude Code sanitizes a server name inside a tool name."""
259
+ return re.sub(r'[^a-z0-9_-]', '_', name.lower())
260
+
261
+
262
+ def _local_mcp_servers(config: Optional[dict], cwd: Optional[str]) -> Iterator[tuple[str, str]]:
263
+ """Yield ``(name as Claude Code namespaces it, config key)`` for each locally configured MCP server."""
264
+ sources = []
265
+ if config:
266
+ sources.append(get_mcp_servers(config))
267
+ if cwd:
268
+ sources.append(((config.get('projects') or {}).get(cwd) or {}).get('mcpServers'))
269
+ if cwd:
270
+ sources.append((load_plugin_json(Path(cwd) / '.mcp.json') or {}).get('mcpServers'))
271
+ for servers in sources:
272
+ if isinstance(servers, dict):
273
+ for name in servers:
274
+ yield name, name
275
+
276
+ settings = load_claude_settings()
277
+ for plugin_key, entry in (resolve_plugins(settings) if settings else {}).items():
278
+ plugin_name = plugin_key.split('@', 1)[0]
279
+ for server in entry.get('mcp_server_names') or []:
280
+ yield f'plugin_{plugin_name}_{server}', server
281
+
282
+
283
+ def _resolve_mcp_server_name(server: str, config: Optional[dict], cwd: Optional[str]) -> str:
284
+ """Map the server segment of ``mcp__<server>__<tool>`` back to its config key; unknown names pass through."""
285
+ wanted = _sanitize_mcp_name(server)
286
+ for namespaced, name in _local_mcp_servers(config, cwd):
287
+ if _sanitize_mcp_name(namespaced) == wanted:
288
+ return name
289
+ return server
290
+
291
+
256
292
  # --- IDE integration ----------------------------------------------------------
257
293
 
258
294
 
@@ -339,6 +375,8 @@ class ClaudeCode(IDE):
339
375
 
340
376
  config = load_claude_config()
341
377
  ide_user_email = _email_from_config(config) if config else None
378
+ if mcp_server_name:
379
+ mcp_server_name = _resolve_mcp_server_name(mcp_server_name, config, raw_payload.get('cwd'))
342
380
 
343
381
  return AIHookPayload(
344
382
  event_name=canonical_event,
@@ -396,14 +434,14 @@ class ClaudeCode(IDE):
396
434
  config = load_claude_config()
397
435
  return _email_from_config(config) if config else None
398
436
 
399
- def get_session_context(self) -> tuple[Optional[dict], dict]:
437
+ def get_session_context(self) -> tuple[list[dict], dict]:
400
438
  config = load_claude_config()
401
- global_config_file = build_global_config_file(_CLAUDE_CONFIG_PATH, get_mcp_servers(config)) if config else None
439
+ global_config_files = build_global_config_files(_CLAUDE_CONFIG_PATH, get_mcp_servers(config)) if config else []
402
440
 
403
441
  settings = load_claude_settings()
404
442
  enriched_plugins = resolve_plugins(settings) if settings else {}
405
443
 
406
- return global_config_file, enriched_plugins
444
+ return global_config_files, enriched_plugins
407
445
 
408
446
  def get_skills(self) -> list[dict]:
409
447
  return walk_skill_dirs(_claude_skills_dir())
@@ -2,7 +2,9 @@
2
2
 
3
3
  import json
4
4
  import os
5
+ import re
5
6
  import sys
7
+ from collections.abc import Iterator
6
8
  from pathlib import Path
7
9
  from typing import ClassVar, Optional
8
10
 
@@ -15,7 +17,7 @@ else: # pragma: no cover - py<3.11 fallback
15
17
 
16
18
  from cycode.cli.apps.ai_guardrails.consts import CYCODE_SCAN_PROMPT_COMMAND, CYCODE_SESSION_START_COMMAND
17
19
  from cycode.cli.apps.ai_guardrails.ides._plugin_utils import (
18
- build_global_config_file,
20
+ build_global_config_files,
19
21
  load_plugin_json,
20
22
  resolve_cached_plugin_dir,
21
23
  walk_enabled_plugins,
@@ -158,6 +160,42 @@ def _resolve_codex_plugins(config: dict) -> dict:
158
160
  )
159
161
 
160
162
 
163
+ def _sanitize_mcp_name(name: str) -> str:
164
+ """Mirrors how Codex sanitizes a server name into a tool namespace."""
165
+ return re.sub(r'[^A-Za-z0-9_]', '_', name) or '_'
166
+
167
+
168
+ def _local_mcp_server_names() -> Iterator[str]:
169
+ """Yield the config key of each locally configured Codex MCP server, plugin servers included."""
170
+ config = _load_codex_config()
171
+ if not config:
172
+ return
173
+ servers = config.get('mcp_servers')
174
+ if isinstance(servers, dict):
175
+ yield from servers
176
+ for entry in _resolve_codex_plugins(config).values():
177
+ yield from entry.get('mcp_server_names') or []
178
+
179
+
180
+ def _split_mcp_tool_name(tool_name: str) -> tuple[Optional[str], Optional[str]]:
181
+ """Split ``mcp__<namespace>__<tool>`` into ``(server config key, tool)``; unknown namespaces split on ``__``."""
182
+ rest = tool_name[len('mcp__') :]
183
+ names = list(_local_mcp_server_names())
184
+ # Codex appends `_<12 hex>` to a namespace on collision or length overflow.
185
+ for suffix in ('__', r'_+[0-9a-f]{12}__'):
186
+ best: Optional[tuple[int, str]] = None
187
+ for name in names:
188
+ namespace = _sanitize_mcp_name(name).rstrip('_')
189
+ match = namespace and re.match(re.escape(namespace) + suffix, rest)
190
+ if match and (best is None or match.end() > best[0]):
191
+ best = (match.end(), name)
192
+ if best:
193
+ return best[1], rest[best[0] :]
194
+
195
+ parts = tool_name.split('__')
196
+ return (parts[1] if len(parts) >= 2 else None), (parts[2] if len(parts) >= 3 else None)
197
+
198
+
161
199
  def _enable_codex_hooks_feature(scope: str, repo_path: Optional[Path] = None) -> tuple[bool, str]:
162
200
  """Set ``[features] hooks = true`` in Codex's ``config.toml``.
163
201
 
@@ -247,12 +285,8 @@ class Codex(IDE):
247
285
  mcp_server_name = None
248
286
  mcp_tool_name = None
249
287
  mcp_arguments = None
250
- if tool_name.startswith('mcp__'):
251
- parts = tool_name.split('__')
252
- if len(parts) >= 2:
253
- mcp_server_name = parts[1]
254
- if len(parts) >= 3:
255
- mcp_tool_name = parts[2]
288
+ if canonical_event == AiHookEventType.MCP_EXECUTION:
289
+ mcp_server_name, mcp_tool_name = _split_mcp_tool_name(tool_name)
256
290
  mcp_arguments = tool_input
257
291
 
258
292
  return AIHookPayload(
@@ -302,17 +336,17 @@ class Codex(IDE):
302
336
  def get_user_email(self) -> Optional[str]:
303
337
  return _email_from_auth()
304
338
 
305
- def get_session_context(self) -> tuple[Optional[dict], dict]:
339
+ def get_session_context(self) -> tuple[list[dict], dict]:
306
340
  config = _load_codex_config()
307
341
  if not config:
308
- return None, {}
342
+ return [], {}
309
343
  # Codex stores MCP servers under `[mcp_servers.<name>]`; the global config
310
344
  # file becomes its own session-context file. Plugins (via
311
345
  # `[plugins."<plugin>@<marketplace>"]`) carry their own config files.
312
346
  config_path = _codex_config_toml_path('user')
313
- global_config_file = build_global_config_file(config_path, config.get('mcp_servers'))
347
+ global_config_files = build_global_config_files(config_path, config.get('mcp_servers'))
314
348
  enriched_plugins = _resolve_codex_plugins(config)
315
- return global_config_file, enriched_plugins
349
+ return global_config_files, enriched_plugins
316
350
 
317
351
  def get_skills(self) -> list[dict]:
318
352
  return walk_skill_dirs(_codex_skills_dir())
@@ -33,7 +33,7 @@ from urllib.request import url2pathname
33
33
 
34
34
  from cycode.cli.apps.ai_guardrails.consts import CYCODE_SCAN_PROMPT_COMMAND, CYCODE_SESSION_START_COMMAND
35
35
  from cycode.cli.apps.ai_guardrails.ides._plugin_utils import (
36
- build_global_config_file,
36
+ build_global_config_files,
37
37
  load_plugin_json,
38
38
  walk_enabled_plugins,
39
39
  )
@@ -283,6 +283,15 @@ def _collect_installed_plugins() -> dict:
283
283
  # --- MCP tool-name splitting ------------------------------------------------------
284
284
 
285
285
 
286
+ def _global_mcp_server_maps() -> list[tuple[Path, object]]:
287
+ """``(path, servers)`` of each runtime's global MCP config: VS Code's ``servers``, the agent's ``mcpServers``."""
288
+ agent_config_path = _copilot_home() / _AGENT_MCP_CONFIG_FILENAME
289
+ return [
290
+ (_vscode_mcp_config_path(), (_load_vscode_mcp_config() or {}).get('servers')),
291
+ (agent_config_path, (_load_jsonc(agent_config_path) or {}).get('mcpServers')),
292
+ ]
293
+
294
+
286
295
  def _known_mcp_server_names() -> list[str]:
287
296
  """Config-declared MCP server names, across both runtimes' config files.
288
297
 
@@ -293,15 +302,7 @@ def _known_mcp_server_names() -> list[str]:
293
302
  Best-effort inventory: servers contributed by extensions, ``chat.mcp.discovery``
294
303
  imports, dev containers, or non-default profiles are not discoverable from disk.
295
304
  """
296
- config = _load_vscode_mcp_config()
297
- servers = (config or {}).get('servers')
298
- names = list(servers.keys()) if isinstance(servers, dict) else []
299
-
300
- agent_config = _load_jsonc(_copilot_home() / _AGENT_MCP_CONFIG_FILENAME) or {}
301
- agent_servers = agent_config.get('mcpServers')
302
- if isinstance(agent_servers, dict):
303
- names.extend(agent_servers.keys())
304
-
305
+ names = [name for _, servers in _global_mcp_server_maps() if isinstance(servers, dict) for name in servers]
305
306
  for plugin in _collect_installed_plugins().values():
306
307
  names.extend(plugin.get('mcp_server_names') or [])
307
308
  return names
@@ -485,14 +486,11 @@ class Copilot(IDE):
485
486
  source=raw_payload.get('source'),
486
487
  )
487
488
 
488
- def get_session_context(self) -> tuple[Optional[dict], dict]:
489
- # VS Code's mcp.json uses `servers` as its top-level key; normalized to the
490
- # canonical mcpServers shape by build_global_config_file.
491
- config = _load_vscode_mcp_config()
492
- global_config_file = (
493
- build_global_config_file(_vscode_mcp_config_path(), config.get('servers')) if config else None
494
- )
495
- return global_config_file, _collect_installed_plugins()
489
+ def get_session_context(self) -> tuple[list[dict], dict]:
490
+ global_config_files = [
491
+ file for path, servers in _global_mcp_server_maps() for file in build_global_config_files(path, servers)
492
+ ]
493
+ return global_config_files, _collect_installed_plugins()
496
494
 
497
495
  def get_skills(self) -> list[dict]:
498
496
  return walk_skill_dirs(_copilot_skills_dir())
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from typing import ClassVar, Optional
7
7
 
8
8
  from cycode.cli.apps.ai_guardrails.consts import CYCODE_SCAN_PROMPT_COMMAND, CYCODE_SESSION_START_COMMAND
9
- from cycode.cli.apps.ai_guardrails.ides._plugin_utils import build_global_config_file
9
+ from cycode.cli.apps.ai_guardrails.ides._plugin_utils import build_global_config_files
10
10
  from cycode.cli.apps.ai_guardrails.ides._skill_utils import walk_skill_dirs
11
11
  from cycode.cli.apps.ai_guardrails.ides.base import IDE, DecisionAction, HookDecision
12
12
  from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
@@ -64,6 +64,44 @@ def _load_cursor_mcp_config(config_path: Optional[Path] = None) -> Optional[dict
64
64
  return None
65
65
 
66
66
 
67
+ def _cursor_mcp_config_paths(workspace_roots: object) -> list[Path]:
68
+ roots = workspace_roots if isinstance(workspace_roots, list) else []
69
+ paths = [Path(root) / _REPO_SUBDIR / _MCP_CONFIG_FILENAME for root in roots if isinstance(root, str) and root]
70
+ paths.append(_cursor_mcp_config_path())
71
+ return paths
72
+
73
+
74
+ def _server_command_line(server: dict) -> Optional[str]:
75
+ command = server.get('command')
76
+ if not isinstance(command, str) or not command:
77
+ return None
78
+ args = server.get('args')
79
+ return ' '.join([command, *(str(arg) for arg in args)]) if isinstance(args, list) else command
80
+
81
+
82
+ def _resolve_mcp_server_name(raw_payload: dict) -> Optional[str]:
83
+ """Cursor sends the server's url or command, but the platform knows it by its mcp.json entry name."""
84
+ url = raw_payload.get('url')
85
+ command = raw_payload.get('command')
86
+ if not url and not command:
87
+ return None
88
+
89
+ lowered_command = command.lower() if command else None
90
+ for config_path in _cursor_mcp_config_paths(raw_payload.get('workspace_roots')):
91
+ servers = (_load_cursor_mcp_config(config_path) or {}).get('mcpServers')
92
+ if not isinstance(servers, dict):
93
+ continue
94
+ for name, server in servers.items():
95
+ if not isinstance(server, dict):
96
+ continue
97
+ if url and server.get('url') == url:
98
+ return name
99
+ if command and (command == _server_command_line(server) or lowered_command == name.lower()):
100
+ return name
101
+
102
+ return command
103
+
104
+
67
105
  class Cursor(IDE):
68
106
  name: ClassVar[str] = 'cursor'
69
107
  display_name: ClassVar[str] = 'Cursor'
@@ -95,7 +133,9 @@ class Cursor(IDE):
95
133
  ide_version=raw_payload.get('cursor_version'),
96
134
  prompt=raw_payload.get('prompt', ''),
97
135
  file_path=raw_payload.get('file_path') or raw_payload.get('path'),
98
- mcp_server_name=raw_payload.get('command'),
136
+ mcp_server_name=(
137
+ _resolve_mcp_server_name(raw_payload) if canonical_event == AiHookEventType.MCP_EXECUTION else None
138
+ ),
99
139
  mcp_tool_name=raw_payload.get('tool_name') or raw_payload.get('tool'),
100
140
  mcp_arguments=(raw_payload.get('arguments') or raw_payload.get('tool_input') or raw_payload.get('input')),
101
141
  )
@@ -124,13 +164,11 @@ class Cursor(IDE):
124
164
  ide_version=raw_payload.get('cursor_version'),
125
165
  )
126
166
 
127
- def get_session_context(self) -> tuple[Optional[dict], dict]:
167
+ def get_session_context(self) -> tuple[list[dict], dict]:
128
168
  config = _load_cursor_mcp_config()
129
169
  if not config:
130
- return None, {}
131
- config_path = _cursor_mcp_config_path()
132
- global_config_file = build_global_config_file(config_path, config.get('mcpServers'))
133
- return global_config_file, {}
170
+ return [], {}
171
+ return build_global_config_files(_cursor_mcp_config_path(), config.get('mcpServers')), {}
134
172
 
135
173
  def get_skills(self) -> list[dict]:
136
174
  return walk_skill_dirs(_cursor_skills_dir())
@@ -3,7 +3,7 @@
3
3
  session-start fetches the tenant's resolved guardrail config from the platform and writes it
4
4
  here; scans only read. Per-agent modes and sensitive-path globs are platform-owned - local
5
5
  policy files never carry them. An absent or corrupt cache means built-in defaults (Report
6
- everywhere + the default globs), always synchronous.
6
+ everywhere + the default globs, with the unauthorized MCP server guardrail Off), always synchronous.
7
7
  """
8
8
 
9
9
  import json
@@ -23,7 +23,7 @@ logger = get_logger('AI Guardrails')
23
23
 
24
24
  GUARDRAILS_CONFIG_FILE_NAME = 'ai-guardrails-config.json'
25
25
 
26
- _DEFAULT_TTL_SECONDS = 900
26
+ DEFAULT_TTL_SECONDS = 900
27
27
 
28
28
  # Guardrail keys are the CLI's block-reason vocabulary. Anything else in the payload (a future
29
29
  # guardrail this CLI doesn't implement) is ignored - unknown config must never fail closed.
@@ -34,14 +34,24 @@ _KNOWN_GUARDRAIL_KEYS = frozenset(
34
34
  BlockReason.SECRETS_IN_FILE,
35
35
  BlockReason.SENSITIVE_PATH,
36
36
  BlockReason.SECRETS_IN_MCP_ARGS,
37
+ BlockReason.UNAUTHORIZED_MCP_SERVER,
37
38
  )
38
39
  )
39
40
 
41
+ # Off by default (not Report), so they never start enforcing on a tenant that hasn't configured them.
42
+ _DEFAULT_OFF_GUARDRAIL_KEYS = frozenset((BlockReason.UNAUTHORIZED_MCP_SERVER.value,))
43
+
40
44
 
41
45
  def get_config_cache_path() -> Path:
42
46
  return Path.home() / CYCODE_CONFIGURATION_DIRECTORY / GUARDRAILS_CONFIG_FILE_NAME
43
47
 
44
48
 
49
+ def default_mode_for(guardrail_key: str) -> str:
50
+ if guardrail_key in _DEFAULT_OFF_GUARDRAIL_KEYS:
51
+ return GuardrailCellMode.OFF.value
52
+ return GuardrailCellMode.REPORT.value
53
+
54
+
45
55
  def _default_sensitive_globs() -> list:
46
56
  return list(DEFAULT_SENSITIVE_PATH_GLOBS)
47
57
 
@@ -60,10 +70,20 @@ class GuardrailConfig:
60
70
  if guardrail.get('key') in _KNOWN_GUARDRAIL_KEYS
61
71
  }
62
72
 
73
+ @property
74
+ def ttl_seconds(self) -> float:
75
+ return self.payload.get('ttl_seconds') or DEFAULT_TTL_SECONDS
76
+
77
+ def _agents(self, guardrail_key: str) -> dict:
78
+ return (self._guardrails.get(guardrail_key) or {}).get('agents') or {}
79
+
63
80
  def mode_for(self, guardrail_key: str, ide_name: Optional[str]) -> str:
64
81
  """The platform keys the cells by our --ide names, so the lookup is direct."""
65
- agents = (self._guardrails.get(guardrail_key) or {}).get('agents') or {}
66
- return str(agents.get((ide_name or '').lower(), GuardrailCellMode.REPORT.value)).lower()
82
+ agents = self._agents(guardrail_key)
83
+ return str(agents.get((ide_name or '').lower(), default_mode_for(guardrail_key))).lower()
84
+
85
+ def is_off_for_every_agent(self, guardrail_key: str) -> bool:
86
+ return all(str(mode).lower() == GuardrailCellMode.OFF for mode in self._agents(guardrail_key).values())
67
87
 
68
88
  def _modes_for_event(self, event_name: str, ide_name: Optional[str]) -> list:
69
89
  return [
@@ -87,8 +107,7 @@ class GuardrailConfig:
87
107
  return globs if isinstance(globs, list) and globs else _default_sensitive_globs()
88
108
 
89
109
  def is_expired(self) -> bool:
90
- ttl = self.payload.get('ttl_seconds') or _DEFAULT_TTL_SECONDS
91
- return time.time() - self.fetched_at > ttl
110
+ return time.time() - self.fetched_at > self.ttl_seconds
92
111
 
93
112
  def needs_refresh(self, tenant_id: Optional[str]) -> bool:
94
113
  """Expired, or fetched for another tenant (the user switched tenants since)."""
@@ -99,14 +118,14 @@ def apply_platform_config(policy: dict, config: Optional[GuardrailConfig], ide_n
99
118
  """Overlay the platform-owned enforcement config onto the local knobs-only policy.
100
119
 
101
120
  The platform is the only mode source: no cache (cold start) means the built-in defaults -
102
- Report everywhere with the default globs - which equal an unconfigured tenant's platform
103
- config, so behaviour is uniform either way. Each matrix cell lands on its own per-feature
104
- action, so the two FileRead guardrails (content scan vs. sensitive path) keep independent modes.
121
+ Report everywhere (unauthorized MCP server Off) with the default globs - which equal an unconfigured
122
+ tenant's platform config, so behaviour is uniform either way. Each matrix cell lands on its own
123
+ per-feature action, so guardrails sharing an event (e.g. the two FileRead ones) keep independent modes.
105
124
  An all-Off event never reaches here at all: scan_command skips it.
106
125
  """
107
126
 
108
127
  def cell(guardrail_key: str) -> str:
109
- return config.mode_for(guardrail_key, ide_name) if config is not None else GuardrailCellMode.REPORT.value
128
+ return config.mode_for(guardrail_key, ide_name) if config is not None else default_mode_for(guardrail_key)
110
129
 
111
130
  def action(guardrail_key: str) -> str:
112
131
  return PolicyMode.BLOCK.value if cell(guardrail_key) == GuardrailCellMode.BLOCK else PolicyMode.WARN.value
@@ -123,7 +142,11 @@ def apply_platform_config(policy: dict, config: Optional[GuardrailConfig], ide_n
123
142
  )
124
143
  file_read['path_action'] = action(BlockReason.SENSITIVE_PATH)
125
144
 
126
- policy.setdefault('mcp', {})['action'] = action(BlockReason.SECRETS_IN_MCP_ARGS)
145
+ mcp = policy.setdefault('mcp', {})
146
+ mcp['scan_args'] = cell(BlockReason.SECRETS_IN_MCP_ARGS) != GuardrailCellMode.OFF
147
+ mcp['action'] = action(BlockReason.SECRETS_IN_MCP_ARGS)
148
+ mcp['check_server'] = cell(BlockReason.UNAUTHORIZED_MCP_SERVER) != GuardrailCellMode.OFF
149
+ mcp['server_action'] = action(BlockReason.UNAUTHORIZED_MCP_SERVER)
127
150
 
128
151
 
129
152
  def save_guardrail_config(payload: dict, tenant_id: Optional[str]) -> None: