"""Offline tests; do not require Windows, PowerShell, WAPT or network access.""" import importlib.util import io import json import hashlib import os from pathlib import Path import sys import tempfile import types import unittest import zipfile import shutil from unittest import mock ROOT = Path(__file__).resolve().parents[1] setuphelpers_stub = types.ModuleType('setuphelpers') setuphelpers_stub.makepath = lambda *parts: os.path.join(*(str(part) for part in parts)) setuphelpers_stub.isfile = os.path.isfile setuphelpers_stub.isdir = os.path.isdir setuphelpers_stub.mkdirs = lambda path: os.makedirs(path, exist_ok=True) setuphelpers_stub.ensure_dir = lambda path: os.makedirs(os.path.dirname(path), exist_ok=True) setuphelpers_stub.filecopyto = lambda source, target: shutil.copy2(source, target) setuphelpers_stub.copytree2 = lambda source, target, follow_symlinks=False: shutil.copytree( source, target, dirs_exist_ok=True, symlinks=not follow_symlinks) setuphelpers_stub.remove_tree = lambda path, ignore_errors=False: shutil.rmtree( path, ignore_errors=ignore_errors) setuphelpers_stub.remove_file = lambda path: os.remove(path) if os.path.exists(path) else None setuphelpers_stub.get_sha256 = lambda path: hashlib.sha256(Path(path).read_bytes()).hexdigest() setuphelpers_stub.json_load_file = lambda path: json.loads(Path(path).read_text(encoding='utf-8')) setuphelpers_stub.json_write_file = lambda path, data, **kwargs: Path(path).write_text( json.dumps(data, **kwargs) + '\n', encoding='utf-8') setuphelpers_stub.system32 = lambda: os.path.join(os.environ.get('SystemRoot', '/tmp'), 'System32') setuphelpers_stub.run = lambda *args, **kwargs: '' setuphelpers_stub.wget = lambda *args, **kwargs: (_ for _ in ()).throw( AssertionError('unexpected network download in synthetic tests')) sys.modules.setdefault('setuphelpers', setuphelpers_stub) spec = importlib.util.spec_from_file_location('wapt_hardeningkitty_setup', ROOT / 'setup.py') pkg = importlib.util.module_from_spec(spec) spec.loader.exec_module(pkg) vendor_spec = importlib.util.spec_from_file_location('hk_vendor', ROOT / 'tools' / 'vendor_assets.py') vendor = importlib.util.module_from_spec(vendor_spec) vendor_spec.loader.exec_module(vendor) verify_spec = importlib.util.spec_from_file_location('hk_package_verify', ROOT / 'tools' / 'package_verify.py') verify = importlib.util.module_from_spec(verify_spec) verify_spec.loader.exec_module(verify) update_spec = importlib.util.spec_from_file_location('hk_update_package', ROOT / 'update_package.py') updater = importlib.util.module_from_spec(update_spec) update_spec.loader.exec_module(updater) def csv_content(rows): import csv with io.StringIO(newline='') as buf: writer = csv.writer(buf, lineterminator='\n') writer.writerow(['ID', 'Category', 'Name', 'Severity', 'Result', 'Recommended', 'TestResult', 'SeverityFinding']) normalized = [] for row in rows: row = list(row) if len(row) == 7: severity = str(row[3]).lower() row.append(severity.title() if severity in ('high', 'medium', 'low') else 'High') normalized.append(row) writer.writerows(normalized) return buf.getvalue() def make_source(root): root = Path(root) (root / 'WAPT').mkdir(parents=True, exist_ok=True) (root / 'WAPT' / 'control').write_text('package: synthetic\n', encoding='utf-8') (root / 'setup.py').write_text('# synthetic setup\n', encoding='utf-8') (root / 'runner.ps1').write_text('Invoke-HardeningKitty -Mode Audit\n', encoding='utf-8') (root / 'audit_config.json').write_text('{}\n', encoding='utf-8') asset_root = root / 'vendor' assets = {x: ('sample ' + x).encode('utf-8') for x in vendor.REQUIRED} assets['HardeningKitty.psd1'] = b"@{\n ModuleVersion = '0.9.5'\n}\n" csv_count = sum(name.startswith('lists/') and name.endswith('.csv') for name in assets) assets.update({'lists/synthetic_machine_%02d.csv' % i: ('synthetic list %d' % i).encode('utf-8') for i in range(max(0, 50 - csv_count))}) for name, data in assets.items(): path = asset_root.joinpath(*name.split('/')) path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(data) manifest = {'upstream_commit': vendor.UPSTREAM_COMMIT, 'files': {k: hashlib.sha256(v).hexdigest() for k, v in assets.items()}} (asset_root / 'vendor-manifest.json').write_text(json.dumps(manifest), encoding='utf-8') return root def make_vendor_archive(): data = io.BytesIO() prefix = '0x6d69636b-windows_hardening-' + vendor.UPSTREAM_COMMIT[:7] with zipfile.ZipFile(data, mode='w') as archive: names = set(vendor.REQUIRED) names.update('lists/finding_list_test_%02d.csv' % i for i in range(50)) for name in names: archive.writestr(prefix + '/' + name, 'synthetic:' + name) return data.getvalue() class PackageTests(unittest.TestCase): def test_runtime_and_vendor_preflight_requirements_stay_aligned(self): self.assertEqual(set(pkg.REQUIRED_VENDOR_FILES), vendor.REQUIRED) def test_compliance_and_missing_baseline_checks(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) baseline = root / 'baseline.csv' baseline.write_text(csv_content([ ['1', 'Firewall', 'Enabled', 'High', '', '', ''], ['2', 'Accounts', 'Guest disabled', 'Medium', '', '', ''], ['3', 'Security', 'Other control', 'Low', '', '', ''], ]), encoding='utf-8') report = root / 'report.csv' report.write_text(csv_content([ ['1', 'Firewall', 'Enabled', 'Passed', '1', '1', 'Passed'], ['2', 'Accounts', 'Guest disabled', 'Medium', '0', '1', 'Failed'], ]), encoding='utf-8') counts, findings = pkg._read_report(str(report)) counts, findings = pkg._check_coverage(str(baseline), str(report), counts, findings) self.assertEqual(counts['total'], 3) self.assertEqual(counts['executed'], 2) self.assertEqual(counts['skipped'], 1) self.assertEqual(counts['passed'], 1) self.assertEqual(counts['medium'], 1) self.assertEqual(counts['unknown'], 1) self.assertEqual(pkg._status_from_counts(counts, {'error_on_high': True, 'warn_on_low': True}), 'ERROR') self.assertEqual(len(findings), 2) self.assertEqual(counts['expected'], 3) def test_only_reviewed_conditional_exclusions_leave_coverage_denominator(self): with tempfile.TemporaryDirectory() as tmp: baseline = Path(tmp, 'baseline.csv') baseline.write_text(csv_content([ ['1', 'Firewall', 'Enabled', 'High', '', '', ''], ['2', 'Tools', 'Conditional check', 'Low', '', '', ''], ]), encoding='utf-8') report = Path(tmp, 'report.csv') report.write_text(csv_content([ ['1', 'Firewall', 'Enabled', 'Passed', '', '', 'Passed'], ]), encoding='utf-8') counts, findings = pkg._read_report(str(report)) counts, findings = pkg._check_coverage(str(baseline), str(report), counts, findings, {'baseline.csv': {'2': 'Only applies when optional tool is installed'}}, 'baseline.csv') self.assertEqual(counts['excluded'], 1) self.assertEqual(counts['unknown'], 0) self.assertEqual(counts['total'], 1) self.assertEqual(pkg._status_from_counts(counts, {'error_on_high': True, 'warn_on_low': True}), 'OK') def test_coverage_joins_by_stable_id_across_localized_csv_labels(self): with tempfile.TemporaryDirectory() as tmp: baseline = Path(tmp, 'baseline.csv') baseline.write_text(csv_content([ ['42', 'Sécurité', 'Contrôle français', 'High', '', '', ''], ]), encoding='utf-8') report = Path(tmp, 'report.csv') report.write_bytes(csv_content([ ['42', 'Security', 'Localized control', 'Passed', '', '', 'Passed'], ]).encode('cp1252')) with mock.patch.object(pkg.locale, 'getpreferredencoding', return_value='cp1252'): counts, findings = pkg._read_report(str(report)) counts, findings = pkg._check_coverage(str(baseline), str(report), counts, findings) self.assertEqual(counts['passed'], 1) self.assertEqual(counts['unknown'], 0) self.assertEqual(counts['skipped'], 0) def test_high_medium_low_thresholds(self): base = {'total': 10, 'unknown': 0, 'high': 0, 'medium': 0, 'low': 0} cfg = {'error_on_high': True, 'warn_on_low': True} self.assertEqual(pkg._status_from_counts(base, cfg), 'OK') base['low'] = 1 self.assertEqual(pkg._status_from_counts(base, cfg), 'WARNING') base['low'] = 0 base['medium'] = 1 self.assertEqual(pkg._status_from_counts(base, cfg), 'WARNING') base['high'] = 1 self.assertEqual(pkg._status_from_counts(base, cfg), 'ERROR') cfg['error_on_high'] = False self.assertEqual(pkg._status_from_counts(base, cfg), 'WARNING') def test_report_never_leaks_values_by_default(self): with tempfile.TemporaryDirectory() as tmp: report = Path(tmp, 'report.csv') report.write_text(csv_content([ ['9', 'Sensitive', 'Setting', 'High', 'private_value', 'expected', 'Failed'] ]), encoding='utf-8') _, findings = pkg._read_report(str(report), False) self.assertNotIn('current', findings[0]) self.assertNotIn('recommended', findings[0]) _, findings2 = pkg._read_report(str(report), True) self.assertEqual(findings2[0]['current'], 'private_value') def test_reporting_payload_size_is_measured_without_sensitive_values(self): with tempfile.TemporaryDirectory() as tmp: report = Path(tmp, 'report.csv') baseline = Path(tmp, 'baseline.csv') rows = [['%04d' % i, 'Synthetic category', 'Synthetic control %04d' % i, 'Low', 'current-setting-' * 8, 'recommended-setting-' * 8, 'Failed'] for i in range(500)] report.write_text(csv_content(rows), encoding='utf-8') baseline.write_text(csv_content([ [row[0], row[1], row[2], 'Low', '', row[5], ''] for row in rows ]), encoding='utf-8') raw_bytes = report.stat().st_size counts, findings = pkg._read_report(str(report), publish_current_values=False) counts, findings = pkg._check_coverage(str(baseline), str(report), counts, findings) summary = dict(counts, status='WARNING', source='GPO', upstream_commit=pkg.UPSTREAM_COMMIT, last_run_utc='2026-10-29T00:00:00+00:00', benchmark='synthetic.csv', os_caption='Synthetic Windows', os_build='1', display_version='synthetic') compact = pkg._build_audit_document(summary, findings, str(report), str(baseline), {}) expanded = pkg._build_audit_document(summary, findings, str(report), str(baseline), {'publish_current_values': True}) compact_bytes = len(json.dumps(compact, ensure_ascii=False, separators=(',', ':')).encode('utf-8')) expanded_bytes = len(json.dumps(expanded, ensure_ascii=False, separators=(',', ':')).encode('utf-8')) self.assertEqual(len(compact['controls']), 500) # A complete structured schema is richer than the CSV; record both # measured sizes instead of claiming it is smaller than source. self.assertGreater(compact_bytes, 0) self.assertGreater(raw_bytes, 0) self.assertGreater(expanded_bytes, compact_bytes) self.assertLessEqual(compact_bytes, pkg.MAX_AUDIT_JSON_BYTES) self.assertNotIn('current', compact['controls']['0000']) def test_ignored_not_applicable_and_unknown_are_not_compliant(self): with tempfile.TemporaryDirectory() as tmp: report = Path(tmp, 'report.csv') report.write_text(csv_content([ ['1', 'A', 'passed', 'Passed', '', '', 'Passed'], ['2', 'B', 'skipped', 'Low', '', '', 'Skipped'], ['3', 'C', 'n/a', '', '', '', 'Not Applicable'], ['4', 'D', 'unknown', '', '', '', 'Unexpected'], ]), encoding='utf-8') counts, findings = pkg._read_report(str(report)) self.assertEqual(counts['passed'], 1) self.assertEqual(counts['ignored'], 1) self.assertEqual(counts['not_applicable'], 1) self.assertEqual(counts['unknown'], 1) self.assertEqual(counts['total'], 3) self.assertEqual(counts['compliant_percent'], 33.33) self.assertEqual(pkg._status_from_counts(counts, {'error_on_high': True, 'warn_on_low': True}), 'ERROR') def test_duplicate_report_control_rejected(self): with tempfile.TemporaryDirectory() as tmp: report = Path(tmp, 'report.csv') row = ['1', 'Firewall', 'Enabled', 'Passed', '', '', 'Passed'] report.write_text(csv_content([row, row]), encoding='utf-8') with self.assertRaisesRegex(RuntimeError, 'duplicate'): pkg._read_report(str(report)) def test_vendor_integrity_manifest(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) assets = {x: ('sample ' + x).encode('utf-8') for x in vendor.REQUIRED} assets.update({'lists/synthetic_machine_%02d.csv' % i: ('synthetic list %d' % i).encode('utf-8') for i in range(max(0, 50 - len(assets)))}) for name, data in assets.items(): path = root.joinpath(*name.split('/')) path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(data) manifest = {'upstream_commit': vendor.UPSTREAM_COMMIT, 'files': {k: hashlib.sha256(v).hexdigest() for k, v in assets.items()}} (root / 'vendor-manifest.json').write_text(json.dumps(manifest), encoding='utf-8') self.assertEqual(pkg._verify_vendor(str(root))['upstream_commit'], vendor.UPSTREAM_COMMIT) (root / 'HardeningKitty.psm1').write_text('tampered', encoding='utf-8') with self.assertRaises(RuntimeError): pkg._verify_vendor(str(root)) def test_installer_and_uninstaller_with_mock_paths(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) package_source = make_source(root / 'package') (package_source / 'tools').mkdir(exist_ok=True) shutil.copy2(ROOT / 'tools' / 'vendor_assets.py', package_source / 'tools' / 'vendor_assets.py') shutil.copy2(ROOT / 'tools' / 'package_verify.py', package_source / 'tools' / 'package_verify.py') verify.write_source_manifest(package_source) prior = (pkg._package_source, pkg.INSTALL_ROOT, pkg.REPORT_ROOT) try: pkg._package_source = lambda: str(package_source) pkg.INSTALL_ROOT = str(root / 'programdata' / 'engine') pkg.REPORT_ROOT = str(root / 'programdata' / 'reports') pkg.install() self.assertEqual(pkg._verify_vendor(pkg.INSTALL_ROOT, runtime=True)['upstream_commit'], vendor.UPSTREAM_COMMIT) self.assertTrue(Path(pkg.REPORT_ROOT).is_dir()) foreign = Path(pkg.INSTALL_ROOT, 'operator-note.txt') foreign.write_text('keep me', encoding='utf-8') with self.assertRaisesRegex(RuntimeError, 'not owned'): pkg.uninstall() self.assertTrue(foreign.is_file()) self.assertTrue(Path(pkg.INSTALL_ROOT, 'HardeningKitty.psm1').is_file()) self.assertTrue(Path(pkg.INSTALL_ROOT).is_dir()) self.assertTrue(Path(pkg.REPORT_ROOT).is_dir()) finally: pkg._package_source, pkg.INSTALL_ROOT, pkg.REPORT_ROOT = prior def test_installer_refuses_to_replace_foreign_existing_directory(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) source = make_source(root / 'package') (source / 'tools').mkdir(exist_ok=True) shutil.copy2(ROOT / 'tools' / 'vendor_assets.py', source / 'tools' / 'vendor_assets.py') shutil.copy2(ROOT / 'tools' / 'package_verify.py', source / 'tools' / 'package_verify.py') verify.write_source_manifest(source) old = (pkg._package_source, pkg.INSTALL_ROOT, pkg.REPORT_ROOT) try: pkg._package_source = lambda: str(source) pkg.INSTALL_ROOT = str(root / 'programdata' / 'engine') pkg.REPORT_ROOT = str(root / 'programdata' / 'reports') Path(pkg.INSTALL_ROOT).mkdir(parents=True) foreign = Path(pkg.INSTALL_ROOT, 'foreign.txt') foreign.write_text('do not delete', encoding='utf-8') with self.assertRaisesRegex(RuntimeError, 'not owned'): pkg.install() self.assertEqual(foreign.read_text(encoding='utf-8'), 'do not delete') finally: pkg._package_source, pkg.INSTALL_ROOT, pkg.REPORT_ROOT = old def test_structured_audit_publishes_one_native_json_key(self): events = [] class FakeWapt: def write_audit_data_if_changed(self, section, key, value, **kwargs): events.append((section, key, value, kwargs)) def write_audit_data(self, section, key, value, **kwargs): events.append((section, key, value, kwargs)) old = getattr(pkg, 'WAPT', None) try: pkg.WAPT = FakeWapt() document = {'schema_version': 2, 'status': 'WARNING', 'controls': {'42': {'status': 'failed'}}} pkg._publish_audit(document, {'publish_to_wapt': True}) self.assertEqual(len(events), 1) section, key, value, kwargs = events[0] self.assertEqual((section, key), ('hardeningkitty', 'hardeningkitty')) self.assertIs(value, document) self.assertEqual(kwargs['max_count'], 2) finally: if old is None: del pkg.WAPT else: pkg.WAPT = old def test_console_template_matches_schema_and_uses_escaped_tags(self): template = (ROOT / 'console-templates' / 'host_audit_hardeningkitty_hardeningkitty.html').read_text(encoding='utf-8') self.assertIn('{{#Items categories}}', template) self.assertIn('{{#Items controls}}', template) self.assertIn('{{error.code}}', template) self.assertIn('{{error.description}}', template) self.assertIn('{{summary.expected}}', template) self.assertNotIn('{{{', template) self.assertNotIn('