import unittest from rpcscope.models import HostEvidence, RpcInterface, Share from rpcscope.rules import evaluate_host class RuleTests(unittest.TestCase): def test_rules_separate_exposure_from_confirmed_vulnerability(self): host = HostEvidence( address="10.10.10.25", smb1=True, signing=False, null_session=True, shares=[Share("Public", ["READ", "WRITE"])], rpc_interfaces=[RpcInterface("12345678-1234-abcd-ef00-0123456789ab", "1.0")], ) findings = evaluate_host(host) ids = {finding.rule_id for finding in findings} self.assertTrue({"SMB-001", "SMB-002", "SMB-003", "SMB-005"} <= ids) rpc = next(finding for finding in findings if finding.rule_id.startswith("RPC-")) self.assertIn("does not prove", rpc.impact) self.assertNotIn("MSF-MS17-010", ids) def test_msf_result_is_explicit(self): host = HostEvidence(address="10.10.10.25", checks={"ms17-010": "vulnerable"}) self.assertTrue(any(f.rule_id == "CHECK-MS17-010" for f in evaluate_host(host))) def test_catalog_check_becomes_detailed_finding(self): host = HostEvidence(address="10.10.10.25", checks={"zerologon": "vulnerable"}) finding = next(f for f in evaluate_host(host) if f.rule_id == "CHECK-ZEROLOGON") self.assertEqual(finding.severity, "critical") self.assertIn("CVE-2020-1472", finding.title) def test_rpc_registration_does_not_claim_remote_reachability(self): host = HostEvidence("10.10.10.25", rpc_interfaces=[ RpcInterface("12345678-1234-abcd-ef00-0123456789ab", "1.0", endpoint="ncalrpc:[local-spooler]") ]) finding = evaluate_host(host)[0] self.assertEqual(finding.severity, "info") self.assertIn("local bindings only", finding.title) self.assertIn("reachability untested", finding.evidence) def test_rpc_report_preserves_remote_binding_after_local_binding(self): host = HostEvidence("10.10.10.25", rpc_interfaces=[ RpcInterface("12345678-1234-abcd-ef00-0123456789ab", "1.0", bindings=["ncalrpc:[local-spooler]", "ncacn_ip_tcp:10.10.10.25[49665]"]) ]) finding = evaluate_host(host)[0] self.assertEqual(finding.severity, "medium") self.assertIn("ncacn_ip_tcp", finding.evidence) self.assertNotIn("local bindings only", finding.title) def test_legacy_fingerprint_uses_version_boundaries(self): for os in ("Windows 11", "Windows Server 2019", "Windows 80", "Samba 4.20.1"): with self.subTest(os=os): self.assertFalse(any(f.rule_id == "SMB-007" for f in evaluate_host( HostEvidence("10.10.10.25", os=os) ))) for os in ("Windows 7 Build 7601", "Windows 8.1", "Windows Server 2008 R2", "Samba 3.6.1"): with self.subTest(os=os): self.assertTrue(any(f.rule_id == "SMB-007" for f in evaluate_host( HostEvidence("10.10.10.25", os=os) ))) def test_acl_write_capability_does_not_claim_direct_write(self): host = HostEvidence("10.10.10.25", shares=[Share("Data", ["READ", "WRITE (ACL)"])]) finding = evaluate_host(host)[0] self.assertEqual(finding.rule_id, "SMB-005") self.assertIn("permission-control", finding.title) self.assertIn("WRITE (ACL)", finding.evidence) def test_suspected_checker_indicator_is_preserved_with_low_confidence(self): host = HostEvidence("10.10.10.25", checks={"smbghost": "suspected"}, check_details={"smbghost": "SMB 3.1.1 compression capability observed"}) finding = evaluate_host(host)[0] self.assertEqual(finding.confidence, "low") self.assertIn("requires validation", finding.title) self.assertEqual(finding.evidence, host.check_details["smbghost"]) self.assertIn("does not distinguish patched", finding.impact) def test_checker_positive_includes_actual_evidence(self): host = HostEvidence("10.10.10.25", checks={"ms17-010": "vulnerable"}, check_details={"ms17-010": "positive checker marker from raw artifact"}) finding = evaluate_host(host)[0] self.assertEqual(finding.confidence, "high") self.assertEqual(finding.evidence, host.check_details["ms17-010"]) def test_rpc_evidence_preserves_version_to_binding_mapping(self): host = HostEvidence("10.10.10.25", rpc_interfaces=[ RpcInterface("12345678-1234-abcd-ef00-0123456789ab", "1.0", endpoint="ncalrpc:[local-spooler]"), RpcInterface("12345678-1234-abcd-ef00-0123456789ab", "2.0", endpoint="ncacn_ip_tcp:10.10.10.25[49665]"), ]) finding = evaluate_host(host)[0] self.assertIn("v1.0 advertised via ncalrpc:[local-spooler]", finding.evidence) self.assertIn("v2.0 advertised via ncacn_ip_tcp:10.10.10.25[49665]", finding.evidence) if __name__ == "__main__": unittest.main()