import copy import os from pathlib import Path import sys from unittest import mock import pytest APP_DIR = Path(__file__).resolve().parents[1] / 'app' sys.path.insert(0, str(APP_DIR)) import child_bootstrap import lifecycle_authority as authority import scanner @pytest.fixture def manifested_git(tmp_path, monkeypatch): app_dir = tmp_path / 'app' app_dir.mkdir() for name in (*authority.CODE_AUTHORITY_FILES, *authority.EXTERNAL_CODE_AUTHORITY_FILES): path = app_dir / name path.parent.mkdir(parents=True, exist_ok=True) path.write_text('# fixture\n', encoding='ascii') trufflehog = tmp_path / 'trufflehog.exe' trufflehog.write_bytes(b'trufflehog fixture') git = tmp_path / 'git.exe' git.write_bytes(b'git fixture') manifest = authority.build_code_manifest( app_dir=app_dir, trufflehog_path=trufflehog, git_path=git, ) monkeypatch.delenv(authority.CHILD_KIND_ENV, raising=False) return manifest, git, app_dir, trufflehog def clone_command(manifest, tmp_path): return [ manifest['executables']['git']['path'], 'clone', '--no-checkout', '--no-recurse-submodules', '--', 'https://example.invalid/owner/repository.git', str(tmp_path / 'clone'), ] def test_manifest_requires_both_content_identities(manifested_git): manifest, git, _, _ = manifested_git assert set(manifest['executables']) == {'trufflehog', 'git'} assert manifest['executables']['git'] == { 'path': authority.canonical_path(git), 'sha256': authority.sha256_file(git), } assert authority.verify_code_manifest(manifest) == manifest digest = authority.code_manifest_sha256(manifest) assert child_bootstrap._verify_manifest( {'code_manifest': manifest, 'code_manifest_sha256': digest}, {'code_manifest_sha256': digest}, ) == manifest['root'] @pytest.mark.parametrize('change', ['missing', 'extra', 'entry', 'relative', 'empty', 'digest']) def test_manifest_rejects_invalid_git_identity(manifested_git, change): manifest = copy.deepcopy(manifested_git[0]) if change == 'missing': del manifest['executables']['git'] elif change == 'extra': manifest['executables']['shell'] = manifest['executables']['git'].copy() elif change == 'entry': manifest['executables']['git'] = None else: field, value = { 'relative': ('path', 'git.exe'), 'empty': ('path', ''), 'digest': ('sha256', 'z' * 64), }[change] manifest['executables']['git'][field] = value with pytest.raises(authority.LifecycleAuthorityError): authority.normalize_code_manifest(manifest) def test_git_drift_rejected_even_with_preserved_size_and_mtime(manifested_git): manifest, git, _, _ = manifested_git before = git.stat() git.write_bytes(b'GIT fixture') os.utime(git, ns=(before.st_atime_ns, before.st_mtime_ns)) assert git.stat().st_size == before.st_size assert git.stat().st_mtime_ns == before.st_mtime_ns with pytest.raises(authority.LifecycleAuthorityError, match='executable:git'): authority.verify_code_manifest(manifest) def test_git_requires_exact_private_acl(manifested_git, monkeypatch): manifest, git, _, _ = manifested_git monkeypatch.setattr(authority, 'private_file_ready', lambda path: path != authority.canonical_path(git)) with pytest.raises(authority.LifecycleAuthorityError, match='executable:git'): authority.verify_code_manifest(manifest, require_private_acl=True) def test_git_included_in_existing_only_preflight(manifested_git): _, git, app_dir, trufflehog = manifested_git paths = authority.manifest_authority_paths( app_dir, trufflehog, existing_only=True, git_path=git, ) assert authority.canonical_path(git) in paths @pytest.mark.parametrize('existing_only', [False, True]) def test_missing_git_fails_closed_in_preflight(manifested_git, existing_only): _, git, app_dir, trufflehog = manifested_git git.unlink() with pytest.raises(authority.LifecycleAuthorityError, match='Git executable'): authority.manifest_authority_paths( app_dir, trufflehog, existing_only=existing_only, git_path=git, ) with pytest.raises(authority.LifecycleAuthorityError, match='Git executable'): authority.build_code_manifest(app_dir, trufflehog, git_path=git) def test_project_private_git_preferred_for_capture_and_preflight(manifested_git): _, _, app_dir, trufflehog = manifested_git private_git = app_dir.parent / 'runtime' / 'git' / 'cmd' / 'git.exe' private_git.parent.mkdir(parents=True) private_git.write_bytes(b'private Git fixture') with mock.patch.object(authority.shutil, 'which', side_effect=AssertionError('unexpected PATH lookup')): manifest = authority.build_code_manifest(app_dir, trufflehog) paths = authority.manifest_authority_paths(app_dir, trufflehog, existing_only=True) resolved = authority.resolve_manifest_executable(None, name='git', app_dir=app_dir) assert resolved == authority.canonical_path(private_git) assert manifest['executables']['git']['path'] == resolved assert manifest['executables']['git']['sha256'] == authority.sha256_file(private_git) assert resolved in paths def test_explicit_git_override_precedes_private_copy(manifested_git): _, git, app_dir, trufflehog = manifested_git private_git = app_dir.parent / 'runtime' / 'git' / 'cmd' / 'git.exe' private_git.parent.mkdir(parents=True) private_git.write_bytes(b'private Git fixture') with mock.patch.object(authority.shutil, 'which', side_effect=AssertionError('unexpected PATH lookup')): manifest = authority.build_code_manifest(app_dir, trufflehog, git_path=git) paths = authority.manifest_authority_paths(app_dir, trufflehog, git_path=git, existing_only=True) assert manifest['executables']['git']['path'] == authority.canonical_path(git) assert authority.canonical_path(git) in paths assert authority.canonical_path(private_git) not in paths with pytest.raises(authority.LifecycleAuthorityError, match='Git executable'): authority.build_code_manifest(app_dir, trufflehog, git_path=app_dir.parent / 'missing.exe') def test_invalid_private_git_does_not_fall_back_to_path(manifested_git): _, _, app_dir, trufflehog = manifested_git private_git = app_dir.parent / 'runtime' / 'git' / 'cmd' / 'git.exe' private_git.mkdir(parents=True) with mock.patch.object(authority.shutil, 'which', side_effect=AssertionError('unexpected PATH lookup')): with pytest.raises(authority.LifecycleAuthorityError, match='Git executable is not a regular file'): authority.build_code_manifest(app_dir, trufflehog) @pytest.mark.parametrize('private_parent_exists', [False, True]) def test_default_git_is_resolved_only_at_manifest_capture(manifested_git, monkeypatch, private_parent_exists): _, git, app_dir, trufflehog = manifested_git if private_parent_exists: (app_dir.parent / 'runtime' / 'git' / 'cmd').mkdir(parents=True) with mock.patch.object(authority.shutil, 'which', return_value=str(git)) as which: manifest = authority.build_code_manifest(app_dir, trufflehog) paths = authority.manifest_authority_paths(app_dir, trufflehog, existing_only=True) assert which.call_args_list == [mock.call('git'), mock.call('git')] assert authority.canonical_path(git) in paths monkeypatch.setattr(scanner, '_runtime_initialized', True) metadata = {'code_manifest': manifest} with mock.patch.object(scanner, 'require_active_supervisor_child', return_value=metadata) as authenticate, \ mock.patch.object(scanner.shutil, 'which', side_effect=AssertionError('launch PATH lookup')): assert scanner.get_git_cmd() == authority.canonical_path(git) authenticate.assert_called_once_with(child_kind='scanner', require_dsn=True) def test_uninitialized_getter_is_not_launch_authority(monkeypatch, manifested_git, tmp_path): monkeypatch.setattr(scanner, '_runtime_initialized', False) with mock.patch.object(scanner.shutil, 'which', return_value=None): assert scanner.get_git_cmd() == 'git' with mock.patch.dict(os.environ, {}, clear=True): with pytest.raises(authority.LifecycleAuthorityError, match='direct mutation is retired'): scanner.require_git_clone_launch_authority(clone_command(manifested_git[0], tmp_path)) def test_remote_scanner_child_prefers_manifested_git(manifested_git): manifest, git, _, _ = manifested_git token = scanner._client_scan_manifest.set(manifest) try: env = scanner.prepend_client_git_environment({'PATH': 'foreign-path'}) finally: scanner._client_scan_manifest.reset(token) entries = env['PATH'].split(os.pathsep) assert os.path.normcase(entries[0]) == os.path.normcase(str(git.parent)) assert entries[1:] == ['foreign-path'] def test_exact_clone_authenticates_and_returns_metadata(manifested_git, tmp_path): metadata = {'code_manifest': manifested_git[0]} with mock.patch.object(scanner, 'require_active_supervisor_child', return_value=metadata) as authenticate: assert scanner.require_git_clone_launch_authority(clone_command(manifested_git[0], tmp_path)) is metadata authenticate.assert_called_once_with(child_kind='scanner', require_dsn=True) @pytest.mark.parametrize('kind', ['docker-shadow', 'keycheck-provider', 'janitor']) def test_git_requires_scanner_child_kind(manifested_git, tmp_path, monkeypatch, kind): monkeypatch.setenv(authority.CHILD_KIND_ENV, kind) monkeypatch.setattr(scanner, '_runtime_initialized', True) with mock.patch.object(scanner, 'require_active_supervisor_child') as authenticate: with pytest.raises(RuntimeError, match='requires scanner authority'): scanner.require_git_clone_launch_authority(clone_command(manifested_git[0], tmp_path)) with pytest.raises(RuntimeError, match='requires scanner authority'): scanner.get_git_cmd() authenticate.assert_not_called() @pytest.mark.parametrize('operation', ['fetch', 'checkout', 'config', 'submodule', 'init', '--version']) def test_other_operations_rejected_before_authentication(manifested_git, tmp_path, operation): cmd = clone_command(manifested_git[0], tmp_path) cmd[1] = operation with mock.patch.object(scanner, 'require_active_supervisor_child') as authenticate: with pytest.raises(RuntimeError, match='argv contract'): scanner.require_git_clone_launch_authority(cmd) authenticate.assert_not_called() @pytest.mark.parametrize('change', ['extra', 'missing', 'reordered', 'separator', 'config', 'depth', 'string', 'none', 'nonstring', 'nul']) def test_only_exact_clone_argv_is_allowed(manifested_git, tmp_path, change): cmd = clone_command(manifested_git[0], tmp_path) if change == 'extra': cmd.append('--verbose') elif change == 'missing': del cmd[3] elif change == 'reordered': cmd[2], cmd[3] = cmd[3], cmd[2] elif change == 'separator': cmd[4] = '--bare' elif change in {'config', 'depth'}: cmd[2] = '-c' if change == 'config' else '--depth=1' elif change == 'string': cmd = ' '.join(cmd) elif change == 'none': cmd = None elif change == 'nonstring': cmd[6] = Path(cmd[6]) elif change == 'nul': cmd[6] += '\x00' with mock.patch.object(scanner, 'require_active_supervisor_child') as authenticate: with pytest.raises(RuntimeError, match='argv contract'): scanner.require_git_clone_launch_authority(cmd) authenticate.assert_not_called() @pytest.mark.parametrize('source', [ 'http://example.invalid/repo', 'ssh://example.invalid/repo', 'file:///fixture/repo', '/fixture/repo', 'C:\\fixture\\repo', 'git@example.invalid:repo', 'ext::command', '--upload-pack=command', 'https:///repo', 'https://example.invalid', 'https://user@example.invalid/repo', 'https://user:password@example.invalid/repo', 'https://example.invalid/repo?token=sentinel', 'https://example.invalid/repo#sentinel', 'https://example.invalid\\@other.invalid/repo', 'https://example.invalid/%20 repo', 'https://example.invalid%2fother/repo', 'https://[broken/repo', 'https://example.invalid:bad/repo', 'https://example.invalid:99999/repo', ]) def test_unsafe_sources_rejected_even_when_uninitialized(manifested_git, tmp_path, monkeypatch, source): monkeypatch.setattr(scanner, '_runtime_initialized', False) cmd = clone_command(manifested_git[0], tmp_path) cmd[5] = source with mock.patch.object(scanner, 'require_active_supervisor_child') as authenticate: with pytest.raises(RuntimeError, match='credential-free absolute HTTPS'): scanner.require_git_clone_launch_authority(cmd) authenticate.assert_not_called() @pytest.mark.parametrize('destination', ['relative', '-option', '']) def test_invalid_destination_rejected(manifested_git, tmp_path, destination): cmd = clone_command(manifested_git[0], tmp_path) cmd[6] = destination with pytest.raises(RuntimeError): scanner.require_git_clone_launch_authority(cmd) @pytest.mark.skipif(os.name != 'nt', reason='Windows drive-relative paths') @pytest.mark.parametrize('destination', ['\\clone', '/clone', 'C:clone']) def test_drive_relative_destination_rejected(manifested_git, tmp_path, destination): cmd = clone_command(manifested_git[0], tmp_path) cmd[6] = destination with pytest.raises(RuntimeError, match='absolute path'): scanner.require_git_clone_launch_authority(cmd) @pytest.mark.parametrize('replacement', ['path', 'bare', 'trufflehog']) def test_unmanifested_executable_is_rejected(manifested_git, tmp_path, replacement): manifest = manifested_git[0] cmd = clone_command(manifest, tmp_path) cmd[0] = { 'path': str(tmp_path / 'replacement.exe'), 'bare': 'git', 'trufflehog': manifest['executables']['trufflehog']['path'], }[replacement] with mock.patch.object(scanner, 'require_active_supervisor_child', return_value={'code_manifest': manifest}): with pytest.raises(RuntimeError, match='immutable supervisor authority'): scanner.require_git_clone_launch_authority(cmd) def test_getter_and_guard_propagate_content_verification_failure(manifested_git, tmp_path, monkeypatch): manifest, git, _, _ = manifested_git git.write_bytes(b'changed Git executable') monkeypatch.setattr(scanner, '_runtime_initialized', True) def authenticate(**kwargs): assert kwargs == {'child_kind': 'scanner', 'require_dsn': True} authority.verify_code_manifest(manifest) return {'code_manifest': manifest} monkeypatch.setattr(scanner, 'require_active_supervisor_child', authenticate) with pytest.raises(authority.LifecycleAuthorityError, match='executable:git'): scanner.get_git_cmd() with pytest.raises(authority.LifecycleAuthorityError, match='executable:git'): scanner.require_git_clone_launch_authority(clone_command(manifest, tmp_path))