"""Offline stdlib tests: python -I -S -B tests/test_container_import_config.py.""" from copy import deepcopy import importlib.util from pathlib import Path import unittest from unittest import mock HELPER = Path(__file__).resolve().parents[1] / 'app' / 'container_import_config.py' SPEC = importlib.util.spec_from_file_location('container_import_config', HELPER) MODULE = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(MODULE) translate_windows_config = MODULE.translate_windows_config class ContainerImportConfigTests(unittest.TestCase): def setUp(self): self.baseline = { 'global': { 'root_dir': '/opt/truf', 'project_dir': '{root_dir}/app', 'runtime_dir': '/data/runtime-linux', 'postgres_data_dir': '/data/postgres-linux', 'postgres_bin_dir': '/usr/lib/postgresql/16/bin', 'result_bundle_dir': '/data/scanner-result-bundles', 'work_dir': '/data/scanner-work', 'control_dir': '/run/truf/control', 'trufflehog_path': '/usr/local/bin/trufflehog', 'proxy_file': '{runtime_dir}/proxy.txt', 'secrets_file': '/data/config/secrets.yaml', 'trufflehog_config': '{project_dir}/trufflehog-custom-detectors.yaml', 'max_active_scans': 1, 'opportunistic_scan_slots': 0, 'trufflehog_job_memory_limit_bytes': 4294967296, 'trufflehog_windows_job_cpu_weight': 2, 'trufflehog_windows_memory_priority': 4, 'no_verification': False, 'api_proxy_enabled': False, 'min_free_gb': 20, 'database_url': '', 'baseline_only_policy': True, }, 'supervisor': { 'control_dir': '{control_dir}', 'instance_file': '{control_dir}/supervisor.instance.json', 'lock_file': '{control_dir}/supervisor.lock', 'control_host': '127.0.0.1', 'control_port': 8765, 'interactive': False, 'autostart': True, 'enabled_sources': ['dockerhub'], 'dashboard': {'enabled': False, 'port': 5000}, }, 'sources': { 'dockerhub': {'enabled': False, 'trufflehog_job_memory_limit_bytes': 6442450944}, 'linux_only': {'enabled': True}, }, 'keychecks': {'enabled': False, 'services': ['baseline-only']}, 'providers': {'baseline_only': {}}, 'query_policy': {'rejected': []}, } self.original = { 'global': { 'root_dir': r'D:\truf', 'project_dir': '.', 'runtime_dir': r'{root_dir}\runtime', 'postgres_data_dir': r'S:\postgres-data', 'result_bundle_dir': r'S:\scanner-result-bundles', 'work_dir': r'S:\scanner-work', 'trufflehog_path': r'C:\Tools\trufflehog.exe', 'control_dir': r'{runtime_dir}\control', 'proxy_file': r'{runtime_dir}\proxy.txt', 'secrets_file': r'{project_dir}\secrets.yaml', 'trufflehog_config': r'{project_dir}\trufflehog-custom-detectors.yaml', 'max_active_scans': 3, 'opportunistic_scan_slots': 1, 'trufflehog_job_memory_limit_bytes': 8589934592, 'trufflehog_windows_job_cpu_weight': 9, 'trufflehog_windows_memory_priority': 5, 'no_verification': True, 'api_proxy_enabled': True, 'download_proxy_enabled': False, 'min_free_gb': 31, 'detectors': ['fixture'], 'drop_detectors': r'fixture\regex', 'database_url': r'postgresql://fixture:fake\password@fixture.invalid/db', }, 'supervisor': { 'control_dir': '{control_dir}', 'instance_file': r'{control_dir}\supervisor.instance.json', 'lock_file': r'{control_dir}\supervisor.lock', 'control_host': 'localhost', 'control_port': 8888, 'interactive': True, 'autostart': False, 'enabled_sources': ['github', 'keychecks'], 'dashboard': {'enabled': True, 'port': 5444}, 'defaults': {'once': True, 'extra_args': ['--query', r'literal\query']}, 'sources': {'github': {'enabled': True, 'interval': 123}}, }, 'sources': { 'github': { 'enabled': True, 'workers': 17, 'queries': [r'path:/src\d+/ "sk-\w{6}"', r'C:\literal\query'], 'query_overrides': {r'literal\query': {'pages': 7}}, 'auth_pool': [{'name': r'fixture\account', 'token': r'fake\token'}], 'url': r'https://fixture.invalid/search?q=\d+&path=C:\literal', 'regex': r'\bfixture[\\/]\w+\s*$', }, 'dockerhub': {'enabled': True, 'trufflehog_job_memory_limit_bytes': 12884901888}, }, 'keychecks': { 'enabled': True, 'services': ['fixture'], 'retry_valid': True, 'service_args': {'fixture': ['--pattern', r'\bfixture\w+']}, 'env': {'FIXTURE_AUTH': r'fake\auth', 'FIXTURE_URL': r'https://fixture.invalid/\x'}, }, 'providers': {'fixture': {'auth': r'fake\auth', 'path': r'C:\opaque\provider-value'}}, 'query_policy': {'rejected': [{'source': 'github', 'query': r'\bfixture\w+'}]}, } def test_preserves_queries_regex_urls_auth_and_policy_without_baseline_merge(self): config, adjusted = translate_windows_config(self.original, self.baseline) for key in ('providers', 'query_policy', 'keychecks'): self.assertEqual(config[key], self.original[key]) self.assertEqual(config['sources']['github'], self.original['sources']['github']) self.assertTrue(config['sources']['dockerhub']['enabled']) self.assertNotIn('linux_only', config['sources']) self.assertNotIn('baseline_only_policy', config['global']) for key in ('no_verification', 'api_proxy_enabled', 'download_proxy_enabled', 'min_free_gb', 'detectors', 'drop_detectors', 'database_url'): self.assertEqual(config['global'][key], self.original['global'][key]) for key in ('enabled_sources', 'defaults', 'sources'): self.assertEqual(config['supervisor'][key], self.original['supervisor'][key]) self.assertFalse(any(path.startswith(('providers.', 'query_policy.')) for path in adjusted)) def test_exact_fixed_paths_do_not_depend_on_import_config_directory(self): config, _ = translate_windows_config(self.original, self.baseline) expected = { 'root_dir': '/opt/truf', 'project_dir': '/opt/truf/app', 'runtime_dir': '/data/runtime-linux', 'postgres_data_dir': '/data/postgres-linux', 'postgres_bin_dir': '/usr/lib/postgresql/16/bin', 'result_bundle_dir': '/data/scanner-result-bundles', 'work_dir': '/data/scanner-work', 'control_dir': '/run/truf/control', 'trufflehog_path': '/usr/local/bin/trufflehog', 'proxy_file': '/data/runtime-linux/proxy.txt', 'secrets_file': '/data/config/secrets.yaml', 'trufflehog_config': '/data/config/trufflehog-custom-detectors.yaml', } self.assertEqual({key: config['global'][key] for key in expected}, expected) self.assertEqual(config['supervisor']['control_dir'], '/run/truf/control') self.assertEqual(config['supervisor']['instance_file'], '/run/truf/control/supervisor.instance.json') self.assertEqual(config['supervisor']['lock_file'], '/run/truf/control/supervisor.lock') def test_only_platform_and_headless_settings_follow_baseline(self): self.original['sources']['github']['trufflehog_windows_job_cpu_weight'] = 7 config, _ = translate_windows_config(self.original, self.baseline) for key in ('max_active_scans', 'opportunistic_scan_slots', 'trufflehog_job_memory_limit_bytes', 'trufflehog_windows_job_cpu_weight', 'trufflehog_windows_memory_priority'): self.assertEqual(config['global'][key], self.baseline['global'][key]) self.assertEqual(config['sources']['dockerhub']['trufflehog_job_memory_limit_bytes'], 6442450944) self.assertEqual(config['sources']['github']['trufflehog_windows_job_cpu_weight'], 2) for key in ('interactive', 'autostart', 'control_host', 'control_port'): self.assertEqual(config['supervisor'][key], self.baseline['supervisor'][key]) self.assertEqual(config['supervisor']['dashboard'], {'enabled': False, 'port': 5444}) def test_normalizes_only_known_path_fields_and_keeps_templates_and_custom_names(self): fields = { 'global': { 'result_spool_dir': r'{runtime_dir}\result_spool', 'legacy_result_spool_dir': r'{runtime_dir}\result_spool', 'results_dir': r'{runtime_dir}\results', 'queue_dir': r'{runtime_dir}\queues', 'state_dir': r'{runtime_dir}\state', 'log_dir': r'{runtime_dir}\logs', 'keycheck_dir': r'{runtime_dir}\keychecks', 'postman_cache_dir': r'{runtime_dir}\postman_cache', 'gharchive_cache_dir': r'{state_dir}\gharchive_cache', 'database_path': r'{results_dir}\scanner_active.db', 'dashboard_db_path': '{database_path}', 'state_file': r'{state_dir}\custom-state.json', 'scan_limiter_db': r'{state_dir}\scan_limiter.db', 'dockerhub_tag_cache_path': r'{state_dir}\dockerhub_tag_cache.sqlite', 'api_proxy_file': '{proxy_file}', 'download_proxy_file': '', }, 'supervisor': { 'log_dir': '{log_dir}', 'state_dir': '{state_dir}', 'supervisor_log': r'{log_dir}\supervisor.log', 'status_file': r'{log_dir}\supervisor.status.txt', 'dashboard_log': r'{log_dir}\dashboard.log', }, 'keychecks': { 'input': r'{results_dir}\found_secrets.jsonl', 'proxy_file': r'{runtime_dir}\proxy.txt', 'keycheck_dir': r'{keycheck_dir}\fixture', 'summary_tsv': r'{keycheck_dir}\summary.tsv', 'summary_json': r'{keycheck_dir}\summary.json', 'alive_summary_tsv': r'{keycheck_dir}\alive_summary.tsv', }, } for section, values in fields.items(): self.original[section].update(values) source_paths = {'target_file': r'inputs\fixture.txt', 'postman_cache_dir': r'{postman_cache_dir}\fixture', 'gharchive_cache_dir': r'{state_dir}\gharchive_cache'} self.original['sources']['github'].update(source_paths) config, adjusted = translate_windows_config(self.original, self.baseline) for section, values in fields.items(): for key, value in values.items(): self.assertEqual(config[section][key], value.replace('\\', '/')) self.assertEqual(section + '.' + key in adjusted, '\\' in value) for key, value in source_paths.items(): self.assertEqual(config['sources']['github'][key], value.replace('\\', '/')) def test_known_copied_detector_and_proxy_references_use_imported_files(self): for detector in (r'{project_dir}\trufflehog-custom-detectors.yaml', r'D:\truf\app\trufflehog-custom-detectors.yaml', 'trufflehog-custom-detectors.yaml'): with self.subTest(detector=detector): self.original['sources']['github']['trufflehog_config'] = detector self.original['keychecks']['proxy_file'] = r'D:\truf\runtime\proxy.txt' config, _ = translate_windows_config(self.original, self.baseline) self.assertEqual(config['sources']['github']['trufflehog_config'], '/data/config/trufflehog-custom-detectors.yaml') self.assertEqual(config['keychecks']['proxy_file'], '/data/runtime-linux/proxy.txt') def test_inputs_and_nested_values_are_independent_on_success(self): before = deepcopy((self.original, self.baseline)) config, _ = translate_windows_config(self.original, self.baseline) self.assertEqual((self.original, self.baseline), before) config['sources']['github']['queries'].append('new query') config['sources']['github']['auth_pool'][0]['token'] = 'changed fake token' config['keychecks']['service_args']['fixture'].append('new argument') config['supervisor']['enabled_sources'].clear() self.assertEqual((self.original, self.baseline), before) def test_adjustment_report_is_exact_sorted_key_paths_and_idempotent(self): config, adjusted = translate_windows_config(self.original, self.baseline) expected = ['global.' + key for key in ( 'root_dir', 'project_dir', 'runtime_dir', 'postgres_data_dir', 'postgres_bin_dir', 'result_bundle_dir', 'work_dir', 'control_dir', 'trufflehog_path', 'proxy_file', 'secrets_file', 'trufflehog_config', 'max_active_scans', 'opportunistic_scan_slots', 'trufflehog_job_memory_limit_bytes', 'trufflehog_windows_job_cpu_weight', 'trufflehog_windows_memory_priority', )] expected += ['supervisor.' + key for key in ( 'control_dir', 'instance_file', 'lock_file', 'control_host', 'control_port', 'interactive', 'autostart', 'dashboard.enabled', )] expected.append('sources.dockerhub.trufflehog_job_memory_limit_bytes') self.assertEqual(adjusted, sorted(expected)) self.assertEqual(translate_windows_config(config, self.baseline), (config, [])) def test_rejects_unreviewed_absolute_executables_and_data_before_overriding(self): for key in ('root_dir', 'project_dir', 'runtime_dir', 'postgres_data_dir', 'postgres_bin_dir', 'result_bundle_dir', 'work_dir', 'trufflehog_path', 'trufflehog_config', 'proxy_file', 'secrets_file', 'database_path', 'api_proxy_file', 'download_proxy_file'): original = deepcopy(self.original) original['global'][key] = r'Z:\private-fixture\custom-path' before = deepcopy((original, self.baseline)) with self.subTest(key=key), self.assertRaisesRegex(ValueError, 'global\\.' + key) as error: translate_windows_config(original, self.baseline) self.assertNotIn('private-fixture', str(error.exception)) self.assertEqual((original, self.baseline), before) def test_rejects_foreign_custom_inputs_including_unc_device_and_drive_relative_paths(self): paths = (r'C:\private-fixture\input.txt', 'C:/private-fixture/input.txt', r'\\server\private-fixture\input.txt', '//server/private-fixture/input.txt', r'\\?\C:\private-fixture\input.txt', r'\private-fixture\input.txt', r'C:private-fixture\input.txt', r'D:\truf\app\custom-input.txt') for value in paths: for prefix, mapping, key in ( ('keychecks', self.original['keychecks'], 'input'), ('sources.github', self.original['sources']['github'], 'target_file'), ('sources.github', self.original['sources']['github'], 'trufflehog_config'), ('supervisor', self.original['supervisor'], 'instance_file'), ): with self.subTest(prefix=prefix, key=key, value=value): previous = deepcopy(mapping) mapping[key] = value with self.assertRaises(ValueError) as error: translate_windows_config(self.original, self.baseline) self.assertIn(prefix + '.' + key, str(error.exception)) self.assertNotIn('private-fixture', str(error.exception)) mapping.clear() mapping.update(previous) def test_rejects_relative_custom_executable_instead_of_substituting_a_different_binary(self): self.original['global']['trufflehog_path'] = r'custom-tools\private-fixture.exe' with self.assertRaisesRegex(ValueError, 'global.trufflehog_path'): translate_windows_config(self.original, self.baseline) def test_rejects_baseline_escaping_the_fixed_storage_contract(self): for section, key, value in ( ('global', 'project_dir', '.'), ('global', 'root_dir', '/elsewhere'), ('global', 'postgres_data_dir', '/elsewhere'), ('supervisor', 'control_dir', '/data/control'), ): baseline = deepcopy(self.baseline) baseline[section][key] = value with self.subTest(section=section, key=key), self.assertRaisesRegex(ValueError, 'Invalid Linux baseline path'): translate_windows_config(self.original, baseline) def test_translation_performs_no_io_or_environment_lookup(self): with mock.patch('builtins.open', side_effect=AssertionError('file IO forbidden')), \ mock.patch('os.getenv', side_effect=AssertionError('environment lookup forbidden')), \ mock.patch('os.environ', {}), mock.patch('builtins.print') as output: translate_windows_config(self.original, self.baseline) output.assert_not_called() if __name__ == '__main__': unittest.main()