mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-10-02 10:45:05 -04:00
Add WIFICANARY plugin - passive WiFi rogue-AP detection
Periodic iw-scan-based detection of the 6 heuristics that don't need monitor-mode hardware (see issue #1789): pwnagotchi/Pineapple signatures, evil-twin/open clones, baseline-AP-absent-with-clone, security downgrades, and duplicate-SSID/different-vendor - all evaluated against a user-curated trusted-AP baseline (WIFICANARY_trusted_aps). A detection creates a flagged Devices entry even for BSSIDs that never associate, per the addendum on the same issue. - WIFICANARY_TRUSTED_SECURITY is multi-select: an observed encryption exactly matching any selected value is accepted; otherwise it's flagged if weaker than the strongest selected value (deliberate - comparing against the weakest would make multi-select pointless, since anything at/above the weakest would silently pass regardless of the rest of the selection). - Added a "known device turned rogue" motor: escalate_known_devices() cross-references each detection's BSSID against the Devices table via the new DeviceInstance.getAllByMacs(). This covers the BSSID-identity half of the issue #1789 addendum's motor 10; the deauth/probe-source-MAC half still needs monitor-mode data this plugin doesn't have. - Vendor is deliberately not looked up by this plugin - any device it creates gets devVendor filled in for free by core's own vendor_update plugin on its next pass. 43 wificanary unit tests + 10 DeviceInstance.getAllByMacs() tests, all test_plugin_conventions.py checks pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011meLPKCzVpdZyAUfv5U6mm
This commit is contained in:
1 parent
cd1d0ed11e
commit
d0a3a5416b
7 files changed
+2129
-1
No files matched your search
@@ -3,6 +3,8 @@ Unit tests for server/models/device_instance.py's DeviceInstance model methods.
|
||||
|
||||
Covers:
|
||||
- DeviceInstance.getAllByName()
|
||||
- DeviceInstance.getByMac()
|
||||
- DeviceInstance.getAllByMacs()
|
||||
"""
|
||||
|
||||
import sys
|
||||
@@ -92,5 +94,53 @@ class TestGetByMac(unittest.TestCase):
|
||||
self.assertIsNone(inst.getByMac("00:00:00:00:00:00"))
|
||||
|
||||
|
||||
class TestGetAllByMacs(unittest.TestCase):
|
||||
"""One query for a batch of MACs - added for callers (e.g. WIFICANARY's
|
||||
known-device-turned-rogue check) that would otherwise call getByMac()
|
||||
once per item in a loop, one DB round-trip each."""
|
||||
|
||||
def setUp(self):
|
||||
self.conn = make_db()
|
||||
insert_device_from_dict(self.conn, make_device_dict("aa:bb:cc:dd:ee:01", devName="host-1"))
|
||||
insert_device_from_dict(self.conn, make_device_dict("aa:bb:cc:dd:ee:02", devName="host-2"))
|
||||
self.conn.commit()
|
||||
|
||||
def _instance(self):
|
||||
from models.device_instance import DeviceInstance
|
||||
inst = DeviceInstance()
|
||||
|
||||
def _fetchall(q, p=()):
|
||||
rows = self.conn.execute(q, p).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
inst._fetchall = _fetchall
|
||||
return inst
|
||||
|
||||
def test_returns_dict_keyed_by_lowercased_mac(self):
|
||||
inst = self._instance()
|
||||
result = inst.getAllByMacs(["AA:BB:CC:DD:EE:01", "aa:bb:cc:dd:ee:02"])
|
||||
self.assertEqual(set(result.keys()), {"aa:bb:cc:dd:ee:01", "aa:bb:cc:dd:ee:02"})
|
||||
self.assertEqual(result["aa:bb:cc:dd:ee:01"]["devName"], "host-1")
|
||||
|
||||
def test_unmatched_mac_simply_absent_from_result(self):
|
||||
inst = self._instance()
|
||||
result = inst.getAllByMacs(["aa:bb:cc:dd:ee:01", "00:00:00:00:00:00"])
|
||||
self.assertEqual(set(result.keys()), {"aa:bb:cc:dd:ee:01"})
|
||||
|
||||
def test_duplicate_macs_collapsed_to_one_query_param(self):
|
||||
inst = self._instance()
|
||||
result = inst.getAllByMacs(["aa:bb:cc:dd:ee:01", "aa:bb:cc:dd:ee:01"])
|
||||
self.assertEqual(set(result.keys()), {"aa:bb:cc:dd:ee:01"})
|
||||
|
||||
def test_empty_input_returns_empty_dict_without_querying(self):
|
||||
inst = self._instance()
|
||||
inst._fetchall = lambda q, p=(): (_ for _ in ()).throw(AssertionError("should not query"))
|
||||
self.assertEqual(inst.getAllByMacs([]), {})
|
||||
|
||||
def test_blank_entries_are_filtered_out(self):
|
||||
inst = self._instance()
|
||||
result = inst.getAllByMacs(["aa:bb:cc:dd:ee:01", "", None])
|
||||
self.assertEqual(set(result.keys()), {"aa:bb:cc:dd:ee:01"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user