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