diff --git a/cycode/cli/apps/scan/code_scanner.py b/cycode/cli/apps/scan/code_scanner.py index dc3727e4..642fe225 100644 --- a/cycode/cli/apps/scan/code_scanner.py +++ b/cycode/cli/apps/scan/code_scanner.py @@ -279,7 +279,10 @@ def scan_documents( scan_batch_thread_func = _get_scan_documents_thread_func(ctx, is_git_diff, is_commit_range, scan_parameters) - if should_use_presigned_upload(scan_type): + # Presigned single-file upload is async-only; a --sync scan must stay on the batched inline path + # so it never builds one oversized zip to POST synchronously. + should_use_sync_flow = _should_use_sync_flow(ctx.info_name, scan_type, ctx.obj['sync']) + if should_use_presigned_upload(scan_type) and not should_use_sync_flow: errors, local_scan_results = _run_presigned_upload_scan( scan_batch_thread_func, scan_type, documents_to_scan, progress_bar, printer ) diff --git a/cycode/cli/consts.py b/cycode/cli/consts.py index 9ef19eb2..b61bce17 100644 --- a/cycode/cli/consts.py +++ b/cycode/cli/consts.py @@ -220,12 +220,13 @@ FILE_MAX_SIZE_LIMIT_IN_BYTES = 5000000 PRESIGNED_LINK_UPLOADED_ZIP_MAX_SIZE_LIMIT_IN_BYTES = 5 * 1024 * 1024 * 1024 # 5 GB (S3 presigned POST limit) -PRESIGNED_UPLOAD_SCAN_TYPES = {SAST_SCAN_TYPE} +PRESIGNED_UPLOAD_SCAN_TYPES = {SAST_SCAN_TYPE, SECRET_SCAN_TYPE} DEFAULT_ZIP_MAX_SIZE_LIMIT_IN_BYTES = 20 * 1024 * 1024 ZIP_MAX_SIZE_LIMIT_IN_BYTES = { SCA_SCAN_TYPE: 200 * 1024 * 1024, SAST_SCAN_TYPE: PRESIGNED_LINK_UPLOADED_ZIP_MAX_SIZE_LIMIT_IN_BYTES, + SECRET_SCAN_TYPE: PRESIGNED_LINK_UPLOADED_ZIP_MAX_SIZE_LIMIT_IN_BYTES, } # scan in batches diff --git a/tests/cli/commands/scan/test_code_scanner.py b/tests/cli/commands/scan/test_code_scanner.py index 8a9f60b7..fb83c317 100644 --- a/tests/cli/commands/scan/test_code_scanner.py +++ b/tests/cli/commands/scan/test_code_scanner.py @@ -2,8 +2,10 @@ from os.path import normpath from unittest.mock import MagicMock, Mock, patch +import pytest + from cycode.cli import consts -from cycode.cli.apps.scan.code_scanner import scan_disk_files +from cycode.cli.apps.scan.code_scanner import scan_disk_files, scan_documents from cycode.cli.files_collector.file_excluder import _is_file_relevant_for_sca_scan from cycode.cli.files_collector.path_documents import _generate_document from cycode.cli.models import Document @@ -162,3 +164,51 @@ def test_entrypoint_cycode_not_added_for_single_file( assert len(entrypoint_docs) == 0 # Verify only the original documents are present assert len(documents_passed) == len(mock_documents) + + +@pytest.mark.parametrize( + ('scan_type', 'command_scan_type', 'sync_option', 'expect_presigned'), + [ + # SAST keeps uploading directly to S3 via a presigned URL (regression guard for the new sync gate). + (consts.SAST_SCAN_TYPE, 'path', False, True), + # Async secret scans now upload as a single file directly to S3 via a presigned URL. + (consts.SECRET_SCAN_TYPE, 'path', False, True), + # A --sync secret scan must stay on the batched inline path and never build one giant zip. + (consts.SECRET_SCAN_TYPE, 'path', True, False), + ], +) +@patch('cycode.cli.apps.scan.code_scanner.print_local_scan_results') +@patch('cycode.cli.apps.scan.code_scanner.set_issue_detected_by_scan_results') +@patch('cycode.cli.apps.scan.code_scanner.try_set_aggregation_report_url_if_needed') +@patch('cycode.cli.apps.scan.code_scanner.run_parallel_batched_scan') +@patch('cycode.cli.apps.scan.code_scanner._run_presigned_upload_scan') +def test_scan_documents_routes_upload_by_scan_type_and_sync( + mock_presigned_upload: Mock, + mock_batched_scan: Mock, + mock_aggregation: Mock, + mock_set_issue: Mock, + mock_print: Mock, + scan_type: str, + command_scan_type: str, + sync_option: bool, + expect_presigned: bool, +) -> None: + mock_presigned_upload.return_value = ([], []) + mock_batched_scan.return_value = ([], []) + + mock_ctx = MagicMock() + mock_ctx.info_name = command_scan_type + mock_ctx.obj = { + 'scan_type': scan_type, + 'progress_bar': MagicMock(), + 'console_printer': MagicMock(), + 'client': MagicMock(), + 'severity_threshold': None, + 'sync': sync_option, + } + documents = [Document('/repo/file.py', 'content', is_git_diff_format=False)] + + scan_documents(mock_ctx, documents, {}) + + assert mock_presigned_upload.called is expect_presigned + assert mock_batched_scan.called is (not expect_presigned)