import tempfile import unittest from pathlib import Path from unittest.mock import patch from rpcscope.catalog import CHECKS from rpcscope.runner import ScanConfig, _msf_command, _redacted, scan class RunnerTests(unittest.TestCase): def test_nonfinite_and_invalid_api_limits_are_rejected_before_collection(self): for attribute, value in (("timeout", float("nan")), ("timeout", float("inf")), ("jobs", True), ("discovery_workers", 1.5), ("connect_timeout", float("nan")), ("connect_timeout", True)): with self.subTest(attribute=attribute, value=value), patch("rpcscope.runner.doctor") as inspect: config = ScanConfig(["192.0.2.1"]) setattr(config, attribute, value) with self.assertRaises(ValueError): scan(config) inspect.assert_not_called() def test_redacts_embedded_password(self): command = ["rpcdump", "user:supersecret@10.10.10.25"] self.assertEqual( _redacted(command, "supersecret"), ["rpcdump", "user:@10.10.10.25"], ) def test_dry_run_does_not_require_installed_collectors(self): with tempfile.TemporaryDirectory() as directory: config = ScanConfig( targets=["10.10.10.0/24"], username="", password="", shares=True, dry_run=True, out_dir=Path(directory), ) self.assertEqual(scan(config), {}) self.assertEqual(config.commands[0][0:3], ["nxc", "smb", "10.10.10.0/24"]) def test_msf_escaped_credentials_are_never_recorded(self): config = ScanConfig(targets=["192.0.2.1"], username="auditor", password='a\\b"c') command = _msf_command("msfconsole", "192.0.2.1", config) redacted = _redacted(command, config.password) self.assertEqual(redacted[-1], "") self.assertNotIn(config.password, " ".join(redacted)) def test_discovery_interruption_retains_partial_hosts_and_skips_checks(self): with tempfile.TemporaryDirectory() as temporary: config = ScanConfig(["192.0.2.0/24"], out_dir=Path(temporary) / "out", checks=[CHECKS["ms17-010"]]) tools = {"nxc": "nxc", "enum4linux-ng": None, "rpcdump": None, "msfconsole": None} def interrupted(command, timeout, log, password, cancel): cancel.set() return 130, "SMB 192.0.2.1 445 HOST [*] Windows 11 (name:HOST) (domain:LAB)" with patch("rpcscope.runner.doctor", return_value=tools), patch("rpcscope.runner._run", side_effect=interrupted) as run: hosts = scan(config) self.assertTrue(config.cancelled) self.assertIn("192.0.2.1", hosts) self.assertEqual(hosts["192.0.2.1"].checks["ms17-010"], "skipped-cancelled") self.assertEqual(run.call_count, 1) if __name__ == "__main__": unittest.main()