"""Pure translation of the reviewed Windows config; YAML and file copies are caller-owned.""" from copy import deepcopy import ntpath # Only these reviewed absolute Windows paths have known container replacements. _FIXED_GLOBAL_PATHS = { 'root_dir': (r'D:\truf', '/opt/truf'), 'project_dir': (r'D:\truf\app', '/opt/truf/app'), 'runtime_dir': (r'D:\truf\runtime', '/data/runtime-linux'), 'postgres_data_dir': (r'S:\postgres-data', '/data/postgres-linux'), 'postgres_bin_dir': (r'D:\truf\runtime\postgres\pgsql\bin', '/usr/lib/postgresql/16/bin'), 'result_bundle_dir': (r'S:\scanner-result-bundles', '/data/scanner-result-bundles'), 'work_dir': (r'S:\scanner-work', '/data/scanner-work'), 'control_dir': (r'D:\truf\runtime\control', '/run/truf/control'), 'trufflehog_path': (r'C:\Tools\trufflehog.exe', '/usr/local/bin/trufflehog'), 'proxy_file': (r'D:\truf\runtime\proxy.txt', '/data/runtime-linux/proxy.txt'), 'secrets_file': (r'D:\truf\app\secrets.yaml', '/data/config/secrets.yaml'), 'trufflehog_config': ( r'D:\truf\app\trufflehog-custom-detectors.yaml', '/data/config/trufflehog-custom-detectors.yaml', ), } _PATH_FIELDS = { 'global': ( 'result_spool_dir', 'legacy_result_spool_dir', 'results_dir', 'queue_dir', 'state_dir', 'log_dir', 'keycheck_dir', 'postman_cache_dir', 'gharchive_cache_dir', 'database_path', 'state_file', 'api_proxy_file', 'download_proxy_file', 'dashboard_db_path', 'scan_limiter_db', 'dockerhub_tag_cache_path', ), 'supervisor': ('log_dir', 'supervisor_log', 'status_file', 'dashboard_log', 'state_dir'), 'keychecks': ('input', 'proxy_file', 'keycheck_dir', 'summary_tsv', 'summary_json', 'alive_summary_tsv'), } _SOURCE_PATH_FIELDS = ('target_file', 'trufflehog_config', 'postman_cache_dir', 'gharchive_cache_dir') _WINDOWS_KNOBS = ( 'trufflehog_job_memory_limit_bytes', 'trufflehog_windows_job_cpu_weight', 'trufflehog_windows_memory_priority', ) def translate_windows_config(original, baseline): """Return an independent config and sorted, changed dotted key paths (never values). ``baseline`` is the parsed config.linux.yaml, not a general merge source. Fixed paths must match the container contract. Other known path fields keep relative paths/templates with Linux separators; unreviewed Windows absolute paths raise ValueError naming only the key. No environment, filesystem, runtime, database, or YAML operations are performed. """ if not isinstance(original, dict) or not isinstance(baseline, dict): raise TypeError('original and baseline must be dictionaries') config = deepcopy(original) adjusted = set() def assign(mapping, key, value, prefix): if key not in mapping or mapping[key] != value: mapping[key] = deepcopy(value) adjusted.add(prefix + '.' + key) def path_value(value, key_path, approved=None): if value is None: return value if not isinstance(value, str): raise ValueError('Expected path string at ' + key_path) if ntpath.splitdrive(value)[0] or value.startswith('\\'): if approved is None or ntpath.normcase(ntpath.normpath(value)) != ntpath.normcase(ntpath.normpath(approved[0])): raise ValueError('Unsupported Windows path at ' + key_path) return approved[1] return value.replace('\\', '/') linux_paths = {key: pair[1] for key, pair in _FIXED_GLOBAL_PATHS.items()} control = _FIXED_GLOBAL_PATHS['control_dir'] supervisor_paths = {'control_dir': control} for key, filename in (('instance_file', 'supervisor.instance.json'), ('lock_file', 'supervisor.lock')): supervisor_paths[key] = (control[0] + '\\' + filename, control[1] + '/' + filename) for section, fixed_paths in (('global', _FIXED_GLOBAL_PATHS), ('supervisor', supervisor_paths)): mapping = config.setdefault(section, {}) for key, pair in fixed_paths.items(): key_path = section + '.' + key value = path_value(mapping.get(key), key_path, pair) if key == 'trufflehog_path' and value not in (None, '', 'trufflehog', 'trufflehog.exe', pair[1]): raise ValueError('Unsupported executable path at ' + key_path) # The copied original policy intentionally replaces the image policy. if key != 'trufflehog_config': try: baseline_path = baseline[section][key].format_map(linux_paths) except (KeyError, AttributeError, ValueError): raise ValueError('Invalid Linux baseline path at ' + key_path) from None if baseline_path != pair[1]: raise ValueError('Invalid Linux baseline path at ' + key_path) assign(mapping, key, pair[1], section) groups = [(section, config.get(section, {}), fields) for section, fields in _PATH_FIELDS.items()] groups.extend(('sources.' + name, source, _SOURCE_PATH_FIELDS) for name, source in config.get('sources', {}).items()) for prefix, mapping, fields in groups: for key in fields: if key not in mapping: continue approved = None if key in ('proxy_file', 'api_proxy_file', 'download_proxy_file'): approved = _FIXED_GLOBAL_PATHS['proxy_file'] elif key == 'trufflehog_config': approved = _FIXED_GLOBAL_PATHS[key] value = path_value(mapping[key], prefix + '.' + key, approved) if key == 'trufflehog_config' and value in ( '{project_dir}/trufflehog-custom-detectors.yaml', 'trufflehog-custom-detectors.yaml', ): value = linux_paths[key] assign(mapping, key, value, prefix) for key in ('max_active_scans', 'opportunistic_scan_slots') + _WINDOWS_KNOBS: assign(config['global'], key, baseline['global'][key], 'global') for key in ('interactive', 'autostart', 'control_host', 'control_port'): assign(config['supervisor'], key, baseline['supervisor'][key], 'supervisor') assign(config['supervisor'].setdefault('dashboard', {}), 'enabled', baseline['supervisor']['dashboard']['enabled'], 'supervisor.dashboard') for name, source in config.get('sources', {}).items(): source_baseline = baseline.get('sources', {}).get(name, {}) for key in _WINDOWS_KNOBS: if key in source or key in source_baseline: assign(source, key, source_baseline.get(key, baseline['global'][key]), 'sources.' + name) return config, sorted(adjusted)