Fixes found running WIFICANARY on a WiFi satellite node that pushes its
detections to a hub with no WiFi of its own.
Synced plugin objects were stored with syncHubNodeName = "decoded"
instead of the node's name. plugin.py took filename.split(".")[2], but by
the time the hub processes the file decode_and_rename_files() has renamed
it to last_result.<PREFIX>.decoded.<NodeName>.<N>.log, so index 2 is the
literal marker. The parsing now lives in
utils.plugin_utils.get_sync_hub_node_name(), next to the function that
produces these names; it keeps node names that contain dots and returns
"" for local result files.
WIFICANARY's absent_baseline_clone was evaluated per trusted_aps entry,
so with a main AP + range extender pair, a weak clone seen while only the
main AP was in range also raised absent_baseline_clone through the
extender's entry, on top of the correct evil_twin. The baseline now
counts as present if any trusted radio for that SSID is visible;
single-entry behaviour is unchanged.
Leaving WIFICANARY_IFACE blank is how such a hub ingests the node's
detections (core processes the synced files after the plugin runs), but
it logged an unconditional "not set - nothing to scan" every cycle. It is
now a verbose "hub-only mode" message, and the node + hub setup is
documented in the setting description and the plugin README.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PzCQWQVz9b2UzqLiGp2mPM
check_trusted_aps() evaluated a rogue AP once per trusted_aps entry sharing
its SSID, so the documented main-AP+extender pattern (same SSID, two
entries) produced duplicate (bssid, motor) rows for a real clone - a
problem once next_release's per-plugin identity-hash dedup guard lands in
main, since it drops a plugin's entire batch on any internal duplicate.
Fixed by deduping once in main() after collecting from all check_*
functions. Also added iw + its setcap to Dockerfile.debian and
.devcontainer/Dockerfile, which the original PR missed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011meLPKCzVpdZyAUfv5U6mm
- check_trusted_aps() was evaluating every AP sharing a protected SSID
against every trusted entry for that SSID, not just its own. A main AP
requiring a stricter accepted security set (e.g. wpa3-only) than a
separately-trusted extender (e.g. wpa2) caused the extender to be
flagged as evil_twin/absent_baseline_clone - it was being judged
against the main AP's accepted set instead of its own. Fixed by
excluding, from each trusted entry's evaluation, any BSSID that has its
own separate trusted entry for the same SSID.
- README's "iw isn't in the published image yet" section was already
stale within the same PR - this branch's own Dockerfile change adds
iw + setcap, so the image ships it. Replaced with one sentence.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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