cycode 3.25.1.dev6__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.
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/PKG-INFO +1 -1
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/__init__.py +1 -1
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/__init__.py +8 -9
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py +6 -6
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/base.py +10 -10
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/claude_code.py +42 -4
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/codex.py +45 -11
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/copilot.py +16 -18
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/cursor.py +45 -7
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/guardrail_config.py +34 -11
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/handlers.py +66 -2
- cycode-3.25.2.dev1/cycode/cli/apps/ai_guardrails/scan/mcp_server_status.py +103 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/types.py +1 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/session_start_command.py +42 -17
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/ai_security_manager_client.py +11 -5
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/models.py +59 -1
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/pyproject.toml +1 -1
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/LICENCE +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/README.md +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/__main__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/app.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/activation_manager.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/command_utils.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/consts.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/hooks_manager.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/_skill_utils.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/install_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/consts.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/detach.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/payload.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/policy.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/scan_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/utils.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/status_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/uninstall_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_remediation/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_remediation/ai_remediation_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_remediation/apply_fix.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_remediation/print_remediation.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/api/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/api/api_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/api/openapi_spec.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/auth/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/auth/auth_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/auth/auth_common.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/auth/auth_manager.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/auth/models.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/configure/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/configure/configure_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/configure/consts.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/configure/messages.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/configure/prompts.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ignore/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ignore/ignore_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/mcp/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/mcp/mcp_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report/report_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/common.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/path/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/path/path_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/repository_url/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/repository_url/repository_url_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/sbom_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report/sbom/sbom_report_file.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report_import/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report_import/report_import_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report_import/sbom/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/report_import/sbom/sbom_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/sca_options.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/aggregation_report.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/code_scanner.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/commit_history/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/commit_history/commit_history_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/commit_range_scanner.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/detection_excluder.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/path/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/path/path_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/pre_commit/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/pre_commit/pre_commit_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/pre_push/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/pre_push/pre_push_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/pre_receive/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/pre_receive/pre_receive_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/remote_url_resolver.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/repository/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/repository/repository_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/scan_ci/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/scan_ci/ci_integrations.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/scan_ci/scan_ci_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/scan_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/scan_parameters.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/scan/scan_result.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/status/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/status/get_cli_status.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/status/models.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/status/status_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/status/version_command.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/cli_types.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/config.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/console.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/consts.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/exceptions/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/exceptions/custom_exceptions.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/exceptions/handle_ai_remediation_errors.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/exceptions/handle_auth_errors.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/exceptions/handle_errors.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/exceptions/handle_report_sbom_errors.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/exceptions/handle_scan_errors.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/commit_range_documents.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/documents_walk_ignore.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/file_excluder.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/iac/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/models/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/path_documents.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/repository_documents.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/base_restore_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/go/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/go/restore_go_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/maven/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/restore_bun_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/restore_deno_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/restore_pnpm_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/restore_yarn_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/workspace/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/workspace/coverage.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/workspace/files.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/workspace/globs.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/workspace/names.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/npm/workspace/resolvers.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/nuget/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/php/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/php/restore_composer_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/python/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/python/restore_pip_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/python/restore_pipenv_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/python/restore_poetry_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/python/restore_uv_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/ruby/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/sbt/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/sca/sca_file_collector.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/walk_ignore.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/files_collector/zip_documents.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/logger.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/main.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/models.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/console_printer.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/json_printer.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/printer_base.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/rich_printer.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/tables/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/tables/table.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/tables/table_models.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/tables/table_printer.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/tables/table_printer_base.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/text_printer.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/code_snippet_syntax.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/detection_data.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/detection_ordering/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/detection_ordering/common_ordering.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/detection_ordering/sca_ordering.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/rich_helpers.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/sca_ossf.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/printers/utils/sca_policy_details.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/user_settings/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/user_settings/base_file_manager.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/user_settings/config_file_manager.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/user_settings/configuration_manager.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/user_settings/credentials_manager.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/user_settings/jwt_creator.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/binary_utils.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/enum_utils.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/get_api_client.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/git_proxy.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/host_info.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/ignore_utils.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/jwt_utils.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/path_utils.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/progress_bar.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/scan_batch.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/scan_utils.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/shell_executor.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/string_utils.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/task_timer.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/trust_store.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/url_utils.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/version_checker.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/utils/yaml_utils.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/config.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/__init__.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/ai_security_manager_service_config.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/auth_client.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/base_token_auth_client.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/cli_activation_client.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/client_creator.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/config.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/config_dev.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/cycode_client.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/cycode_client_base.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/cycode_dev_based_client.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/cycode_oidc_based_client.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/cycode_token_based_client.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/headers.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/import_sbom_client.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/logger.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/report_client.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/scan_client.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cyclient/scan_config_base.py +0 -0
- {cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/logger.py +0 -0
|
@@ -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.
|
|
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[
|
|
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 ``(
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
45
|
+
config_files: list[dict] = []
|
|
46
46
|
plugins: dict = {}
|
|
47
47
|
for ide in IDES.values():
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
53
|
+
return config_files, plugins
|
|
55
54
|
|
|
56
55
|
|
|
57
56
|
def collect_all_skills() -> list[dict]:
|
{cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py
RENAMED
|
@@ -42,17 +42,17 @@ def load_plugin_json(path: Path) -> Optional[dict]:
|
|
|
42
42
|
return None
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def
|
|
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 ``
|
|
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
|
|
52
|
+
servers = mcp_servers if isinstance(mcp_servers, dict) else {}
|
|
53
53
|
if not servers:
|
|
54
|
-
return
|
|
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[
|
|
180
|
-
"""Return ``(
|
|
179
|
+
def get_session_context(self) -> tuple[list[dict], dict]:
|
|
180
|
+
"""Return ``(global_config_files, enabled_plugins)`` for session-context reporting.
|
|
181
181
|
|
|
182
|
-
``
|
|
183
|
-
``{"path": <full path>, "content": <normalized {"mcpServers": ...} JSON>}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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: ``(
|
|
188
|
+
Default: ``([], {})`` (no plugin system, no discoverable MCP config).
|
|
189
189
|
Override to surface MCP/plugin inventory.
|
|
190
190
|
"""
|
|
191
|
-
return
|
|
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
|
-
``
|
|
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
|
-
|
|
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[
|
|
437
|
+
def get_session_context(self) -> tuple[list[dict], dict]:
|
|
400
438
|
config = load_claude_config()
|
|
401
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
251
|
-
|
|
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[
|
|
339
|
+
def get_session_context(self) -> tuple[list[dict], dict]:
|
|
306
340
|
config = _load_codex_config()
|
|
307
341
|
if not config:
|
|
308
|
-
return
|
|
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
|
-
|
|
347
|
+
global_config_files = build_global_config_files(config_path, config.get('mcp_servers'))
|
|
314
348
|
enriched_plugins = _resolve_codex_plugins(config)
|
|
315
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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[
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
|
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=
|
|
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[
|
|
167
|
+
def get_session_context(self) -> tuple[list[dict], dict]:
|
|
128
168
|
config = _load_cursor_mcp_config()
|
|
129
169
|
if not config:
|
|
130
|
-
return
|
|
131
|
-
|
|
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())
|
{cycode-3.25.1.dev6 → cycode-3.25.2.dev1}/cycode/cli/apps/ai_guardrails/scan/guardrail_config.py
RENAMED
|
@@ -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
|
-
|
|
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 =
|
|
66
|
-
return str(agents.get((ide_name or '').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
|
-
|
|
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
|
|
103
|
-
config, so behaviour is uniform either way. Each matrix cell lands on its own
|
|
104
|
-
action, so
|
|
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
|
|
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', {})
|
|
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:
|
|
@@ -21,6 +21,7 @@ if TYPE_CHECKING:
|
|
|
21
21
|
|
|
22
22
|
from cycode.cli.apps.ai_guardrails.consts import GuardrailsMode, PolicyMode
|
|
23
23
|
from cycode.cli.apps.ai_guardrails.ides.base import HookDecision
|
|
24
|
+
from cycode.cli.apps.ai_guardrails.scan.mcp_server_status import is_enforced, load_mcp_server_statuses
|
|
24
25
|
from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
|
|
25
26
|
from cycode.cli.apps.ai_guardrails.scan.policy import get_policy_value
|
|
26
27
|
from cycode.cli.apps.ai_guardrails.scan.types import (
|
|
@@ -217,6 +218,16 @@ class _ArgScanFeature:
|
|
|
217
218
|
deny_agent_message: str
|
|
218
219
|
ask_message: Callable[[str], str]
|
|
219
220
|
ask_agent_message: str
|
|
221
|
+
scan_enabled: bool = True
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class _PreScanFinding(NamedTuple):
|
|
225
|
+
"""A finding on the call itself (e.g. an unauthorized MCP server), made before the text scan."""
|
|
226
|
+
|
|
227
|
+
block_reason: BlockReason
|
|
228
|
+
mode: GuardrailsMode
|
|
229
|
+
deny_message: str
|
|
230
|
+
deny_agent_message: str
|
|
220
231
|
|
|
221
232
|
|
|
222
233
|
def _handle_arg_scan(
|
|
@@ -225,8 +236,12 @@ def _handle_arg_scan(
|
|
|
225
236
|
policy: dict,
|
|
226
237
|
feature: _ArgScanFeature,
|
|
227
238
|
scan_text: str,
|
|
239
|
+
pre_scan_finding: Optional[_PreScanFinding] = None,
|
|
228
240
|
) -> HookDecision:
|
|
229
|
-
"""Shared scan + decision flow for MCP_EXECUTION and COMMAND_EXEC events.
|
|
241
|
+
"""Shared scan + decision flow for MCP_EXECUTION and COMMAND_EXEC events.
|
|
242
|
+
|
|
243
|
+
A Report-mode pre-scan finding still scans, and a secret found then takes over the response.
|
|
244
|
+
"""
|
|
230
245
|
ai_client = ctx.obj['ai_security_client']
|
|
231
246
|
|
|
232
247
|
max_bytes = get_policy_value(policy, 'secrets', 'max_bytes', default=200000)
|
|
@@ -239,6 +254,18 @@ def _handle_arg_scan(
|
|
|
239
254
|
error_message = None
|
|
240
255
|
|
|
241
256
|
try:
|
|
257
|
+
if pre_scan_finding is not None:
|
|
258
|
+
block_reason = pre_scan_finding.block_reason
|
|
259
|
+
if pre_scan_finding.mode == GuardrailsMode.BLOCK:
|
|
260
|
+
outcome = AIHookOutcome.BLOCKED
|
|
261
|
+
return HookDecision.deny(
|
|
262
|
+
feature.event_type, pre_scan_finding.deny_message, pre_scan_finding.deny_agent_message
|
|
263
|
+
)
|
|
264
|
+
outcome = AIHookOutcome.WARNED
|
|
265
|
+
|
|
266
|
+
if not feature.scan_enabled:
|
|
267
|
+
return HookDecision.allow(feature.event_type)
|
|
268
|
+
|
|
242
269
|
scan_outcome = _scan_text_for_secrets(
|
|
243
270
|
ctx,
|
|
244
271
|
clipped,
|
|
@@ -282,8 +309,43 @@ def _handle_arg_scan(
|
|
|
282
309
|
)
|
|
283
310
|
|
|
284
311
|
|
|
312
|
+
def _check_mcp_server_authorization(payload: AIHookPayload, policy: dict) -> Optional[_PreScanFinding]:
|
|
313
|
+
"""Fails open: no server name or no cached statuses lets the call through."""
|
|
314
|
+
mcp_config = get_policy_value(policy, 'mcp', default={})
|
|
315
|
+
if not get_policy_value(mcp_config, 'check_server', default=False):
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
server_name = payload.mcp_server_name
|
|
319
|
+
if not server_name:
|
|
320
|
+
logger.debug('No MCP server name in the payload; skipping the server authorization check')
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
statuses = load_mcp_server_statuses()
|
|
324
|
+
if statuses is None:
|
|
325
|
+
logger.debug('No cached MCP server statuses; skipping the server authorization check')
|
|
326
|
+
return None
|
|
327
|
+
|
|
328
|
+
status = statuses.status_of(server_name)
|
|
329
|
+
if not is_enforced(status):
|
|
330
|
+
return None
|
|
331
|
+
|
|
332
|
+
logger.debug('MCP server is not authorized, %s', {'mcp_server_name': server_name, 'status': status})
|
|
333
|
+
return _PreScanFinding(
|
|
334
|
+
block_reason=BlockReason.UNAUTHORIZED_MCP_SERVER,
|
|
335
|
+
mode=get_effective_mode(mcp_config, action_key='server_action'),
|
|
336
|
+
deny_message=(
|
|
337
|
+
f"Cycode blocked MCP server '{server_name}': it is not authorized in your organization. "
|
|
338
|
+
'Contact your admin to authorize it.'
|
|
339
|
+
),
|
|
340
|
+
deny_agent_message=(
|
|
341
|
+
f"The MCP server '{server_name}' is not authorized in this organization. "
|
|
342
|
+
'Do not retry its tools or reach it another way.'
|
|
343
|
+
),
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
|
|
285
347
|
def handle_before_mcp_execution(ctx: typer.Context, payload: AIHookPayload, policy: dict) -> HookDecision:
|
|
286
|
-
"""
|
|
348
|
+
"""Check the MCP server is authorized, then scan the tool arguments for secrets."""
|
|
287
349
|
tool = payload.mcp_tool_name or 'unknown'
|
|
288
350
|
args = payload.mcp_arguments or {}
|
|
289
351
|
args_text = args if isinstance(args, str) else json.dumps(args)
|
|
@@ -297,8 +359,10 @@ def handle_before_mcp_execution(ctx: typer.Context, payload: AIHookPayload, poli
|
|
|
297
359
|
deny_agent_message='Do not pass secrets to tools. Use secret references (name/id) instead.',
|
|
298
360
|
ask_message=lambda v: f'Allow MCP tool call "{tool}"? {v}',
|
|
299
361
|
ask_agent_message='Possible secrets detected in tool arguments; proceed with caution.',
|
|
362
|
+
scan_enabled=get_policy_value(policy, 'mcp', 'scan_args', default=True),
|
|
300
363
|
),
|
|
301
364
|
scan_text=args_text,
|
|
365
|
+
pre_scan_finding=_check_mcp_server_authorization(payload, policy),
|
|
302
366
|
)
|
|
303
367
|
|
|
304
368
|
|