diff --git a/cycode/cli/apps/scan/code_scanner.py b/cycode/cli/apps/scan/code_scanner.py index 5b4c3e78..724009a5 100644 --- a/cycode/cli/apps/scan/code_scanner.py +++ b/cycode/cli/apps/scan/code_scanner.py @@ -1,3 +1,4 @@ +import os import time from platform import platform from typing import TYPE_CHECKING, Callable, Optional @@ -21,6 +22,7 @@ from cycode.cli.files_collector.sca.sca_file_collector import add_sca_dependencies_tree_documents_if_needed from cycode.cli.files_collector.zip_documents import zip_documents from cycode.cli.models import CliError, Document, LocalScanResult +from cycode.cli.utils.path_utils import get_absolute_path, get_path_by_os from cycode.cli.utils.progress_bar import ScanProgressBarSection from cycode.cli.utils.scan_batch import run_parallel_batched_scan from cycode.cli.utils.scan_utils import ( @@ -53,6 +55,20 @@ def scan_disk_files(ctx: typer.Context, paths: tuple[str, ...]) -> None: paths, is_cycodeignore_allowed=is_cycodeignore_allowed_by_scan_config(ctx), ) + + # Add entrypoint.cycode file at root path to mark the scan root (only for single path) + if len(paths) == 1: + root_path = paths[0] + absolute_root_path = get_absolute_path(root_path) + entrypoint_path = get_path_by_os(os.path.join(absolute_root_path, consts.CYCODE_ENTRYPOINT_FILENAME)) + entrypoint_document = Document( + entrypoint_path, + '', # Empty file content + is_git_diff_format=False, + absolute_path=entrypoint_path, + ) + documents.append(entrypoint_document) + add_sca_dependencies_tree_documents_if_needed(ctx, scan_type, documents) scan_documents(ctx, documents, get_scan_parameters(ctx, paths)) except Exception as e: diff --git a/cycode/cli/consts.py b/cycode/cli/consts.py index e06f9b00..0acd887e 100644 --- a/cycode/cli/consts.py +++ b/cycode/cli/consts.py @@ -18,6 +18,7 @@ IAC_SCAN_SUPPORTED_FILE_PREFIXES = ('dockerfile', 'containerfile') CYCODEIGNORE_FILENAME = '.cycodeignore' +CYCODE_ENTRYPOINT_FILENAME = 'entrypoint.cycode' SECRET_SCAN_FILE_EXTENSIONS_TO_IGNORE = ( '.DS_Store', diff --git a/tests/cli/commands/scan/test_code_scanner.py b/tests/cli/commands/scan/test_code_scanner.py index bf4d3574..0a43f1c1 100644 --- a/tests/cli/commands/scan/test_code_scanner.py +++ b/tests/cli/commands/scan/test_code_scanner.py @@ -1,6 +1,9 @@ import os +from os.path import normpath +from unittest.mock import MagicMock, Mock, patch from cycode.cli import consts +from cycode.cli.apps.scan.code_scanner import scan_disk_files 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 @@ -72,3 +75,47 @@ def test_generate_document() -> None: assert isinstance(generated_tfplan_document, Document) assert generated_tfplan_document.path.endswith('.tf') assert generated_tfplan_document.is_git_diff_format == is_git_diff + + +@patch('cycode.cli.apps.scan.code_scanner.get_relevant_documents') +@patch('cycode.cli.apps.scan.code_scanner.scan_documents') +@patch('cycode.cli.apps.scan.code_scanner.get_scan_parameters') +def test_entrypoint_cycode_added_to_documents( + mock_get_scan_parameters: Mock, + mock_scan_documents: Mock, + mock_get_relevant_documents: Mock, +) -> None: + """Test that entrypoint.cycode file is added to documents in scan_disk_files.""" + # Arrange + mock_ctx = MagicMock() + mock_ctx.obj = { + 'scan_type': consts.SAST_SCAN_TYPE, + 'progress_bar': MagicMock(), + } + mock_get_scan_parameters.return_value = {} + + mock_documents = [ + Document('/test/path/file1.py', 'content1', is_git_diff_format=False), + Document('/test/path/file2.js', 'content2', is_git_diff_format=False), + ] + mock_get_relevant_documents.return_value = mock_documents.copy() + test_path = '/Users/test/repositories' + + # Act + scan_disk_files(mock_ctx, (test_path,)) + + # Assert + call_args = mock_scan_documents.call_args + documents_passed = call_args[0][1] + + # Verify entrypoint document was added + entrypoint_docs = [doc for doc in documents_passed if doc.path.endswith(consts.CYCODE_ENTRYPOINT_FILENAME)] + assert len(entrypoint_docs) == 1 + + entrypoint_doc = entrypoint_docs[0] + # Normalize paths for cross-platform compatibility + expected_path = normpath(os.path.join(os.path.abspath(test_path), consts.CYCODE_ENTRYPOINT_FILENAME)) + assert normpath(entrypoint_doc.path) == expected_path + assert entrypoint_doc.content == '' + assert entrypoint_doc.is_git_diff_format is False + assert normpath(entrypoint_doc.absolute_path) == normpath(entrypoint_doc.path)