Files
NetAlertX/server/plugins/wificanary
Mauricio CamayoandClaude Sonnet 5 d0a3a5416b 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
2026-09-23 15:56:47 -05:00
..

Overview

Runs a periodic passive WiFi scan (iw scan, no monitor mode) and flags rogue APs against a baseline you define: pwnagotchi/WiFi Pineapple signatures, evil-twin/open clones of a protected SSID, a protected AP going missing while a clone is visible, security downgrades, and a protected SSID suddenly broadcast from an unexpected vendor. Originated from issue #1789, which also covers why deauth/probe-flood/beacon-flood detection is intentionally not included here - those need real monitor-mode frame capture, not a scan snapshot. For that, pair this plugin with a dedicated monitor-mode tool such as ESP32 WiFi Canary.

Requirements

  • A WiFi interface reachable from the NetAlertX host, in station mode (monitor mode is not required - a normal onboard or USB WiFi adapter is enough). If your NetAlertX host has no WiFi hardware, this plugin has nothing to scan with.
  • The container needs iw installed and enough privilege to run sudo iw dev <iface> scan (same style of requirement as ARPSCAN's sudo arp-scan) - host networking and NET_ADMIN, typically.

iw isn't in the published image yet

The official NetAlertX image doesn't ship iw (it has arp-scan/nmap/etc., but nothing WiFi-specific), so WIFICANARY_IFACE will fail to scan until it's added. Same fix Dockerfile already applies to arp-scan/nmap/nbtscan/traceroute: install the package, then setcap it so it works for the non-root runtime user without needing real sudo (this hardened image's sudo is a no-op passthrough, not a privilege escalation - see the Dockerfile's final stage).

# In the apk add line that already installs arp-scan/nmap/nbtscan/etc.:
RUN apk add --no-cache ... iw ...

# Alongside the existing setcap lines for nmap/arp-scan/nbtscan/traceroute:
    setcap cap_net_raw,cap_net_admin+eip /usr/sbin/iw && \

Until this lands in the published ghcr.io/jokob-sk/netalertx image, build your own from this repo's Dockerfile (docker compose build) rather than pulling the tag - pulling the published image will have WIFICANARY_IFACE scans fail with "command not found."

Usage

  • Set WIFICANARY_IFACE to your wireless interface (e.g. wlan0).
  • Add each network you want protected to WIFICANARY_trusted_aps - SSID, optionally its BSSID (recommended: without a BSSID, the evil-twin/absent-baseline checks fall back to matching on SSID alone), and every encryption you'd accept from it (select more than one for a WPA2/WPA3-transition-mode AP).
  • Have a range extender or mesh node broadcasting the same SSID as your main AP? Add it as its own WIFICANARY_trusted_aps entry (same SSID, its own BSSID/security) rather than leaving it out - a real extender is very often a different vendor/OUI than the main router, and every trusted BSSID's OUI for a given SSID is treated as legitimate, not just the first one.
  • Enable the plugin (WIFICANARY_RUN → schedule) and set a schedule in WIFICANARY_RUN_SCHD.
  • A detection creates a new, dangerous-by-default Devices entry for the rogue BSSID (even though it never associated with your network) - turn off WIFICANARY_IMPORT_ON if you'd rather tune your trusted-AP list against the plugin's history first, without devices being created yet.
  • Pwnagotchi and WiFi Pineapple signature checks run unconditionally, regardless of WIFICANARY_trusted_aps.
  • If a detected rogue BSSID turns out to already be a device NetAlertX knows from another source (ARP, DHCP, an importer...), the finding is escalated in place - the reason is rewritten to name the known device, and the motor gets a _known_device suffix (e.g. evil_twin_known_device) so a Workflow rule can route it to a more urgent channel than a stranger's radio.

Notes

  • Vendor names for a rogue device do show up in the GUI, but not from this plugin - a Devices row it creates gets its Vendor field filled in by core's own VNDRPDT (vendor_update) plugin on its next run, same as any other device. That lookup is a local OUI-database match, not a network call, so it's deliberately kept out of the scan step itself.

  • The duplicate-SSID/different-vendor check only looks at SSIDs you've listed in WIFICANARY_trusted_aps - an untracked network's own AP diversity (e.g. a cafe chain) is never flagged. For a tracked SSID, every explicitly-trusted BSSID's OUI is whitelisted (see the range-extender note above) - only an OUI that matches none of them gets flagged. "Vendor" here means OUI (BSSID's first 3 octets) compared directly between the APs sharing an SSID, not a vendor-name lookup.

  • WIFICANARY_TRUSTED_SECURITY is multi-select. An observed encryption exactly matching any selected value is always accepted; otherwise it's flagged if it's weaker than the strongest value you selected - deliberately, not a typo: comparing against the weakest would make selecting more than one value pointless (anything at or above the weakest would silently pass either way, making the rest of the selection meaningless). Worked example for wep + wpa2 selected:

    Observed Result
    wep OK (listed)
    wpa2 OK (listed)
    wpa Alert - not listed, and weaker than wpa2
    open Alert - weaker than everything

    Select open here only for a network you intend to run unencrypted on purpose (e.g. a guest SSID) - otherwise leave it out so an unexpected open clone or downgrade still trips an alert.

  • Encryption is classified from the iw scan IEs into open / wep / wpa / wpa2 / wpa3. A Privacy-flagged AP with neither an RSN nor a WPA information element is reported as wep - the closest reasonable guess for that combination, not a certainty.

  • See the WIFICANARY addendum on issue #1789 for the reasoning behind creating a device for never-associated attacker BSSIDs, and for the "known device turned rogue" idea. The implemented version above only covers the BSSID-identity angle (is the radio itself a device you already trust?) - the addendum's original, richer version (cross-referencing the source MAC of attack traffic like deauth/probe floods) still needs monitor-mode data this plugin doesn't have.

  • Author: mauricio-camayo