cycode 3.12.1.dev1__tar.gz → 3.12.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cycode-3.12.1.dev1 → cycode-3.12.2}/PKG-INFO +15 -1
- {cycode-3.12.1.dev1 → cycode-3.12.2}/README.md +14 -0
- cycode-3.12.2/cycode/__init__.py +1 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report/sbom/path/path_command.py +4 -1
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/sca_options.py +11 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/code_scanner.py +57 -5
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/commit_range_scanner.py +1 -1
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/scan_command.py +8 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/exceptions/custom_exceptions.py +20 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/exceptions/handle_scan_errors.py +6 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/path_documents.py +6 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/sca_file_collector.py +5 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/cycode_client_base.py +85 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/scan_client.py +42 -14
- {cycode-3.12.1.dev1 → cycode-3.12.2}/pyproject.toml +1 -1
- cycode-3.12.1.dev1/cycode/__init__.py +0 -1
- {cycode-3.12.1.dev1 → cycode-3.12.2}/LICENCE +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/__main__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/app.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/activation_manager.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/command_utils.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/consts.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/ensure_auth_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/hooks_manager.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/install_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/scan/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/scan/consts.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/scan/handlers.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/scan/payload.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/scan/policy.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/scan/response_builders.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/scan/scan_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/scan/types.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/scan/utils.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/status_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_guardrails/uninstall_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_remediation/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_remediation/ai_remediation_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_remediation/apply_fix.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ai_remediation/print_remediation.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/auth/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/auth/auth_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/auth/auth_common.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/auth/auth_manager.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/auth/models.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/configure/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/configure/configure_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/configure/consts.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/configure/messages.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/configure/prompts.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ignore/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/ignore/ignore_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/mcp/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/mcp/mcp_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report/report_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report/sbom/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report/sbom/common.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report/sbom/path/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report/sbom/repository_url/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report/sbom/repository_url/repository_url_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report/sbom/sbom_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report/sbom/sbom_report_file.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report_import/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report_import/report_import_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report_import/sbom/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/report_import/sbom/sbom_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/aggregation_report.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/commit_history/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/commit_history/commit_history_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/detection_excluder.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/path/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/path/path_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/pre_commit/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/pre_commit/pre_commit_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/pre_push/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/pre_push/pre_push_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/pre_receive/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/pre_receive/pre_receive_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/remote_url_resolver.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/repository/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/repository/repository_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/scan_ci/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/scan_ci/ci_integrations.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/scan_ci/scan_ci_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/scan_parameters.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/scan/scan_result.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/status/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/status/get_cli_status.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/status/models.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/status/status_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/apps/status/version_command.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/cli_types.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/config.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/console.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/consts.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/exceptions/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/exceptions/handle_ai_remediation_errors.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/exceptions/handle_auth_errors.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/exceptions/handle_errors.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/exceptions/handle_report_sbom_errors.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/commit_range_documents.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/documents_walk_ignore.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/file_excluder.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/iac/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/models/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/repository_documents.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/base_restore_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/go/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/go/restore_go_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/maven/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/npm/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/npm/restore_deno_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/npm/restore_pnpm_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/npm/restore_yarn_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/nuget/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/php/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/php/restore_composer_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/python/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/python/restore_pipenv_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/python/restore_poetry_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/ruby/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/sbt/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/walk_ignore.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/files_collector/zip_documents.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/logger.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/main.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/models.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/console_printer.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/json_printer.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/printer_base.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/rich_printer.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/tables/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/tables/table.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/tables/table_models.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/tables/table_printer.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/tables/table_printer_base.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/text_printer.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/utils/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/utils/code_snippet_syntax.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/utils/detection_data.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/utils/detection_ordering/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/utils/detection_ordering/common_ordering.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/utils/detection_ordering/sca_ordering.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/printers/utils/rich_helpers.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/user_settings/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/user_settings/base_file_manager.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/user_settings/config_file_manager.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/user_settings/configuration_manager.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/user_settings/credentials_manager.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/user_settings/jwt_creator.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/binary_utils.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/enum_utils.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/get_api_client.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/git_proxy.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/ignore_utils.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/jwt_utils.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/path_utils.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/progress_bar.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/scan_batch.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/scan_utils.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/shell_executor.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/string_utils.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/task_timer.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/url_utils.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/version_checker.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cli/utils/yaml_utils.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/config.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/__init__.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/ai_security_manager_client.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/ai_security_manager_service_config.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/auth_client.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/base_token_auth_client.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/cli_activation_client.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/client_creator.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/config.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/config_dev.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/cycode_client.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/cycode_dev_based_client.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/cycode_oidc_based_client.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/cycode_token_based_client.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/headers.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/import_sbom_client.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/logger.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/models.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/report_client.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/cyclient/scan_config_base.py +0 -0
- {cycode-3.12.1.dev1 → cycode-3.12.2}/cycode/logger.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cycode
|
|
3
|
-
Version: 3.12.
|
|
3
|
+
Version: 3.12.2
|
|
4
4
|
Summary: Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENCE
|
|
@@ -71,6 +71,7 @@ This guide walks you through both installation and usage.
|
|
|
71
71
|
4. [Package Vulnerabilities](#package-vulnerabilities-option)
|
|
72
72
|
5. [License Compliance](#license-compliance-option)
|
|
73
73
|
6. [Lock Restore](#lock-restore-option)
|
|
74
|
+
7. [Stop on Error](#stop-on-error-option)
|
|
74
75
|
2. [Repository Scan](#repository-scan)
|
|
75
76
|
1. [Branch Option](#branch-option)
|
|
76
77
|
3. [Path Scan](#path-scan)
|
|
@@ -661,6 +662,7 @@ The Cycode CLI application offers several types of scans so that you can choose
|
|
|
661
662
|
| `--monitor` | When specified, the scan results will be recorded in Cycode. |
|
|
662
663
|
| `--cycode-report` | Display a link to the scan report in the Cycode platform in the console output. |
|
|
663
664
|
| `--no-restore` | When specified, Cycode will not run the restore command. This will scan direct dependencies ONLY! |
|
|
665
|
+
| `--stop-on-error` | Abort the scan if any file collection or dependency restore failure occurs, instead of skipping the failed file and continuing. |
|
|
664
666
|
| `--gradle-all-sub-projects` | Run gradle restore command for all sub projects. This should be run from |
|
|
665
667
|
| `--maven-settings-file` | For Maven only, allows using a custom [settings.xml](https://maven.apache.org/settings.html) file when scanning for dependencies |
|
|
666
668
|
| `--help` | Show options for given command. |
|
|
@@ -767,6 +769,18 @@ If a lockfile already exists alongside the manifest, Cycode reads it directly wi
|
|
|
767
769
|
addSbtPlugin("software.purpledragon" % "sbt-dependency-lock" % "1.5.1")
|
|
768
770
|
```
|
|
769
771
|
|
|
772
|
+
#### Stop on Error Option
|
|
773
|
+
|
|
774
|
+
By default, Cycode continues scanning even if a file cannot be read (e.g. due to a permission error) or a dependency lockfile cannot be generated during an SCA scan. The failed item is skipped with a warning and the scan proceeds with the remaining files.
|
|
775
|
+
|
|
776
|
+
Use `--stop-on-error` to change this behaviour: the scan aborts immediately on the first such failure and reports the error.
|
|
777
|
+
|
|
778
|
+
```bash
|
|
779
|
+
cycode scan -t sca --stop-on-error path ~/home/git/codebase
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
This is useful in CI pipelines where a silent failure would produce an incomplete scan result. When `--stop-on-error` is triggered you can either fix the underlying issue or, for SCA restore failures specifically, add `--no-restore` to skip lockfile generation and scan direct dependencies only.
|
|
783
|
+
|
|
770
784
|
### Repository Scan
|
|
771
785
|
|
|
772
786
|
A repository scan examines an entire local repository for any exposed secrets or insecure misconfigurations. This more holistic scan type looks at everything: the current state of your repository and its commit history. It will look not only for secrets that are currently exposed within the repository but previously deleted secrets as well.
|
|
@@ -30,6 +30,7 @@ This guide walks you through both installation and usage.
|
|
|
30
30
|
4. [Package Vulnerabilities](#package-vulnerabilities-option)
|
|
31
31
|
5. [License Compliance](#license-compliance-option)
|
|
32
32
|
6. [Lock Restore](#lock-restore-option)
|
|
33
|
+
7. [Stop on Error](#stop-on-error-option)
|
|
33
34
|
2. [Repository Scan](#repository-scan)
|
|
34
35
|
1. [Branch Option](#branch-option)
|
|
35
36
|
3. [Path Scan](#path-scan)
|
|
@@ -620,6 +621,7 @@ The Cycode CLI application offers several types of scans so that you can choose
|
|
|
620
621
|
| `--monitor` | When specified, the scan results will be recorded in Cycode. |
|
|
621
622
|
| `--cycode-report` | Display a link to the scan report in the Cycode platform in the console output. |
|
|
622
623
|
| `--no-restore` | When specified, Cycode will not run the restore command. This will scan direct dependencies ONLY! |
|
|
624
|
+
| `--stop-on-error` | Abort the scan if any file collection or dependency restore failure occurs, instead of skipping the failed file and continuing. |
|
|
623
625
|
| `--gradle-all-sub-projects` | Run gradle restore command for all sub projects. This should be run from |
|
|
624
626
|
| `--maven-settings-file` | For Maven only, allows using a custom [settings.xml](https://maven.apache.org/settings.html) file when scanning for dependencies |
|
|
625
627
|
| `--help` | Show options for given command. |
|
|
@@ -726,6 +728,18 @@ If a lockfile already exists alongside the manifest, Cycode reads it directly wi
|
|
|
726
728
|
addSbtPlugin("software.purpledragon" % "sbt-dependency-lock" % "1.5.1")
|
|
727
729
|
```
|
|
728
730
|
|
|
731
|
+
#### Stop on Error Option
|
|
732
|
+
|
|
733
|
+
By default, Cycode continues scanning even if a file cannot be read (e.g. due to a permission error) or a dependency lockfile cannot be generated during an SCA scan. The failed item is skipped with a warning and the scan proceeds with the remaining files.
|
|
734
|
+
|
|
735
|
+
Use `--stop-on-error` to change this behaviour: the scan aborts immediately on the first such failure and reports the error.
|
|
736
|
+
|
|
737
|
+
```bash
|
|
738
|
+
cycode scan -t sca --stop-on-error path ~/home/git/codebase
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
This is useful in CI pipelines where a silent failure would produce an incomplete scan result. When `--stop-on-error` is triggered you can either fix the underlying issue or, for SCA restore failures specifically, add `--no-restore` to skip lockfile generation and scan direct dependencies only.
|
|
742
|
+
|
|
729
743
|
### Repository Scan
|
|
730
744
|
|
|
731
745
|
A repository scan examines an entire local repository for any exposed secrets or insecure misconfigurations. This more holistic scan type looks at everything: the current state of your repository and its commit history. It will look not only for secrets that are currently exposed within the repository but previously deleted secrets as well.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '3.12.2' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
|
|
@@ -10,6 +10,7 @@ from cycode.cli.apps.sca_options import (
|
|
|
10
10
|
GradleAllSubProjectsOption,
|
|
11
11
|
MavenSettingsFileOption,
|
|
12
12
|
NoRestoreOption,
|
|
13
|
+
StopOnErrorOption,
|
|
13
14
|
apply_sca_restore_options_to_context,
|
|
14
15
|
)
|
|
15
16
|
from cycode.cli.exceptions.handle_report_sbom_errors import handle_report_exception
|
|
@@ -30,8 +31,9 @@ def path_command(
|
|
|
30
31
|
no_restore: NoRestoreOption = False,
|
|
31
32
|
gradle_all_sub_projects: GradleAllSubProjectsOption = False,
|
|
32
33
|
maven_settings_file: MavenSettingsFileOption = None,
|
|
34
|
+
stop_on_error: StopOnErrorOption = False,
|
|
33
35
|
) -> None:
|
|
34
|
-
apply_sca_restore_options_to_context(ctx, no_restore, gradle_all_sub_projects, maven_settings_file)
|
|
36
|
+
apply_sca_restore_options_to_context(ctx, no_restore, gradle_all_sub_projects, maven_settings_file, stop_on_error)
|
|
35
37
|
|
|
36
38
|
client = get_report_cycode_client(ctx)
|
|
37
39
|
report_parameters = ctx.obj['report_parameters']
|
|
@@ -51,6 +53,7 @@ def path_command(
|
|
|
51
53
|
consts.SCA_SCAN_TYPE,
|
|
52
54
|
(str(path),),
|
|
53
55
|
is_cycodeignore_allowed=is_cycodeignore_allowed_by_scan_config(ctx),
|
|
56
|
+
stop_on_error=stop_on_error,
|
|
54
57
|
)
|
|
55
58
|
# TODO(MarshalX): combine perform_pre_scan_documents_actions with get_relevant_document.
|
|
56
59
|
# unhardcode usage of context in perform_pre_scan_documents_actions
|
|
@@ -35,13 +35,24 @@ MavenSettingsFileOption = Annotated[
|
|
|
35
35
|
),
|
|
36
36
|
]
|
|
37
37
|
|
|
38
|
+
StopOnErrorOption = Annotated[
|
|
39
|
+
bool,
|
|
40
|
+
typer.Option(
|
|
41
|
+
'--stop-on-error',
|
|
42
|
+
help='When specified, stops the process if any file collection or restore failure occurs.',
|
|
43
|
+
rich_help_panel=_SCA_RICH_HELP_PANEL,
|
|
44
|
+
),
|
|
45
|
+
]
|
|
46
|
+
|
|
38
47
|
|
|
39
48
|
def apply_sca_restore_options_to_context(
|
|
40
49
|
ctx: typer.Context,
|
|
41
50
|
no_restore: bool,
|
|
42
51
|
gradle_all_sub_projects: bool,
|
|
43
52
|
maven_settings_file: Optional[Path],
|
|
53
|
+
stop_on_error: bool = False,
|
|
44
54
|
) -> None:
|
|
45
55
|
ctx.obj['no_restore'] = no_restore
|
|
46
56
|
ctx.obj['gradle_all_sub_projects'] = gradle_all_sub_projects
|
|
47
57
|
ctx.obj['maven_settings_file'] = maven_settings_file
|
|
58
|
+
ctx.obj['stop_on_error'] = stop_on_error
|
|
@@ -47,6 +47,36 @@ start_scan_time = time.time()
|
|
|
47
47
|
logger = get_logger('Code Scanner')
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
class _UploadProgressAggregator:
|
|
51
|
+
"""Aggregates upload progress across parallel batch uploads for display in the progress bar."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, progress_bar: 'BaseProgressBar') -> None:
|
|
54
|
+
self._progress_bar = progress_bar
|
|
55
|
+
self._slots: list[list[int]] = []
|
|
56
|
+
|
|
57
|
+
def create_callback(self) -> Callable[[int, int], None]:
|
|
58
|
+
"""Create a progress callback for one batch upload. Each batch gets its own slot."""
|
|
59
|
+
slot = [0, 0]
|
|
60
|
+
self._slots.append(slot)
|
|
61
|
+
|
|
62
|
+
def on_upload_progress(bytes_read: int, total_bytes: int) -> None:
|
|
63
|
+
slot[0] = bytes_read
|
|
64
|
+
slot[1] = total_bytes
|
|
65
|
+
|
|
66
|
+
# Sum across all batch slots to show combined progress
|
|
67
|
+
total_read = sum(s[0] for s in self._slots)
|
|
68
|
+
total_size = sum(s[1] for s in self._slots)
|
|
69
|
+
|
|
70
|
+
if total_read >= total_size:
|
|
71
|
+
self._progress_bar.update_right_side_label(None)
|
|
72
|
+
else:
|
|
73
|
+
mb_read = total_read / (1024 * 1024)
|
|
74
|
+
mb_total = total_size / (1024 * 1024)
|
|
75
|
+
self._progress_bar.update_right_side_label(f'Uploading {mb_read:.1f} / {mb_total:.1f} MB')
|
|
76
|
+
|
|
77
|
+
return on_upload_progress
|
|
78
|
+
|
|
79
|
+
|
|
50
80
|
def scan_disk_files(ctx: typer.Context, paths: tuple[str, ...]) -> None:
|
|
51
81
|
scan_type = ctx.obj['scan_type']
|
|
52
82
|
progress_bar = ctx.obj['progress_bar']
|
|
@@ -58,6 +88,7 @@ def scan_disk_files(ctx: typer.Context, paths: tuple[str, ...]) -> None:
|
|
|
58
88
|
scan_type,
|
|
59
89
|
paths,
|
|
60
90
|
is_cycodeignore_allowed=is_cycodeignore_allowed_by_scan_config(ctx),
|
|
91
|
+
stop_on_error=ctx.obj.get('stop_on_error', False),
|
|
61
92
|
)
|
|
62
93
|
|
|
63
94
|
# Add entrypoint.cycode file at root path to mark the scan root (only for single path that is a directory)
|
|
@@ -120,6 +151,9 @@ def _get_scan_documents_thread_func(
|
|
|
120
151
|
severity_threshold = ctx.obj['severity_threshold']
|
|
121
152
|
sync_option = ctx.obj['sync']
|
|
122
153
|
command_scan_type = ctx.info_name
|
|
154
|
+
progress_bar = ctx.obj['progress_bar']
|
|
155
|
+
|
|
156
|
+
aggregator = _UploadProgressAggregator(progress_bar)
|
|
123
157
|
|
|
124
158
|
def _scan_batch_thread_func(batch: list[Document]) -> tuple[str, CliError, LocalScanResult]:
|
|
125
159
|
local_scan_result = error = error_message = None
|
|
@@ -142,6 +176,7 @@ def _get_scan_documents_thread_func(
|
|
|
142
176
|
is_commit_range,
|
|
143
177
|
scan_parameters,
|
|
144
178
|
should_use_sync_flow,
|
|
179
|
+
on_upload_progress=aggregator.create_callback(),
|
|
145
180
|
)
|
|
146
181
|
|
|
147
182
|
enrich_scan_result_with_data_from_detection_rules(cycode_client, scan_result)
|
|
@@ -267,15 +302,18 @@ def _perform_scan_v4_async(
|
|
|
267
302
|
scan_parameters: dict,
|
|
268
303
|
is_git_diff: bool,
|
|
269
304
|
is_commit_range: bool,
|
|
305
|
+
on_upload_progress: Optional[Callable] = None,
|
|
270
306
|
) -> ZippedFileScanResult:
|
|
271
307
|
upload_link = cycode_client.get_upload_link(scan_type)
|
|
272
308
|
logger.debug('Got upload link, %s', {'upload_id': upload_link.upload_id})
|
|
273
309
|
|
|
274
|
-
cycode_client.upload_to_presigned_post(
|
|
310
|
+
cycode_client.upload_to_presigned_post(
|
|
311
|
+
upload_link.url, upload_link.presigned_post_fields, zipped_documents, on_upload_progress
|
|
312
|
+
)
|
|
275
313
|
logger.debug('Uploaded zip to presigned URL')
|
|
276
314
|
|
|
277
315
|
scan_async_result = cycode_client.scan_repository_from_upload_id(
|
|
278
|
-
scan_type, upload_link.upload_id, scan_parameters, is_git_diff, is_commit_range
|
|
316
|
+
scan_type, upload_link.upload_id, zipped_documents, scan_parameters, is_git_diff, is_commit_range
|
|
279
317
|
)
|
|
280
318
|
logger.debug(
|
|
281
319
|
'Presigned upload scan request triggered, %s',
|
|
@@ -291,9 +329,14 @@ def _perform_scan_async(
|
|
|
291
329
|
scan_type: str,
|
|
292
330
|
scan_parameters: dict,
|
|
293
331
|
is_commit_range: bool,
|
|
332
|
+
on_upload_progress: Optional[Callable] = None,
|
|
294
333
|
) -> ZippedFileScanResult:
|
|
295
334
|
scan_async_result = cycode_client.zipped_file_scan_async(
|
|
296
|
-
zipped_documents,
|
|
335
|
+
zipped_documents,
|
|
336
|
+
scan_type,
|
|
337
|
+
scan_parameters,
|
|
338
|
+
is_commit_range=is_commit_range,
|
|
339
|
+
on_upload_progress=on_upload_progress,
|
|
297
340
|
)
|
|
298
341
|
logger.debug('Async scan request has been triggered successfully, %s', {'scan_id': scan_async_result.scan_id})
|
|
299
342
|
|
|
@@ -325,6 +368,7 @@ def _perform_scan(
|
|
|
325
368
|
is_commit_range: bool,
|
|
326
369
|
scan_parameters: dict,
|
|
327
370
|
should_use_sync_flow: bool = False,
|
|
371
|
+
on_upload_progress: Optional[Callable] = None,
|
|
328
372
|
) -> ZippedFileScanResult:
|
|
329
373
|
if should_use_sync_flow:
|
|
330
374
|
# it does not support commit range scans; should_use_sync_flow handles it
|
|
@@ -333,12 +377,20 @@ def _perform_scan(
|
|
|
333
377
|
if should_use_presigned_upload(scan_type):
|
|
334
378
|
try:
|
|
335
379
|
return _perform_scan_v4_async(
|
|
336
|
-
cycode_client,
|
|
380
|
+
cycode_client,
|
|
381
|
+
zipped_documents,
|
|
382
|
+
scan_type,
|
|
383
|
+
scan_parameters,
|
|
384
|
+
is_git_diff,
|
|
385
|
+
is_commit_range,
|
|
386
|
+
on_upload_progress,
|
|
337
387
|
)
|
|
338
388
|
except requests.exceptions.RequestException:
|
|
339
389
|
logger.warning('Direct upload to object storage failed. Falling back to upload via Cycode API. ')
|
|
340
390
|
|
|
341
|
-
return _perform_scan_async(
|
|
391
|
+
return _perform_scan_async(
|
|
392
|
+
cycode_client, zipped_documents, scan_type, scan_parameters, is_commit_range, on_upload_progress
|
|
393
|
+
)
|
|
342
394
|
|
|
343
395
|
|
|
344
396
|
def poll_scan_results(
|
|
@@ -113,7 +113,7 @@ def _perform_commit_range_scan_v4_async(
|
|
|
113
113
|
logger.debug('Uploaded to-commit zip')
|
|
114
114
|
|
|
115
115
|
scan_async_result = cycode_client.commit_range_scan_from_upload_ids(
|
|
116
|
-
scan_type, from_upload_link.upload_id, to_upload_link.upload_id, scan_parameters
|
|
116
|
+
scan_type, from_upload_link.upload_id, to_upload_link.upload_id, from_commit_zipped_documents, scan_parameters
|
|
117
117
|
)
|
|
118
118
|
logger.debug('V4 commit range scan request triggered, %s', {'scan_id': scan_async_result.scan_id})
|
|
119
119
|
|
|
@@ -41,6 +41,13 @@ def scan_command(
|
|
|
41
41
|
soft_fail: Annotated[
|
|
42
42
|
bool, typer.Option('--soft-fail', help='Run the scan without failing; always return a non-error status code.')
|
|
43
43
|
] = False,
|
|
44
|
+
stop_on_error: Annotated[
|
|
45
|
+
bool,
|
|
46
|
+
typer.Option(
|
|
47
|
+
'--stop-on-error',
|
|
48
|
+
help='When specified, stops the scan if any file collection or restore failure occurs.',
|
|
49
|
+
),
|
|
50
|
+
] = False,
|
|
44
51
|
severity_threshold: Annotated[
|
|
45
52
|
SeverityOption,
|
|
46
53
|
typer.Option(
|
|
@@ -131,6 +138,7 @@ def scan_command(
|
|
|
131
138
|
|
|
132
139
|
ctx.obj['show_secret'] = show_secret
|
|
133
140
|
ctx.obj['soft_fail'] = soft_fail
|
|
141
|
+
ctx.obj['stop_on_error'] = stop_on_error
|
|
134
142
|
ctx.obj['scan_type'] = scan_type
|
|
135
143
|
ctx.obj['sync'] = sync
|
|
136
144
|
ctx.obj['severity_threshold'] = severity_threshold
|
|
@@ -55,6 +55,11 @@ class HttpUnauthorizedError(RequestHttpError):
|
|
|
55
55
|
return f'HTTP unauthorized error occurred during the request. Message: {self.error_message}'
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
class SlowUploadConnectionError(CycodeError):
|
|
59
|
+
def __str__(self) -> str:
|
|
60
|
+
return 'Upload was interrupted mid-transfer, indicating a slow or unstable network connection.'
|
|
61
|
+
|
|
62
|
+
|
|
58
63
|
class ZipTooLargeError(CycodeError):
|
|
59
64
|
def __init__(self, size_limit: int) -> None:
|
|
60
65
|
self.size_limit = size_limit
|
|
@@ -64,6 +69,15 @@ class ZipTooLargeError(CycodeError):
|
|
|
64
69
|
return f'The size of zip to scan is too large, size limit: {self.size_limit}'
|
|
65
70
|
|
|
66
71
|
|
|
72
|
+
class FileCollectionError(CycodeError):
|
|
73
|
+
def __init__(self, error_message: str) -> None:
|
|
74
|
+
self.error_message = error_message
|
|
75
|
+
super().__init__(self.error_message)
|
|
76
|
+
|
|
77
|
+
def __str__(self) -> str:
|
|
78
|
+
return self.error_message
|
|
79
|
+
|
|
80
|
+
|
|
67
81
|
class AuthProcessError(CycodeError):
|
|
68
82
|
def __init__(self, error_message: str) -> None:
|
|
69
83
|
self.error_message = error_message
|
|
@@ -93,6 +107,12 @@ KNOWN_USER_FRIENDLY_REQUEST_ERRORS: CliErrors = {
|
|
|
93
107
|
code='timeout_error',
|
|
94
108
|
message='The request timed out. Please try again by executing the `cycode scan` command',
|
|
95
109
|
),
|
|
110
|
+
SlowUploadConnectionError: CliError(
|
|
111
|
+
soft_fail=True,
|
|
112
|
+
code='slow_upload_error',
|
|
113
|
+
message='The scan upload was interrupted. This is likely due to a slow or unstable network connection. '
|
|
114
|
+
'Please try again by executing the `cycode scan` command',
|
|
115
|
+
),
|
|
96
116
|
HttpUnauthorizedError: CliError(
|
|
97
117
|
soft_fail=True,
|
|
98
118
|
code='auth_error',
|
|
@@ -26,6 +26,12 @@ def handle_scan_exception(ctx: typer.Context, err: Exception, *, return_exceptio
|
|
|
26
26
|
'Please try ignoring irrelevant paths using the `cycode ignore --by-path` command '
|
|
27
27
|
'and execute the scan again',
|
|
28
28
|
),
|
|
29
|
+
custom_exceptions.FileCollectionError: CliError(
|
|
30
|
+
soft_fail=False,
|
|
31
|
+
code='file_collection_error',
|
|
32
|
+
message='File collection failed. '
|
|
33
|
+
'Use --no-restore to skip dependency restoration, or fix the underlying issue.',
|
|
34
|
+
),
|
|
29
35
|
custom_exceptions.TfplanKeyError: CliError(
|
|
30
36
|
soft_fail=True,
|
|
31
37
|
code='key_error',
|
|
@@ -2,6 +2,7 @@ import os
|
|
|
2
2
|
from collections.abc import Generator
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
+
from cycode.cli.exceptions.custom_exceptions import FileCollectionError
|
|
5
6
|
from cycode.cli.files_collector.file_excluder import excluder
|
|
6
7
|
from cycode.cli.files_collector.iac.tf_content_generator import (
|
|
7
8
|
generate_tf_content_from_tfplan,
|
|
@@ -109,6 +110,7 @@ def get_relevant_documents(
|
|
|
109
110
|
*,
|
|
110
111
|
is_git_diff: bool = False,
|
|
111
112
|
is_cycodeignore_allowed: bool = True,
|
|
113
|
+
stop_on_error: bool = False,
|
|
112
114
|
) -> list[Document]:
|
|
113
115
|
relevant_files = _get_relevant_files(
|
|
114
116
|
progress_bar, progress_bar_section, scan_type, paths, is_cycodeignore_allowed=is_cycodeignore_allowed
|
|
@@ -119,6 +121,10 @@ def get_relevant_documents(
|
|
|
119
121
|
progress_bar.update(progress_bar_section)
|
|
120
122
|
|
|
121
123
|
content = get_file_content(file)
|
|
124
|
+
if content is None:
|
|
125
|
+
if stop_on_error:
|
|
126
|
+
raise FileCollectionError(f'Failed to read file: {file}')
|
|
127
|
+
continue
|
|
122
128
|
if not content:
|
|
123
129
|
continue
|
|
124
130
|
|
|
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional
|
|
|
4
4
|
import typer
|
|
5
5
|
|
|
6
6
|
from cycode.cli import consts
|
|
7
|
+
from cycode.cli.exceptions.custom_exceptions import FileCollectionError
|
|
7
8
|
from cycode.cli.files_collector.repository_documents import get_file_content_from_commit_path
|
|
8
9
|
from cycode.cli.files_collector.sca.base_restore_dependencies import BaseRestoreDependencies
|
|
9
10
|
from cycode.cli.files_collector.sca.go.restore_go_dependencies import RestoreGoDependencies
|
|
@@ -116,6 +117,10 @@ def _try_restore_dependencies(
|
|
|
116
117
|
'Error occurred while trying to generate dependencies tree, %s',
|
|
117
118
|
{'filename': document.path, 'handler': type(restore_dependencies).__name__},
|
|
118
119
|
)
|
|
120
|
+
if ctx.obj.get('stop_on_error', False):
|
|
121
|
+
raise FileCollectionError(
|
|
122
|
+
f'Failed to generate dependencies tree for {document.path} using {type(restore_dependencies).__name__}'
|
|
123
|
+
)
|
|
119
124
|
return None
|
|
120
125
|
|
|
121
126
|
if restore_dependencies_document.content is None:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import platform
|
|
3
3
|
import ssl
|
|
4
|
+
from io import BytesIO
|
|
4
5
|
from typing import TYPE_CHECKING, Callable, ClassVar, Optional
|
|
5
6
|
|
|
6
7
|
import requests
|
|
@@ -15,6 +16,7 @@ from cycode.cli.exceptions.custom_exceptions import (
|
|
|
15
16
|
RequestHttpError,
|
|
16
17
|
RequestSslError,
|
|
17
18
|
RequestTimeoutError,
|
|
19
|
+
SlowUploadConnectionError,
|
|
18
20
|
)
|
|
19
21
|
from cycode.cyclient import config
|
|
20
22
|
from cycode.cyclient.headers import get_cli_user_agent, get_correlation_id
|
|
@@ -90,6 +92,23 @@ def _should_retry_exception(exception: BaseException) -> bool:
|
|
|
90
92
|
return is_request_error or is_server_error
|
|
91
93
|
|
|
92
94
|
|
|
95
|
+
class UploadProgressTracker:
|
|
96
|
+
"""File-like wrapper that tracks bytes read during upload and fires a progress callback."""
|
|
97
|
+
|
|
98
|
+
def __init__(self, data: bytes, callback: Optional[Callable[[int, int], None]]) -> None:
|
|
99
|
+
self._io = BytesIO(data)
|
|
100
|
+
self._callback = callback
|
|
101
|
+
self.bytes_read = 0
|
|
102
|
+
self.len = len(data)
|
|
103
|
+
|
|
104
|
+
def read(self, size: int = -1) -> bytes:
|
|
105
|
+
chunk = self._io.read(size)
|
|
106
|
+
self.bytes_read += len(chunk)
|
|
107
|
+
if self._callback and chunk:
|
|
108
|
+
self._callback(self.bytes_read, self.len)
|
|
109
|
+
return chunk
|
|
110
|
+
|
|
111
|
+
|
|
93
112
|
class CycodeClientBase:
|
|
94
113
|
MANDATORY_HEADERS: ClassVar[dict[str, str]] = {
|
|
95
114
|
'User-Agent': get_cli_user_agent(),
|
|
@@ -117,6 +136,72 @@ class CycodeClientBase:
|
|
|
117
136
|
def get(self, url_path: str, headers: Optional[dict] = None, **kwargs) -> Response:
|
|
118
137
|
return self._execute(method='get', endpoint=url_path, headers=headers, **kwargs)
|
|
119
138
|
|
|
139
|
+
def post_multipart(
|
|
140
|
+
self,
|
|
141
|
+
url_path: str,
|
|
142
|
+
form_fields: dict,
|
|
143
|
+
files: dict,
|
|
144
|
+
on_upload_progress: Optional[Callable[[int, int], None]] = None,
|
|
145
|
+
hide_response_content_log: bool = False,
|
|
146
|
+
) -> Response:
|
|
147
|
+
"""POST a multipart form body with optional upload progress tracking and retry."""
|
|
148
|
+
url = self.build_full_url(self.api_url, url_path)
|
|
149
|
+
logger.debug('Executing request, %s', {'method': 'POST', 'url': url})
|
|
150
|
+
|
|
151
|
+
# Encode the multipart body once up front so we can reuse the same bytes across retries.
|
|
152
|
+
# A dummy URL is used because requests.Request requires one, but only the encoded body matters here.
|
|
153
|
+
prepared = requests.Request('POST', 'https://dummy', data=form_fields, files=files).prepare()
|
|
154
|
+
|
|
155
|
+
return self._send_multipart(
|
|
156
|
+
url=url,
|
|
157
|
+
body=prepared.body,
|
|
158
|
+
content_type=prepared.headers['Content-Type'],
|
|
159
|
+
on_upload_progress=on_upload_progress,
|
|
160
|
+
hide_response_content_log=hide_response_content_log,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
@retry(
|
|
164
|
+
retry=retry_if_exception(_should_retry_exception),
|
|
165
|
+
stop=_RETRY_STOP_STRATEGY,
|
|
166
|
+
wait=_RETRY_WAIT_STRATEGY,
|
|
167
|
+
reraise=True,
|
|
168
|
+
before_sleep=_retry_before_sleep,
|
|
169
|
+
)
|
|
170
|
+
def _send_multipart(
|
|
171
|
+
self,
|
|
172
|
+
url: str,
|
|
173
|
+
body: bytes,
|
|
174
|
+
content_type: str,
|
|
175
|
+
on_upload_progress: Optional[Callable[[int, int], None]],
|
|
176
|
+
hide_response_content_log: bool,
|
|
177
|
+
) -> Response:
|
|
178
|
+
# Wrap the body in a fresh tracker each attempt so bytes_read starts from zero.
|
|
179
|
+
tracker = UploadProgressTracker(body, on_upload_progress)
|
|
180
|
+
headers = self.get_request_headers({'Content-Type': content_type})
|
|
181
|
+
try:
|
|
182
|
+
response = _get_request_function()(
|
|
183
|
+
method='post', url=url, data=tracker, headers=headers, timeout=self.timeout
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
content = 'HIDDEN' if hide_response_content_log else response.text
|
|
187
|
+
logger.debug(
|
|
188
|
+
'Receiving response, %s',
|
|
189
|
+
{'status_code': response.status_code, 'url': url, 'content': content},
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
response.raise_for_status()
|
|
193
|
+
return response
|
|
194
|
+
except (exceptions.ChunkedEncodingError, exceptions.ConnectionError) as e:
|
|
195
|
+
# A connection drop before the full body was sent indicates a slow/unstable network.
|
|
196
|
+
if tracker.bytes_read < tracker.len:
|
|
197
|
+
raise SlowUploadConnectionError from e
|
|
198
|
+
# Full body was sent — map to our types so _should_retry_exception handles retry logic.
|
|
199
|
+
if isinstance(e, exceptions.ConnectionError):
|
|
200
|
+
raise RequestConnectionError from e
|
|
201
|
+
raise
|
|
202
|
+
except Exception as e:
|
|
203
|
+
self._handle_exception(e)
|
|
204
|
+
|
|
120
205
|
@retry(
|
|
121
206
|
retry=retry_if_exception(_should_retry_exception),
|
|
122
207
|
stop=_RETRY_STOP_STRATEGY,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from copy import deepcopy
|
|
3
|
-
from typing import TYPE_CHECKING, Optional, Union
|
|
3
|
+
from typing import TYPE_CHECKING, Callable, Optional, Union
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
6
|
import requests
|
|
@@ -8,10 +8,14 @@ from requests import Response
|
|
|
8
8
|
|
|
9
9
|
from cycode.cli import consts
|
|
10
10
|
from cycode.cli.config import configuration_manager
|
|
11
|
-
from cycode.cli.exceptions.custom_exceptions import
|
|
11
|
+
from cycode.cli.exceptions.custom_exceptions import (
|
|
12
|
+
CycodeError,
|
|
13
|
+
RequestHttpError,
|
|
14
|
+
SlowUploadConnectionError,
|
|
15
|
+
)
|
|
12
16
|
from cycode.cli.files_collector.models.in_memory_zip import InMemoryZip
|
|
13
17
|
from cycode.cyclient import models
|
|
14
|
-
from cycode.cyclient.cycode_client_base import CycodeClientBase
|
|
18
|
+
from cycode.cyclient.cycode_client_base import CycodeClientBase, UploadProgressTracker
|
|
15
19
|
from cycode.cyclient.logger import logger
|
|
16
20
|
|
|
17
21
|
if TYPE_CHECKING:
|
|
@@ -114,18 +118,18 @@ class ScanClient:
|
|
|
114
118
|
scan_parameters: dict,
|
|
115
119
|
is_git_diff: bool = False,
|
|
116
120
|
is_commit_range: bool = False,
|
|
121
|
+
on_upload_progress: Optional[Callable[[int, int], None]] = None,
|
|
117
122
|
) -> models.ScanInitializationResponse:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
response = self.scan_cycode_client.post(
|
|
123
|
+
response = self.scan_cycode_client.post_multipart(
|
|
121
124
|
url_path=self.get_zipped_file_scan_async_url_path(scan_type),
|
|
122
|
-
|
|
125
|
+
form_fields={
|
|
123
126
|
'is_git_diff': is_git_diff,
|
|
124
127
|
'scan_parameters': json.dumps(scan_parameters),
|
|
125
128
|
'is_commit_range': is_commit_range,
|
|
126
129
|
'compression_manifest': self._create_compression_manifest_string(zip_file),
|
|
127
130
|
},
|
|
128
|
-
files=
|
|
131
|
+
files={'file': ('multiple_files_scan.zip', zip_file.read(), 'application/octet-stream')},
|
|
132
|
+
on_upload_progress=on_upload_progress,
|
|
129
133
|
)
|
|
130
134
|
return models.ScanInitializationResponseSchema().load(response.json())
|
|
131
135
|
|
|
@@ -135,17 +139,38 @@ class ScanClient:
|
|
|
135
139
|
response = self.scan_cycode_client.get(url_path=url_path, hide_response_content_log=self._hide_response_log)
|
|
136
140
|
return models.UploadLinkResponseSchema().load(response.json())
|
|
137
141
|
|
|
138
|
-
def upload_to_presigned_post(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
def upload_to_presigned_post(
|
|
143
|
+
self,
|
|
144
|
+
url: str,
|
|
145
|
+
fields: dict[str, str],
|
|
146
|
+
zip_file: 'InMemoryZip',
|
|
147
|
+
on_upload_progress: Optional[Callable[[int, int], None]] = None,
|
|
148
|
+
) -> None:
|
|
149
|
+
all_files = {key: (None, value) for key, value in fields.items()}
|
|
150
|
+
all_files['file'] = ('multiple_files_scan.zip', zip_file.read(), 'application/octet-stream')
|
|
151
|
+
|
|
152
|
+
prepared = requests.Request('POST', 'https://dummy', files=all_files).prepare()
|
|
153
|
+
tracker = UploadProgressTracker(prepared.body, on_upload_progress)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
# We are not using Cycode client, as we are calling aws S3.
|
|
157
|
+
response = requests.post(
|
|
158
|
+
url,
|
|
159
|
+
data=tracker,
|
|
160
|
+
headers={'Content-Type': prepared.headers['Content-Type']},
|
|
161
|
+
timeout=self.scan_cycode_client.timeout,
|
|
162
|
+
)
|
|
163
|
+
response.raise_for_status()
|
|
164
|
+
except (requests.exceptions.ChunkedEncodingError, requests.exceptions.ConnectionError) as e:
|
|
165
|
+
if tracker.bytes_read < tracker.len:
|
|
166
|
+
raise SlowUploadConnectionError from e
|
|
167
|
+
raise
|
|
144
168
|
|
|
145
169
|
def scan_repository_from_upload_id(
|
|
146
170
|
self,
|
|
147
171
|
scan_type: str,
|
|
148
172
|
upload_id: str,
|
|
173
|
+
zip_file: InMemoryZip,
|
|
149
174
|
scan_parameters: dict,
|
|
150
175
|
is_git_diff: bool = False,
|
|
151
176
|
is_commit_range: bool = False,
|
|
@@ -159,6 +184,7 @@ class ScanClient:
|
|
|
159
184
|
'is_git_diff': is_git_diff,
|
|
160
185
|
'is_commit_range': is_commit_range,
|
|
161
186
|
'scan_parameters': json.dumps(scan_parameters),
|
|
187
|
+
'compression_manifest': self._create_compression_manifest_string(zip_file),
|
|
162
188
|
},
|
|
163
189
|
)
|
|
164
190
|
return models.ScanInitializationResponseSchema().load(response.json())
|
|
@@ -206,6 +232,7 @@ class ScanClient:
|
|
|
206
232
|
scan_type: str,
|
|
207
233
|
from_commit_upload_id: str,
|
|
208
234
|
to_commit_upload_id: str,
|
|
235
|
+
from_commit_zip_file: InMemoryZip,
|
|
209
236
|
scan_parameters: dict,
|
|
210
237
|
is_git_diff: bool = False,
|
|
211
238
|
) -> models.ScanInitializationResponse:
|
|
@@ -218,6 +245,7 @@ class ScanClient:
|
|
|
218
245
|
'to_commit_upload_id': to_commit_upload_id,
|
|
219
246
|
'is_git_diff': is_git_diff,
|
|
220
247
|
'scan_parameters': json.dumps(scan_parameters),
|
|
248
|
+
'compression_manifest': self._create_compression_manifest_string(from_commit_zip_file),
|
|
221
249
|
},
|
|
222
250
|
)
|
|
223
251
|
return models.ScanInitializationResponseSchema().load(response.json())
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '3.12.1.dev1' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|