Companion C2: Command Center
A self-contained wireless detection node. Runs standalone - no server, no cloud, no subscription.
Featured in Seeed Studio Best 20 XIAO Projects in 2025.
Most WiFi and BLE detection tools need a laptop and a backend. AntiHunter runs on the device, and you drive one node two ways with no extra infrastructure:
- Web UI - the node hosts its own WiFi access point. Connect a phone or laptop, run scans and read results in the browser.
- Meshtastic radio - send commands and receive detections over LoRa from a paired Meshtastic radio, with no WiFi AP. The Headless firmware runs this way with no web UI at all.
- To cover a larger area, add nodes. They share detections over the same mesh and can report to the optional Command Center.
At a glance
- ESP32-S3 · WiFi + BLE scanning · GPS · SD logging · vibration sensing · LoRa mesh
- Drop-in ESP32-C5 build adds 5 GHz alongside 2.4 GHz (testing)
- Full firmware: standalone web UI over the node's own AP · Headless firmware: serial + mesh only, for silent deployments
- Sentinel counterintel engine (Beta branch only)
- Vibration & attack triggered actions for set and forget operation
Start Here - setup order, decisions, symptom table, and every guide, manual and script.
- Quick Start
- What It Detects
- Use Cases
- Hardware
- Build & Flash
- Configuration & Operations
- System Architecture
- Mesh Networking
- Mesh Commands
- API Reference
- Acknowledgments
- Legal
AntiHunter is distributed WiFi and BLE intelligence & attack detection. Controlled from its own WiFi, or using Meshtastic radio commands.
Vibration based scans and self-destruct option. Defensive by design. Knows the devices and networks around you, alerts when it matters. Integrates as far or close as you choose.
Flash it from your browser.
Visit the docs folder for additional user resources and troubleshooting.
Built or bought a node/kit? The Operator's Guide takes you from unboxing to deployment: antennas, flashing, mesh setup, every detector, the vibration sensor, Command Center install and a printable quick-reference card.
- Open the Web Flasher in Chrome or Edge, on desktop.
- Pick Full (web UI) or Headless (serial + mesh)
- Choose a Release Channel (Stable or Beta)
- Plug in your ESP32-S3, and click Connect & Flash.
- First boot:
- Full firmware - connect to the
AntihunterWiFi AP (passwordantihunt3r123), open http://192.168.4.1. Change the AP credentials under RF Settings first. - Headless firmware - configure then drive it over serial or mesh commands.
- Full firmware - connect to the
- Add a watchlist entry or start a scan.
To flash from a terminal or build from source, see Build & Flash.
| Feature | What it does | Scan modes |
|---|---|---|
| Target Scan | MAC/OUI/SSID watchlist with instant mesh alerts | WiFi, BLE, or both |
| Device Scanner | Captures all nearby WiFi and BLE devices with RSSI, channels, names | WiFi, BLE, or both |
| Probe Request Scanner | Passive sniffer -- reveals SSIDs devices are searching for | WiFi, BLE, or both |
| Ghost SSID Detection | Flags probed SSIDs with no responding AP nearby | Probe / Device scan |
| Baseline Anomaly Detection | Learn-then-alert: spots new, missing, and changed devices | WiFi + BLE |
| MAC Randomization Correlation | Links randomized MACs to persistent identities via behavioral signatures | WiFi + BLE |
| Deauth Attack Detection | Real-time deauth/disassoc frame detection with source tracking | WiFi promiscuous |
| Sentinel Counterintel (Beta only) | Passive detection of attacker-tool activity (deauth/beacon/auth/assoc floods, SAE DoS, karma, evil-twin, probe floods, handshake capture); per-detector toggles, mesh broadcast, and optional persistent start-on-boot | WiFi promiscuous |
| Drone RID Detection | Identifies drones broadcasting Remote ID (ODID/ASTM F3411, French ID); Serial + CAA | WiFi beacon/NAN + BLE (BT4/BT5) |
| Triangulation | Multi-node RSSI-based location estimation via mesh (experimental) | WiFi, BLE |
| Mesh Networking | LoRa mesh via Meshtastic -- alerts, remote commands, coordination | UART serial |
| Secure Data Destruction | Tamper-triggered or remote wipe with post-wipe obfuscation | Vibration / mesh |
| Vibration Trigger | Choose a scan to run when vibration detected | Vibration / mesh |
| Privacy Mode | One-click MAC/GPS/SSID redaction for screenshots | Web UI button |
| Battery Saver | 80MHz CPU, light sleep, reduced GPS, mesh heartbeat only | Mesh command |
| Allowlist | Global device allowlist -- ignored across all scan modes | Web UI / API |
| Data Explorer | Review findings, device logs and scan data | Web UI / API |
| Accent Colors | Recolor the destructive controls and Sentinel banners | Web UI, System tab |
Target Scan
Maintain a watchlist of MAC addresses (full or OUI prefix), SSIDs, or identity IDs (T-XXXX). Scans WiFi channels and BLE frequencies, alerting on detection via web UI, mesh, and command center.
- WiFi-only, BLE-only, or combined scanning
- Global allowlist filters out known devices
- Logs RSSI, channel, GPS, and device names to SD
- Real-time alerts over mesh network
Device Scanner
- Captures all WiFi and BLE devices in range: MACs, SSIDs, signal strength, names, and channels.
- WiFi AP discovery runs a periodic all-channel scan (gated by WiFi Scan Interval); target frames are captured passively in promiscuous mode while hopping channels between scans.
- Check Capture Probes to piggyback probe-request collection onto the scan, feeding the probe database (MAC, vendor, RSSI, SSIDs, randomization status).
Probe Request Scanner
Correlates all three 802.11 address fields to detect ghost SSIDs (networks that exist only in a device's history), identify which APs responded, and catch silent devices via destination-address matching.
- Three-field correlation: probe requests (addr2=source), probe responses (addr1=client, addr2=AP, addr3=BSSID), and destination-address matching all feed one per-device record
- Destination address (addr1) matching: detects probe requests addressed TO a target MAC -- catches silent or sleeping devices that never transmit their own identity
- Ghost SSID detection: cross-references probes against responses to flag SSIDs with no responding AP. Ghost SSIDs appear prefixed with
~(e.g.~"HomeNetwork"vs"CoffeeShop") and reveal networks the device connected to elsewhere -- location history, home/work networks, travel patterns - SSID watchlist: add SSIDs to the target list alongside MACs and OUIs
- OUI vendor identification (68-vendor table)
- MAC randomization detection (locally-administered bit check)
- Mesh alerting for watchlist hits (60s dedup cooldown)
- RSSI min/max/current tracking, up to 4 probed SSIDs per device
Packet Capture - records raw traffic to SD as a standard pcap.
- WiFi: full radiotap header with channel, rate and RSSI. Both bands on C5
- BLE: advertisements written as link-layer PDUs, so Wireshark dissects ADV_IND, ADV_DIRECT_IND and SCAN_RSP
- Sweeps the RF Settings channels, or a channel list and dwell under Advanced. Management-frames-only filter
- Captures list on the Scan tab: download, delete, delete-all. The recording file cannot be deleted
- Started by hand or by a Sentinel attack response.
auto_captures are pruned against a size budget and free-space floor; manual captures are not
Sentinel - Counterintel Engine - (Beta version only)
Enable and it runs in the background whenever you aren't scanning. Passive WiFi monitoring that flags attacker-tool activity by frame signatures plus behavioral fallbacks. Tuned and tested against both popular consumer ESP32 attack firmware and professional Linux tooling.
- Verified against: airgeddon, aireplay-ng, bettercap, wifite, mdk4, angryoxide, eaphammer, hostapd-mana, wifipumpkin3, hcxdumptool, purpose-built test scripts, and common consumer ESP32 attack firmware.
- Detectors are organized into toggleable groups. Each detection logs to serial + SD and broadcasts to mesh peers.
| Group | Detectors | How they're caught |
|---|---|---|
| DoS | Deauth flood, deauth forge, broadcast deauth, AP-targeted deauth, beacon flood, auth flood, assoc-sleep, SAE DoS | Fixed/rotated deauth seqCtrl + duration (reason codes are used for tool attribution, never on their own as an attack trigger - reasons 1/2/6/7 are all legitimate deauth causes), impersonation bursts, beacon-spam rate + static templates, open-system auth flood, assoc-req PM-bit floods, SAE commit floods (algo 3 / txn 1) |
| Rogue AP | Evil-twin, OWE abuse, Karma / MANA | Clone of our own AP (SSID/BSSID collision); OWE-transition downgrade; bait-probe answered by an AP that never beacons that SSID |
| Recon | PMKID harvest, probe flood, handshake capture | Orphaned-M1 / KDE PMKID solicitation; fixed-seq + behavioral probe spam (≥15 MACs/SSID/5s); forced & passive EAPOL M1–M4 capture |
| Physical | FragAttacks, TSF / multi-channel twin, WiFi interference | A-MSDU PN reuse / mixed-key frags; same BSSID on ≥2 channels within 5s; per-channel PDR-vs-RSSI collapse (CRC-fail flood) |
| Mesh disruption | Self-spoof, channel flood, command audit | Own node-id seen inbound; inbound rate DoS; every privileged mesh command logged with the radio id that issued it - a provenance audit trail, not an alert (injection is indistinguishable from legit ops on a shared channel, so we record the source instead of guessing) |
- Field-verified on hardware (confirmed firing against the live tools above): deauth (flood/forge/AP-targeted), beacon flood, auth flood, assoc-sleep, SAE DoS, karma, evil-twin, probe flood, handshake capture.
- Experimental: OWE abuse, PMKID harvest, FragAttacks, TSF multi-channel twin, WiFi interference, mesh disruption.
- Behavioral fallbacks (survive template changes): SSID-rotate forge, behavioral probe-flood, EAPOL-capture bait, broadcast-deauth-while-beaconing.
- Hotspot false-positive suppression: the crypto/handshake detectors (PMKID, KRACK, handshake capture, SAE-DoS) and all beacon-based detectors (evil-twin, OWE, SSID-confusion, TSF, beacon-flood) skip locally-administered / randomized BSSIDs. Phone hotspots and MAC-randomizing devices produce normal handshakes, SAE retries and M3 retransmits that would otherwise trip these detectors as attacks. Volume-based DoS detectors (deauth/auth/assoc floods, probe-flood) intentionally do not skip them, since real floods commonly spoof randomized sources.
- Outputs:
[DETECT]serial lines + per-detector SD.jsonl+ mesh broadcast to peer nodes for quorum confirmation. - AP clients: stations that associate to the node's own AP, with MAC, association count and first/last-seen age. AP Clients panel,
GET /api/apclients.json. - Mesh command audit: every inbound mesh line beginning
@,TRIANGULATEorTRI_, logged with the sender id, truncated to 96 characters. Captured before addressing is resolved, so commands aimed at other nodes are included. Mesh Commands panel,GET /api/mesh_cmd.jsonl, SD/mesh_cmd.jsonl. - Control & boot: Start/stop from the Sentinel tab. Off at boot by default; opt into a persistent Start-on-Boot setting via the Web Flasher / Configurator /
SENTINEL_BOOTmesh command - when enabled it auto-starts at power-on and survives reboot. - Attack response: pick what runs on a confirmed attack with a source MAC - triangulate, packet capture, device discovery, probe sweep, drone RID, each with its own duration. Only one can hold the radio, so several run in that order one at a time. Detection pauses while each runs.
The mesh labels Sentinel emits, for log parsers and C2, are listed under Mesh Commands → Sentinel label reference.
Deauth Attack Detection - standalone WiFi deauth/disassoc frame sniffer with real-time detection. Integrates with randomization tracking for source identification.
Drone RID Detection - detects drones broadcasting Remote ID per FAA/EASA standards over WiFi and Bluetooth. Supports ODID/ASTM F3411 over WiFi (NAN action frames, beacon frames) and BLE (BT4 legacy + BT5 long-range advertising, service UUID 0xFFFA), plus French drone ID (OUI 0x6a5c35). Decodes all ODID message types (Basic ID, Location, System, Operator ID, Auth, Self-ID), preferring Serial Number over CAA Registration ID. Extracts UAV ID, pilot location, and flight telemetry. Mesh alerts and SD logging.
Baseline Anomaly Detection
Two-phase scan: establish a baseline of known devices, then monitor for anomalies -- new devices, disappearances, reappearances, and significant RSSI changes. Persistent storage survives reboots.
- RAM cache: 200-500 devices, SD overflow: 1K-100K device db
- Automatic tiering between RAM and SD
Important
A longer initial scan produces a more reliable baseline.
MAC Randomization Correlation (beta) - links randomized MAC addresses to persistent device identities using behavioral signatures: IE fingerprinting, channel sequencing, timing, RSSI patterns, and sequence-number correlation. Assigns identity IDs (T-XXXX) with SD persistence.
- Up to 256 simultaneous identities, 128 linked MACs each (LRU eviction of oldest identity at cap; stale tracks pruned every 60s)
- Dual signature support (full and minimal IE patterns)
- Confidence-based linking with adaptive thresholds
- Detects global MAC leaks and WiFi-BLE correlation
Tip
Use the Privacy button to redact MACs, GPS, and SSIDs before sharing screenshots.
Triangulation (experimental) - multiple nodes scan for a target simultaneously. Each records RSSI and GPS coordinates. Data is aggregated over mesh for weighted trilateration with Kalman filtering.
- Outputs: GPS coordinates, confidence, estimated uncertainty (m), average HDOP
- Google Maps link sent over mesh
- Per-target distance tuning multipliers (0.1x - 5.0x)
Note
Target RSSI greater than -80 produces better results for BLE devices.
RF Environment Calibration
Path loss model: distance = 10^((RSSI0 - RSSI) / (10 * n))
| Environment | WiFi n | BLE n | WiFi RSSI0 | BLE RSSI0 | Use Case |
|---|---|---|---|---|---|
| Open Sky | 2.0 | 2.0 | -23 dBm | -60 dBm | Clear LOS, minimal obstruction |
| Suburban | 2.7 | 2.5 | -24 dBm | -62 dBm | Light foliage, scattered buildings |
| Indoor | 3.2 | 2.9 | -25 dBm | -65 dBm | Typical indoor, some walls |
| Indoor Dense | 4.0 | 3.5 | -27 dBm | -69 dBm | Office spaces, many partitions |
| Industrial | 4.8 | 4.0 | -30 dBm | -73 dBm | Heavy obstruction, machinery |
- Perimeter security and intrusion detection
- Penetration testing and wireless security auditing
- Counter-UAV operations and airspace monitoring
- Surveillance detection and OPSEC audits
- Device fingerprinting across MAC randomization
- Probe analysis and rogue device detection
- Event security and monitoring
Buy a node from the store - bare PCB, soldered core, parts kit or assembled (what each tier ships) - or build your own from the parts below.
Important
Requires regulated 5V power supply. Unregulated battery sources cause voltage instability. A 2A fast-blow inline fuse on the battery line is optional added protection.
- Seeed XIAO ESP32-S3 (minimum 8MB flash), or XIAO ESP32-C5 for 2.4 + 5 GHz - drop-in on the same board, see the C5 page (testing)
- Meshtastic board: Heltec v3.2 (recommended) or T114. Alternatives in discussions.
- GPS, SDHC, vibration, and RTC modules
- Operator's Guide - unboxing, antennas, flashing, mesh setup, deployment
- Illustrated assembly manual
- PCB welcome letter
- BOM parts links & images
Bill of Materials
CORE COMPONENTS
- 1x DIGI PCB (82mm, 2-layer)
- 1x Seeed Studio XIAO ESP32-S3
- 1x Heltec WiFi LoRa 32 V3.2 (T114 also compatible, V3.2 preferred)
- 1x ATGM336H GPS Module
- 1x Micro SD SDHC TF Card Adapter Reader Module
- 1x SD Card (FAT32, 8GB shipped with every built tier; 32GB+ not recommended)
- 1x SW-420 Vibration Sensor
- 1x DS3231 Real Time Clock Module
CONNECTORS & FASTENERS
- 5x JST 2.54 2-Pin Terminals
- 10x M3 Mounting Inserts
- 4x M2 Mounting Inserts (power board)
- 2x M3x15mm Brass Standoffs
- 1x 1/4" Tripod Insert
- 2x JST Power Male Cable (switch, power board)
- 8x M3x4-6mm Flat Top Screws (for enclosure lids, max 6mm heads)
- 6x M3x4-6mm Screws (for PCB and front/rear covers)
- 4x M2x4-6mm Screws (for power board; or M3x4-6mm straight into the plastic without inserts)
- 2-4x M2.5 13-15mm Screws (for fan)
ANTENNA & CABLING
- 3x U.FL to SMA Pigtail Cable (SMA bulkhead, 10-20cm)
- 1x 6dBi Antenna 2.4GHz (WiFi/BLE)
- 1x 6dBi Antenna LoRa (region-dependent: 868MHz EU / 915MHz US / 923MHz Asia)
- 1x Active GPS Antenna (L1, SMA)
POWER & THERMAL
- 1x 30mm 5V Fan (7-10mm) - JST 2.54
- 1x 3-Pin Mini On/Off Switch
- 1x KSD9700 Normally Open Thermal Wire Sensor (30-40C)
- 1x Type-C Female Chassis Jack, waterproof (2-pin, 22 AWG leads, 14mm panel nut, dust cap)
- 1x Type-C 15W 3A 5V Fast Charge UPS Power Supply (2S 18650 Charger Module DC-DC Step Up Booster Converter, 88x41x22mm)
- 2x 18650 cells, protected flat-top (not supplied with any tier)
ENCLOSURE
- 1x Weatherproof Enclosure (3D printable)
- STL files: hw folder
- 1x TPU Seal Kit (housing, USB-C, GPS antenna)
FAN NOTES
- The sticker side is not always the exhaust side. Run the fan for a second and feel which way it blows before you screw it down. Assembled units ship with the fan set to exhaust.
- Shorting the two THERMO pins bypasses the thermal switch and runs the fan whenever the node is powered.
Pinout Reference
XIAO ESP32S3 Pin Diagram
Pin assignments may evolve. Verify compatibility with your board revision.
| Function | GPIO | Description |
|---|---|---|
| Vibration Sensor | 2 | SW-420 tamper detection (interrupt) |
| RTC SDA | 3 | DS3231 I2C data |
| RTC SCL | 6 | DS3231 I2C clock |
| GPS RX | 44 | NMEA data receive |
| GPS TX | 43 | GPS transmit (unused) |
| SD CS | 1 | SD card chip select |
| SD SCK | 7 | SPI clock |
| SD MISO | 8 | SPI MISO |
| SD MOSI | 9 | SPI MOSI |
| Mesh RX | 4 | Meshtastic UART receive |
| Mesh TX | 5 | Meshtastic UART transmit |
The Web Flasher is the simplest path. Use the options below to flash from a terminal or build from source.
No firmware is shipped on it: you flash AntiHunter yourself, for integrity and regulatory purposes.
Soldered Core PCB and Assembled tiers ship the Heltec radio on the latest stable Meshtastic, already configured for the node: serial module on, TEXTMSG at 115200 on the board's pins, screen blanks after 1s, status LED off, Bluetooth on with a shipped pairing pin. LoRa region is UNSET, so the radio receives but does not transmit until you set it, and it is on the public default channel - set the region, the pin and your own channel on every tier. Bare PCB and Parts Kit builds flash and configure the radio themselves.
| Tier | What ships | What you supply |
|---|---|---|
| Bare PCB (note) | One unpopulated 82mm board | Everything: source the BOM, solder per the assembly manual, flash and configure Meshtastic on the radio, fit a FAT32 SD card |
| Soldered Core PCB (note) | The core of the node - a fully populated PCB: XIAO ESP32-S3, Heltec LoRa radio, GPS, RTC, vibration sensor, SD reader, 8GB card fitted, radio flashed and serial-configured. Factory U.FL whip antennas only | Optional: Enclosure, regulated 5V power, external antennas |
| Parts Kit (note) | Every part on the BOM as loose components - PCB, modules, 8GB card, enclosure and TPU seals, 6dBi 2.4GHz and 6dBi LoRa antennas, U.FL→SMA pigtails and bulkheads, fan, thermal switch, power switch, waterproof USB-C panel jack, UPS board, fasteners. Nothing soldered, nothing flashed. No GPS antenna | Soldering and assembly per the manual, Meshtastic on the radio, 2x 18650 cells, active GPS antenna |
| Assembled (note) | Built, sealed and bench-tested. 8GB card fitted, GPS helix antenna, radio flashed and serial-configured | 2x 18650 cells |
Then, on every tier:
- Attach all three antennas before powering on - on soldered PCB tier: ceramic is GPS, the labelled 2.4GHz one is the ESP32, the third is LoRa on the Heltec. All other tiers use SMA antennas.
- Flash AntiHunter - web flasher (Chrome or Edge), the CLI installer, or PlatformIO. See below.
- Finish the radio - set your LoRa region, change the pairing pin, make your own encrypted channel primary and turn the public one off: Radio Setup. Bare PCB and Parts Kit builds flash Meshtastic and set the serial module here too.
- Set your node ID and AP password - web UI at
http://192.168.4.1, or over mesh.
curl -fsSL -o flashAntihunter.sh https://raw.githubusercontent.com/lukeswitz/AntiHunter/beta/Dist/flashAntihunter.sh
chmod +x flashAntihunter.sh
./flashAntihunter.shThe script first asks for a release channel (Stable or Beta), then Full or Headless. Stable pulls from main, Beta from beta.
Use -c to configure device parameters during flash, -e to erase flash first, -l to list available firmware.
Post-flash:
- Full firmware: Connect to
AntihunterWiFi AP (password:antihunt3r123), openhttp://192.168.4.1. Configure RF settings, detection modes, and change the AP credentials in RF Settings. - Headless firmware: Serial monitor or mesh commands only.
Prerequisites: PlatformIO, Git, USB cable. Optional: VS Code with PlatformIO extension.
git clone https://github.com/lukeswitz/AntiHunter.git
cd AntiHunterpio device list # List connected devices
pio run -e AntiHunter-full -t upload # Flash full firmware (web UI)
pio run -e AntiHunter-headless -t upload # Flash headless firmware
pio device monitor -e AntiHunter-full # Serial monitor
pio run -e AntiHunter-full -t erase -t upload # Clean flash (erase + upload)Build environments (same firmware sources; differ only in features/board):
AntiHunter-full-- Web UI/SoftAP dashboard (ESPAsyncWebServer + AsyncTCP);AntiHunter-headless-- serial + mesh only, no web deps.- ESP32-C5 (2.4 + 5 GHz, testing): envs
AntiHunter-c5-full/-c5-headlesson thefeat/c5branch -- see the ESP32-C5 page. - RadarNode (24GHz radar, experimental): env
RadarNode-c5-- see the RadarNode page.
Note
During the Web Flash process, choose "Erase Device" if upgrading from pre v0.9.2 firmware or to clear saved settings. Preferences are also saved and synced to/from SD storage; if corrupted, settings self-heal. The Web Flasher's Sentinel & Detectors section configures the full detection engine (Start-on-Boot, radio mode, every detector toggle, mesh flags, thresholds) - full parity with the web UI's Detectors tab. Anything left on Default keeps the firmware setting.
Configure via the web interface at http://192.168.4.1 or the API. All settings persist to NVS and SD.
| Preset | WiFi Chan Time | WiFi Scan Int | BLE Scan Int | BLE Scan Dur | RSSI Threshold | Use Case |
|---|---|---|---|---|---|---|
| Relaxed | 300ms | 5000ms | 6000ms | 3000ms | -80 dBm | Low power |
| Balanced | 160ms | 3000ms | 4000ms | 2000ms | -95 dBm | General use (default) |
| Aggressive | 110ms | 1500ms | 2000ms | 1000ms | -100 dBm | Fast detection, high coverage |
| Custom | User-defined | User-defined | User-defined | User-defined | User-defined | Fine-tuned |
Parameter Tuning
- WiFi Channel Time: Passive dwell per channel (50-300ms). This is the primary WiFi knob now - it must clear the ~100ms beacon interval to catch every AP on a channel; shorter = faster channel coverage but risks missing beacons.
- WiFi Scan Interval: No longer gates WiFi discovery (which is now continuous passive capture, not cyclic active scans). Retained in config/NVS for compatibility.
- BLE Scan Interval: Time between BLE cycles (1000-10000ms).
- BLE Scan Duration: Active scanning per cycle (1000-5000ms). Longer improves BLE discovery but keeps the shared radio on BLE longer, pausing WiFi channel-hopping.
WiFi and BLE share one 2.4 GHz radio and the scan loop is single-threaded: a BLE scan holds the radio for its full duration, during which WiFi promiscuous capture is off-air. So BLE Scan Duration is the fraction of each cycle WiFi is dark. The presets set BLE Scan Duration to half the BLE Scan Interval - an even 50/50 radio split.
- RSSI Threshold: Global signal filter (-100 to -10 dBm). Triangulation is exempt.
- WiFi Channels: Comma-separated (e.g. 1,6,11) or range (1..14). Default: 1,2,3,4,5,6,7,8,9,10,11 (US 2.4 GHz channels).
[!TIP] Lower intervals = faster detection, higher power. Higher intervals = reduced power, may miss brief transmissions.
Tamper detection and emergency data wiping.
- Auto-erase on tampering: Vibration-triggered destruction (disabled by default)
- Setup delay: Grace period after enabling for deployment
- Manual secure wipe: Via web interface
- Remote force erase: Mesh-commanded with token auth (5-min expiry, device-specific)
- Obfuscation: Plants a dummy IoT weather config after wipe
Warning: Data destruction is permanent and irreversible.
Auto-Erase Configuration
| Parameter | Range | Description |
|---|---|---|
| Setup delay | 30s - 10min | Grace period before auto-erase activates |
| Vibrations required | 2-5 | Movement count to trigger |
| Detection window | 10-60s | Time frame for vibration detection |
| Erase delay | 10-300s | Countdown before destruction |
| Cooldown period | 5-60min | Minimum time between tamper attempts |
Usage:
- Enable auto-erase via web interface with setup delay
- Configure thresholds for your environment
- Deploy and walk away during setup period
- Monitor mesh alerts for tamper events
- Remote erase:
@NODE ERASE_REQUESTto generate token, then@NODE ERASE_FORCE:<token>
- Privacy Mode - one-click redaction of MACs, GPS, and SSIDs for screenshots (web UI button).
- Battery Saver - stops WiFi/BLE scanning, drops CPU to 80MHz, enables light sleep, polls GPS once per minute; mesh UART stays active. Started via mesh command.
- Allowlist - global device allowlist, ignored across all scan modes (web UI / API).
- Accent Colors - sets the color of the destructive controls (STOP, the wipe buttons, Clear, error toasts, danger badges) and of the Sentinel banners; five choices each, across all three themes (web UI, System tab). Stored in the browser, not on the node.
Nodes function independently and coordinate via Meshtastic mesh networking.
Workflow: Detection -> Data collection (RSSI, GPS, timestamp) -> Mesh broadcast -> Command center aggregation
Node types. All of these share this PCB and mesh:
| Board | Sensor | Firmware | Status | |
|---|---|---|---|---|
| DIGI | ESP32-S3 | WiFi + BLE, 2.4 GHz | AntiHunter-full / -headless |
stable |
| DIGI C5 | ESP32-C5 | WiFi + BLE, 2.4 and 5 GHz | AntiHunter-c5-full / -c5-headless |
testing |
| RadarNode | ESP32-C5 | 24GHz radar, WiFi/BLE on trigger | RadarNode-c5 |
experimental |
The C5 is a drop-in replacement for the S3 on the same board - same pads, same peripherals, and it adds 5 GHz scanning. A RadarNode detects a moving target on radar, then sweeps WiFi and BLE to record which devices were present at that moment; it tags its STATUS reply with TYPE:RADAR, which the RadarNode UI and the Command Center use to type peers. Both are flashed from the web flasher under the Experimental channel, which asks you to acknowledge that these are test builds before it will flash.
AntiHunter Command Center: Aggregates data from all nodes with real-time mapping and visualization.
Same firmware, same detectors, same mesh commands, same scan engine. Full adds a WiFi AP hosting the web UI and API.
| Full | Headless | |
|---|---|---|
| Control | Web UI and API over its own AP, plus serial and mesh | Serial and mesh |
| WiFi discovery | Active all-channel sweep plus promiscuous capture on Device Scan, Target Scan, Baseline and Triangulation | Identical |
| Results | /results, rendered as cards in the browser |
Same text at /last_results.txt on SD |
| Device database | /devicedb.jsonl |
Not written |
| Fleet roster | Fleet tab and /api/mesh |
Not tracked |
| RF footprint | AP beacons continuously | Never beacons |
Karma bait is the only detector that transmits, a probe request every 8s. Off by default, enable with GROUP:rogue:on. Sentinel defend has no AP to pin to on Headless, so use scan.
Meshtastic LoRa mesh via UART for long-range distributed sensing. Optional - a single node runs fully standalone without it.
- Connection: TEXTMSG mode, 115200 baud. Pins:
10 RX / 9 TX(T114),19 RX / 20 TX(Heltec V3) - Protocol: Standard Meshtastic serial, public and encrypted channels
- Rate limiting: 3s intervals (configurable)
- Addressing:
@ALL COMMANDfor broadcast,@AH01 COMMANDfor a specific node. Node IDs: 2-5 alphanumeric chars. - Sender names: emoji/non-ASCII short names are stripped and still dispatch. A radio short-named the same as a node's own ID is dropped as a self-echo — keep them distinct.
Soldered Core PCB and Assembled tiers ship this already applied - serial on, TEXTMSG 115200 on the board's pins, screen 1s, LED off, BLE on with a shipped pin. Region and channel are still yours to set. Bare PCB and Parts Kit builds do all of it.
Flash the radio with stable Meshtastic, connect it on its own, then run scripts/meshtastic_config.py. One config group per call, each value read back afterwards.
options:
--port PORT serial port (auto-detected if omitted)
--board {heltec-v3,t114}
board type, sets the serial-module pins (default heltec-v3)
--screen on|off|SECS 'off' blanks after 1s, 'on' stays lit, or give seconds
--led {on,off} status LED heartbeat
--ble {on,off} Bluetooth on or off
--pin NNNNNN|none|random
BLE pairing: 6-digit fixed pin, 'none', or 'random'
--serial {on,off} AntiHunter serial module (TEXTMSG, 115200, board pins)
--region REGION LoRa region, e.g. US. UNSET means receive only
python3 scripts/meshtastic_config.py # print current settings
python3 scripts/meshtastic_config.py --serial on # node link, pins per --board
python3 scripts/meshtastic_config.py --region US # UNSET = RX only, no TX
python3 scripts/meshtastic_config.py --pin 481920 --screen off --led off
python3 scripts/meshtastic_config.py --board t114 --serial on --ble offNo flags on a terminal gives an interactive menu.
The same settings can be applied from the Meshtastic app or web client - Serial: enabled, TEXTMSG, 115200, pins per board.
Before deployment: set the region, change the BLE pairing pin, make your own encrypted channel primary and turn the public channel off in the Meshtastic app.
You do not need line of sight to every node. Nodes in between relay for you - 3 hops by default, 7 max.
A Meshtastic radio on its own, in the ROUTER or REPEATER role, extends the line further. AntiHunter radios stay on the default CLIENT role.
Node commands and detections travel as standard Meshtastic text messages on public or encrypted channels. Any Meshtastic client or integration that reaches the radio reaches the node:
- Meshtastic client - pair the radio to any client - Android, iOS, macOS, or the web client - over Bluetooth, WiFi, or USB. Send
@node COMMANDmessages to run scans and read detections from anywhere in mesh range - no WiFi AP, no Command Center. Clients with quick chat put the commands you use most on a button. - TAK / ATAK - set the radio's role to
TAK, install the Meshtastic ATAK plugin matching your ATAK version, and leave the Meshtastic app running in the background. Outgoing ATAK CoT then also goes over the mesh. TheTAK_TRACKERrole sends the radio's own position to ATAK without ATAK running. The plugin handles CoT only - node alerts arrive as text in the Meshtastic app. - MQTT - a Meshtastic MQTT gateway node forwards mesh traffic to a broker for logging, Home Assistant, or Node-RED.
Mesh TX Architecture
Scan tasks (sniffer/baseline/drone/randdet/blueteam) are pure producers. They enqueue device-broadcast messages into a 256-entry PSRAM-backed FreeRTOS queue (meshTxQueue) and exit immediately when the scan ends. A dedicated background consumer task (meshTxTask) drains the queue at the LoRa airtime cap via the existing token-bucket rate limiter (SerialRateLimiter, ~167 B/s sustained). Device rows are packed into frames up to 230 B (under Meshtastic's 237 B text-payload cap).
Consequences:
- Starting a new scan never waits on prior scan's mesh TX. Drain happens in background.
/stop(web UI or mesh STOP command) flushes the queue immediately (cancels pending TX).- Header badge
Mesh TX K/Nshows live drain progress; auto-hides when queue empty.
Cross-Scan Dedup
To save airtime on repeated scans of the same RF environment, broadcast DEVICE: messages are deduplicated by MAC address with a configurable TTL.
| Setting | Effect |
|---|---|
meshDedupTtl = 0 (disabled) |
Every scan broadcasts every observed device. No skip. |
meshDedupTtl = 300 (5 min, default) |
If a MAC was broadcast in the last 5 min, skip it on subsequent scans within that window. |
meshDedupTtl = 3600 (1 hr max) |
Hourly per-MAC airtime cap. Tightest savings. |
Applies only to: sniffer + baseline DEVICE: broadcasts. Never applied to triangulation (T_F:/T_C:/T_D: need multi-RSSI), anomaly alerts (ANOMALY:, DEVICE_DISAPPEARED:, etc.), drone alerts (DRONE:, DRONE_LOST:), attack alerts (DEAUTH_FLOOD:, ATTACK:), summaries (SCAN_DONE:, BLUE_DONE:, etc.), or randomization identities (IDENTITY:).
SCAN_DONE reporting: with dedup enabled, TX=N DUP=M reflects N MACs broadcast this scan window and M MACs skipped due to dedup. Total unique devices observed = U=N+M (approximately).
Configure via:
- Web UI: Network Settings → Mesh Dedup TTL
- HTTP:
POST /mesh-dedup-ttl?ttl=Nwhere N is seconds (0=disable) - Mesh:
@ALL CONFIG_DEDUP_TTL:N(sec) - Clear cache:
POST /mesh-dedup-clear(forces all MACs to re-broadcast on next scan)
All timestamps UTC. Node IDs: 2-5 alphanumeric characters (A-Z, 0-9), no spaces.
Tip
@ALL broadcasts to all nodes. Replace with a node ID for targeted commands.
| Command | Does | Parameters | Example |
|---|---|---|---|
STATUS |
Report mode, scan state, hits, temp, uptime, GPS | None | @ALL STATUS |
STOP |
Stop everything running | None | @ALL STOP |
| Command | Does | Parameters | Example |
|---|---|---|---|
CONFIG_TARGETS |
Set the watchlist | Pipe-delimited MACs, OUIs, SSIDs | @ALL CONFIG_TARGETS:AA:BB:CC:DD:EE:FF|11:22:33|MyNetwork |
CONFIG_NODEID |
Rename the node | 2-5 alphanumeric | @AH01 CONFIG_NODEID:AH02 |
CONFIG_RSSI |
Set the RSSI floor | -128 to -10 | @ALL CONFIG_RSSI:-80 |
CONFIG_CHANNELS |
Set the channels to sweep | Comma-separated or a range | @ALL CONFIG_CHANNELS:1..11 |
CONFIG_BAND |
Pick the band, C5 only | 0 2.4, 1 5, 2 both |
@ALL CONFIG_BAND:2 |
CONFIG_DEDUP_TTL |
Set cross-scan MAC dedup | Seconds 0-3600, 0 disables | @ALL CONFIG_DEDUP_TTL:300 |
CONFIG_SESSION_DEDUP |
Toggle per-session dedup | 0/1 |
@ALL CONFIG_SESSION_DEDUP:1 |
MESH_DEDUP_CLEAR |
Clear the dedup cache | None | @ALL MESH_DEDUP_CLEAR |
| Command | Does | Parameters | Example |
|---|---|---|---|
SCAN_START |
Hunt the watchlist | mode:secs:channels[:FOREVER] |
@ALL SCAN_START:2:300:1..11 |
DEVICE_SCAN_START |
List everything in range | mode:secs[:FOREVER[:+PROBE]] |
@ALL DEVICE_SCAN_START:2:300:+PROBE |
BASELINE_START |
Learn the area, then flag changes | duration[:FOREVER], min 60s |
@ALL BASELINE_START:300 |
BASELINE_STATUS |
Report baseline progress | None | @ALL BASELINE_STATUS |
DRONE_START |
Watch for drone Remote ID | secs[:FOREVER] |
@ALL DRONE_START:300 |
DEAUTH_START |
Watch for deauth attacks | secs[:FOREVER] |
@ALL DEAUTH_START:300 |
RANDOMIZATION_START |
Link randomized MACs to devices | mode:secs[:FOREVER] |
@ALL RANDOMIZATION_START:2:300 |
PROBE_START / PROBE_STOP |
Collect probe requests | mode:secs[:FOREVER][:+ALL] |
@ALL PROBE_START:2:300:+ALL |
PCAP_START / PCAP_STOP |
Record traffic to SD as pcap | radio:secs:band[:FOREVER] |
@ALL PCAP_START:0:300:0 |
The +PROBE flag on DEVICE_SCAN_START enables probe request capture during device scans, populating the probe database alongside normal device discovery.
| Command | Does | Parameters | Example |
|---|---|---|---|
SENTINEL_ON / SENTINEL_OFF |
Start or stop Sentinel | None | @ALL SENTINEL_ON |
SENTINEL_STATUS |
Report Sentinel state | None | @AH01 SENTINEL_STATUS |
SENTINEL_MODE |
Pin a channel or hop | defend or scan |
@ALL SENTINEL_MODE:scan |
SENTINEL_BOOT |
Auto-start at power-on | 1/0 |
@ALL SENTINEL_BOOT:1 |
GROUP |
Toggle a detector group | <name>:<on|off> |
@ALL GROUP:dos:on |
DETECT_CFG |
Set detector tunables | <json>, ≤180 chars |
@AH01 DETECT_CFG:{"pmkid":true} |
DETECT_CFG_GET |
Dump the config to serial | None | @AH01 DETECT_CFG_GET |
INCIDENTS |
Dump the incident log | [:<1-200>] |
@AH01 INCIDENTS:50 |
INCIDENTS_CLEAR |
Clear the incident log | None | @ALL INCIDENTS_CLEAR |
ATTACKER_TRILAT |
Triangulate a confirmed attacker | 1/0/on/off |
@ALL ATTACKER_TRILAT:1 |
ATTACKER_TRILAT_STATUS |
Report that setting | None | @AH01 ATTACKER_TRILAT_STATUS |
GROUP members: dos = eviltwin, sae, assoc_sleep · rogue = eviltwin, owe, karma · recon = pmkid, probe_flood, hshk · physical = frag, tsf, jam · mesh = mesh_guard · all = every member listed here.
DETECT_CFG sets the rest: ssid_confusion, pwna, csa_quiet, rid_spoof, bloom_gossip, ble_malformed, the 15 mesh_* emit toggles and the numeric thresholds. DETECT_CFG_GET prints every key to serial. GROUP and DETECT_CFG write to NVS. Deauth, beacon and auth detection have no toggle.
Headless has no SoftAP. defend pins to whatever channel the radio last used, so use scan.
| Command | Parameters | Example |
|---|---|---|
SENTINEL_ON / SENTINEL_OFF |
None | @ALL SENTINEL_ON |
SENTINEL_STATUS |
None | @AH01 SENTINEL_STATUS |
SENTINEL_MODE |
defend (pin AP channel) or scan (hop all channels) |
@ALL SENTINEL_MODE:scan |
SENTINEL_BOOT |
1/0 — persist auto-start on boot (NVS sentBoot) |
@ALL SENTINEL_BOOT:1 |
GROUP |
<name>:<on|off> — toggle a detector group (name: dos, rogue, recon, physical, mesh, all). ACK: GROUP_ACK:OK:<name>:<on|off> or GROUP_ACK:FAIL:<reason> |
@ALL GROUP:dos:on |
DETECT_CFG |
<json> — apply detector tunables (JSON, ≤180 chars). ACK: DETECT_CFG_ACK:OK or :FAIL |
@AH01 DETECT_CFG:{"pmkid":true} |
DETECT_CFG_GET |
None — dumps current detector config to serial. ACK: DETECT_CFG_LEN:<n> (see serial) |
@AH01 DETECT_CFG_GET |
INCIDENTS |
[:<1-200>] — dumps sentinel incident log to serial. ACK: INCIDENTS_LEN:<n> (see serial) |
@AH01 INCIDENTS:50 |
INCIDENTS_CLEAR |
None — clear incident log. ACK: INCIDENTS_CLEAR_ACK:OK |
@ALL INCIDENTS_CLEAR |
ATTACKER_TRILAT |
1/0 — auto-triangulate the source MAC of a confirmed attack (deauth flood, SAE DoS, PMKID, evil-twin, etc.), per-MAC cooldown. Off by default. ACK: ATTACKER_TRILAT_ACK:ON/:OFF |
@ALL ATTACKER_TRILAT:1 |
ATTACKER_TRILAT_STATUS |
None. Reply: ATTACKER_TRILAT_STATUS: ON/OFF |
@AH01 ATTACKER_TRILAT_STATUS |
Triangulation Commands
| Command | Does | Parameters | Example |
|---|---|---|---|
TRIANGULATE_START |
Locate a MAC across nodes | target:duration[:rfEnv[:wifiPwr:blePwr]] |
@AH01 TRIANGULATE_START:AA:BB:CC:DD:EE:FF:60:2:1.0:1.0 |
TRIANGULATE_STOP |
Stop it | None | @ALL TRIANGULATE_STOP |
TRIANGULATE_RESULTS |
Report the fix | None | @AH01 TRIANGULATE_RESULTS |
Security Commands
| Command | Does | Parameters | Example |
|---|---|---|---|
ERASE_REQUEST |
Ask to wipe, returns a challenge | None | @AH01 ERASE_REQUEST |
ERASE_FORCE |
Wipe with the answered challenge | Auth token | @AH02 ERASE_FORCE:AH_12345678_87654321_00001234 |
ERASE_CANCEL |
Abort a pending wipe | None | @AH01 ERASE_CANCEL |
AUTOERASE_ENABLE |
Wipe if the node is moved | setup:erase:vibs:window:cooldown |
@AH01 AUTOERASE_ENABLE:60:30:3:30:300 |
AUTOERASE_DISABLE |
Turn that off | None | @AH01 AUTOERASE_DISABLE |
AUTOERASE_STATUS |
Report auto-erase state | None | @AH01 AUTOERASE_STATUS |
VIBRATION_ON / VIBRATION_OFF |
Enable the movement sensor | None | @AH01 VIBRATION_ON |
VIBRATION_STATUS |
Report sensor state | None | @AH01 VIBRATION_STATUS |
VIBSCAN_SET |
Start a scan when moved | en:mode:dur[:cooldown] |
@AH01 VIBSCAN_SET:1:2:60:60 |
VIBSCAN_STATUS |
Report that setting | None | @AH01 VIBSCAN_STATUS |
CONFIG_ERASE_PSK |
Set the key that authorizes a wipe | <key>, 1-64 chars |
@AH01 CONFIG_ERASE_PSK:myS3cretKey |
FACTORY_RESET |
Reset one node, needs the key | <FULL|CONFIG|DATA>:<key> |
@AH01 FACTORY_RESET:FULL:myS3cretKey |
| Command | Parameters | Example | |
| --------- | ------------ | --------- | |
ERASE_REQUEST |
None | @AH01 ERASE_REQUEST |
|
ERASE_FORCE |
Auth token | @AH02 ERASE_FORCE:AH_12345678_87654321_00001234 |
|
ERASE_CANCEL |
None | @AH01 ERASE_CANCEL |
|
AUTOERASE_ENABLE |
setup:erase:vibs:window:cooldown (seconds, except vibs count) |
@AH01 AUTOERASE_ENABLE:60:30:3:30:300 |
|
AUTOERASE_DISABLE |
None | @AH01 AUTOERASE_DISABLE |
|
AUTOERASE_STATUS |
None | @AH01 AUTOERASE_STATUS |
|
VIBRATION_STATUS |
None | @AH01 VIBRATION_STATUS |
|
VIBRATION_ON |
None | @AH01 VIBRATION_ON |
|
VIBRATION_OFF |
None | @AH01 VIBRATION_OFF |
|
VIBSCAN_SET |
en:mode:dur[:cooldownSecs] — auto-start a scan when the vibration sensor fires. en 0/1; mode 0=off, 1=all-device, 2=probe-req, 3=rand-MAC, 4=list, 5=drone, 6=deauth, 7=baseline; dur seconds (0=forever). Skipped if a scan is already running or during battery-saver. ACK: VIBSCAN_ACK:OK En:.. Mode:.. Dur:..s Cd:..s |
@AH01 VIBSCAN_SET:1:2:60:60 |
|
VIBSCAN_STATUS |
None. Reply: VIBSCAN_STATUS: En:.. Mode:.. Dur:..s Cd:..s |
@AH01 VIBSCAN_STATUS |
|
CONFIG_ERASE_PSK |
<key> (1-64 chars) — set/clear the pre-shared key authorizing erase/factory-reset. ACK: CONFIG_ACK:ERASE_PSK:SET or :CLEARED |
@AH01 CONFIG_ERASE_PSK:myS3cretKey |
|
FACTORY_RESET |
<FULL|CONFIG|DATA>:<credential> — factory reset (single node only, requires erase PSK credential). ACK: FACTORY_RESET_ACK:<tier> - rebooting or :DENIED/:BAD_TIER/:BAD_FORMAT/:BUSY |
@AH01 FACTORY_RESET:FULL:myS3cretKey |
Battery Saver Commands
| Command | Does | Parameters | Example |
|---|---|---|---|
BATTERY_SAVER_START |
Drop to low power | interval_minutes 1-30 |
@AH01 BATTERY_SAVER_START:10 |
BATTERY_SAVER_STOP |
Return to normal | None | @AH01 BATTERY_SAVER_STOP |
BATTERY_SAVER_STATUS |
Report power state | None | @AH01 BATTERY_SAVER_STATUS |
Stops WiFi/BLE scanning, reduces CPU to 80MHz, enables light sleep, GPS polled once per minute. Mesh UART stays active. Heartbeat format:
NODE_ID: HEARTBEAT: Temp:XXC GPS:lat,lon Battery:SAVER
Heartbeat Commands
Periodic status broadcast over mesh. Disabled by default.
| Command | Does | Parameters | Example |
|---|---|---|---|
HB_ON / HB_OFF |
Toggle the heartbeat | None | @AH01 HB_ON |
HB_INTERVAL |
Set how often it sends | minutes 1-60 |
@AH01 HB_INTERVAL:10 |
Format: NODE_ID: Time:YYYY-MM-DD_HH:MM:SS Temp:XX.XC [GPS:lat,lon]
Alert Message Formats
| Alert Type | Format |
|---|---|
| Target Detected | NODE_ID: Target: TYPE MAC RSSI:dBm [Name:name] [GPS=lat,lon] |
| Baseline Anomaly | NODE_ID: ANOMALY-NEW/RETURN/RSSI: TYPE MAC RSSI:dBm [details] |
| Deauth Attack | NODE_ID: ATTACK: DEAUTH|DISASSOC [BROADCAST|TARGETED] SRC:MAC DST:MAC RSSI:dBm CH:N R:reason [GPS:lat,lon] |
| Drone Detected | NODE_ID: DRONE: MAC ID:uavId R-dBm [GPS:lat,lon] [ALT:m] [SPD:m/s] [OP:lat,lon] - sent once per appearance, WiFi and BLE alike. Telemetry fields are dropped if the line would exceed the mesh MTU. A drone that stays in range is never re-announced; one that returns after going stale is re-announced at most once per 120s |
| Drone Lost | NODE_ID: DRONE_LOST: MAC [ID:uavId] AGE:secs - sent once, 120s after the last Remote ID beacon. Not repeated while the aircraft stays away, and the Web UI keeps the detection, marked stale |
| Triangulation Data | NODE_ID: T_D: MAC Hits=N RSSI:dBm Type:WiFi/BLE GPS=lat,lon HDOP=X.XX - one per participating node per reporting cycle, coordinator included. Slots are assigned by node-ID order, so every node derives the same rotation |
| Triangulation Final | NODE_ID: T_F: MAC=addr GPS=lat,lon CONF=85.5 UNC=12.3 |
| Triangulation Complete | NODE_ID: T_C: MAC=addr Nodes=N [Google Maps link] |
| Probe Watchlist Hit | NODE_ID: PROBE_HIT MAC [Randomized|Vendor] RSSI=dBm CH=N [SSID="network" [GHOST]] [DST] - vendor token omitted entirely when unknown |
| Packet Capture Started | NODE_ID: PCAP_START: WIFI|BLE D=secs - D=0 means the capture runs until stopped |
| Packet Capture Done | NODE_ID: PCAP_DONE: F=frames B=bytes D=dropped - D counts frames the SD writer could not keep up with |
| Tamper Detected | NODE_ID: TAMPER_DETECTED: Auto-erase in Xs [GPS:lat,lon] |
| Status Response | NODE_ID: STATUS: Mode:TYPE Scan:STATE Hits:N Temp:XXC Up:HH:MM:SS GPS=lat,lon |
Sentinel label reference (mesh labels and their values)
Log parsers and C2 must handle all of these. Values are taken from detect.cpp; anything not listed here is not emitted.
| Mesh prefix | Payload | Enumerated values |
|---|---|---|
DEAUTH_FORGE:<src>:<tool>:<rssi> |
tool tag | static: MARAUDER (reason=2 + seq=0xFFF0 + dur=0x013A - the template shared by ESP32Marauder, Bruce and Evil-M5Project), MICHAEL_TKIP (reason=14). behavioral: MDK4, ESP_DEAUTHER, AIREPLAY, BETTERCAP |
DEAUTH_FLOOD:<src>:<count>:<rssi> |
frame count | - |
DEAUTH_AP_TARGETED:<client>:<reason>:<count> |
client + reason code | reason is context only, see the table above |
BEACON_FORGE:<bssid>:<reason>:<rssi> |
forgery reason | FORGE_TSF_STATIC, FORGE_BI_1000, FORGE_SRC_MCAST, FORGE_CSA_FF, FORGE_QUIET_ELEM, FORGE_SSID_ROTATE, FORGE_EVIL_PORTAL, FORGE_EVIL_PORTAL_ESP, FORGE_KARMA_BRUCE |
BEACON_FLOOD:<rssi> |
- | serial line also carries tool=<reason> or tool=- |
EVILTWIN:<bssid>:<reason>:<rssi>:<ssid> |
twin reason | SELF_CLONE, SELF_CLONE_OPEN, SSID_COLLISION, TWIN_MULTICH, TSF_RESTART |
PROBE_FLOOD:<kind>:<what>:<rssi> |
flood kind | RANDOMIZED, SINGLE_MAC, MARAUDER (probe-request template seq=0x0001, fires on one frame) |
PROBE_FLOOD_BEHAVE:<ssid>:src=<n>:<rssi> / PROBE_FLOOD_AP:... |
- | - |
FRAG:<src>:<reason> |
CVE shape | PN_GAP (CVE-2020-26146), MIXED_PLAIN (CVE-2020-26147) |
HSHK:<bssid>:<sta>:<msg>:<replay>:<rssi> |
usable pair | M1M2 (challenge), M1M4, M2M3, M3M4 (authorized) |
PMKID_HARVEST:<src>:<bssid>:<rssi> |
tool | serial adds tool=HCXDUMPTOOL when the M1 replay counter is in [0xF000,0xFFFE] |
PMKID_FORGE:<src>:<bssid>:<rssi> / PMKID_FORGE:<src>:FAKE_M1:<rssi> |
forge kind | FORGE_PMKID (Marauder BAD_MSG, fixed PMKID 11 22 … ff 11), FAKE_M1 (zero ANonce; serial tag ROGUE_M1) |
EAPOL_BAIT:<src>:<sta>:<count>:<rssi>:<confidence> |
confidence | high (deauth carried a DEAUTH_FORGE tool fingerprint and EAPOL followed ≤2 s), medium (fingerprinted deauth >2 s, or unfingerprinted deauth with EAPOL ≤1 s). An unfingerprinted deauth followed by EAPOL after >1 s is a normal reassociation and does not alert |
CSA_SPOOF:<bssid>:<switch_count> |
count | fires at switch_count ≥ 50; Marauder hardcodes 255 |
QUIET_ABUSE:<bssid>:<duration_tu> |
duration | fires at ≥ 1000 TU; Marauder uses 0xFFFF |
KARMA_CAND:<bssid>:<distinct_ssids> / KARMA_CONFIRMED:<bssid>:<rssi> |
- | candidate at ≥2 distinct SSIDs on one BSSID / 60 s |
AUTH_FLOOD:<bssid>:<distinct_src>:<frames> |
- | open-system (algo 0) only; SAE is SAE_DOS |
SAE_DOS:<bssid>:<unmatched_commits> |
- | - |
JAMMING:<ch>:<pdr_pct>:<err_frames>:<noise_floor_dbm> |
- | per-channel, 1 s window, 60 s cooldown. Fires at pdr < 30% with ≥15 error frames whose mean RSSI is > -70 dBm on a channel that carried a valid frame in the last 30 s. noise_floor_dbm is the mean rx_ctrl.noise_floor over every frame seen on that channel in the window, 0 when no frame reported one. /jamming.jsonl additionally carries err_snr = mean error-frame RSSI minus noise floor |
ASSOC_SLEEP, SSID_CONFUSION, OWE_ABUSE, PWNAGOTCHI, RECON, ATTACKER_HUNT |
- | single-reason detectors |
R: deauth/disassoc reason codes (IEEE 802.11-2020 Table 9-49). The value is reported
as context only - since c0d710d the reason code is not used to decide whether a frame
is an attack, because 1/2/6/7 are all normal causes. Codes seen in practice:
| Code | Meaning | Typical source |
|---|---|---|
| 1 | Unspecified reason | esp8266_deauther template; generic tools |
| 2 | Previous authentication no longer valid | Marauder / Bruce / Evil-M5 template (with seq=0xFFF0, dur=0x013A) |
| 3 | Deauthenticated, STA leaving | normal client roam/disconnect |
| 4 | Disassociated due to inactivity | normal AP housekeeping |
| 5 | Disassociated, AP out of resources | normal AP under load |
| 6 | Class 2 frame from non-authenticated STA | bettercap; also normal |
| 7 | Class 3 frame from non-associated STA | aireplay-ng (with dur=0x013A), GhostESP; also normal AP behavior |
| 8 | STA leaving BSS | normal |
| 14 | Michael MIC failure (TKIP) | MICHAEL_TKIP forge tag |
Any other value is passed through verbatim as Reason code N.
Note
All timestamps use UTC
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Web interface |
/diag |
GET | System diagnostics |
/stop |
GET | Fast-abort every scan/task: aborts in-flight WiFi/BLE scans, stops triangulation, cancels mesh drain. /diag reports Stopping: yes until the task actually exits |
/config |
GET/POST | System configuration (JSON) |
/clear-results |
POST | Clear all scan results |
| Endpoint | Method | Description |
|---|---|---|
/scan |
POST | Start target scan (mode, secs, forever, ch, triangulate, targetMac). With triangulate=1, returns 400 and the reason if triangulation cannot start (bad/empty targetMac, debounce, busy task) |
/sniffer |
POST | Start detection scan (detection, secs, forever, randomizationMode, probeScanMode, captureProbes) |
/drone |
POST | Start drone RID detection (secs, forever) |
/pcap/status |
GET | Capture state (JSON): active, radio, band, channel, frames, bytes, dropped, elapsed, current file, dualBand, SD budget/floor/free in MB |
/pcap/list |
GET | Captures in /pcap (JSON): name, size, and whether that file is still being written |
/pcap/download |
GET | Streams a capture. f=<name> selects one, omit it for the most recent |
/pcap/delete |
POST | Deletes one capture (f). Refused while that file is recording |
/pcap/delete-all |
POST | Deletes every capture in /pcap |
/pcap/limits |
POST | Auto-capture pruning (budgetMB, floorMB), persisted |
| Endpoint | Method | Description |
|---|---|---|
/results |
GET | Latest scan/triangulation results |
/sniffer-cache |
GET | Cached device detections |
/probe-results |
GET | Probe request results |
/deauth-results |
GET | Deauth attack logs |
/randomization-results |
GET | Randomization correlation results |
/baseline-results |
GET | Baseline anomaly results |
/drone-results |
GET | Drone detection results |
/drone-log |
GET | Drone event log (JSON) |
The Fleet tab lists senders heard on the mesh.
Nodes - ids matching the node-id rule (2-5 chars, A-Z0-9), with mode, scan state, hits, uptime, temp
and GPS from their STATUS and heartbeat lines. type is RADAR on TYPE:RADAR, else DIGI. Online =
heard within 2 minutes.
Other Mesh Radios - every other sender id, including your own paired radio. Tagged Control once an
@ line is seen from it, Unknown otherwise. Also shown in the Sentinel tab.
Rosters hold 48 entries in RAM, dropped after 15 minutes unheard. Ping Nodes broadcasts @ALL STATUS.
| Endpoint | Method | Description |
|---|---|---|
/api/mesh |
GET | Fleet roster (JSON: node, peers, radios) |
/api/mesh/ping |
POST | Broadcast @ALL STATUS |
/api/mesh/clear |
POST | Clear both rosters |
| Endpoint | Method | Description |
|---|---|---|
/api/probedb |
GET | Probe database (JSON: mac, vendor, name, SSIDs, RSSI, randomization status) |
/api/probedb/clear |
POST | Clear probe database |
/api/probes.jsonl |
GET | Stream probe log from SD (JSONL) |
vendor is the IEEE OUI-registered organization, resolved from the first 24 bits of the MAC.
name is the device's advertised BLE name. Randomized (locally administered) MACs carry no OUI
assignment and resolve to no vendor.
Every device seen by a Device Discovery or target scan is merged into /devicedb.jsonl on SD and
survives reboots. Capped at 2000 entries; the least recently seen entry is evicted when full.
| Endpoint | Method | Description |
|---|---|---|
/api/devicedb |
GET | All discovered devices (JSON: mac, ble, vendor, name, rssi, ch, sessions, seen, first, last, rand) |
/api/devicedb/clear |
POST | Clear device database |
The Data tab in the web UI provides a searchable, sortable view of all SD-logged scan data. Select a dataset from the dropdown, search across any column, click column headers to sort, and page through results.
| Endpoint | Method | Description |
|---|---|---|
/api/deauth.jsonl |
GET | Deauth/disassoc attack log (JSONL) |
/api/deauth/clear |
POST | Clear deauth log (RAM + SD) |
/api/drones.jsonl |
GET | Drone RID detection log (JSONL) |
/api/drones/clear |
POST | Clear drone log (RAM + SD) |
/api/vibrations.jsonl |
GET | Vibration/tamper event log (JSONL) |
/api/vibrations/clear |
POST | Clear vibration log (SD) |
/api/antihunter.log |
GET | System event log (text) |
/api/antihunter.log/clear |
POST | Clear system log |
Available datasets: All Discovered Devices, Probe Devices, Probe Events, Deauth Attacks, Drone Detections, Vibration Events, Baseline Stats, Sentinel Incidents, and System Log. All datasets support export (download the raw file) and clear (with confirmation). The headless firmware logs the same data to SD without the web UI, except the device database, which is web-build only.
Configuration Endpoints
| Endpoint | Method | Description |
|---|---|---|
/node-id |
GET/POST | Get/set node ID (2-5 alphanumeric, A-Z 0-9) |
/mesh-interval |
GET/POST | Get/set mesh send interval (1500-30000ms) |
/save |
POST | Save target configuration |
/export |
GET | Export target MAC list |
/allowlist-export |
GET | Export allowlist |
/allowlist-save |
POST | Save allowlist |
/api/time |
POST | Set RTC time from Unix timestamp |
Sentinel / Detection Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/detect/config |
GET | Current detector config (JSON: every detector enable, mesh-broadcast flag, threshold) |
/api/detect/config |
POST | Set detector config. JSON body of {key:bool/int} - same keys returned by GET (e.g. pmkid, eviltwin, sae, karma, probe_flood, assoc_sleep, mesh_* flags, thresholds). The Web Flasher/Configurator sends these under a nested detectors object at flash time. |
/api/detect/health |
GET | Detector runtime health (heap, queue depth, drops, per-detector counts) |
/api/sentinel/status |
GET | Sentinel running state |
/api/sentinel/start / /api/sentinel/stop |
POST | Start/stop the Sentinel engine |
/api/incidents.json |
GET | Recent incident ring (JSON) |
/api/incidents.jsonl |
GET | Full incident log from SD (JSONL) |
/api/incidents |
DELETE | Clear all incidents (RAM + SD) |
/api/mesh_cmd.jsonl |
GET | Mesh command provenance audit from SD (JSONL: ts, epoch, src radio id, cmd) |
/api/mesh_cmd |
DELETE | Clear the mesh command audit log |
/api/apclients.json |
GET | Stations associated to this node's AP |
Each incident record carries: ts (device uptime ms), epoch (RTC Unix seconds - 0 if RTC unset; used by the Analysis tab to show real timestamps), node, src, type, raw.
Persistent boot setting: sentinelBoot (bool) in the configurator JSON / NVS pref sentBoot - auto-starts the Sentinel at power-on when true.
RF Configuration Endpoints
| Endpoint | Method | Parameters | Description |
|---|---|---|---|
/rf-config |
GET | - | RF config (JSON) |
/rf-config |
POST | preset (0-2) |
Apply preset: 0=Relaxed, 1=Balanced, 2=Aggressive |
/rf-config |
POST | wifiChannelTime, wifiScanInterval, bleScanInterval, bleScanDuration, wifiChannels, globalRssiThreshold |
Full custom config |
/rf-config |
POST | globalRssiThreshold (-100 to -10) |
RSSI threshold only |
/rf-config |
POST | bandMode (0-2) |
C5 only. Band: 0=2.4GHz, 1=5GHz, 2=both. Also returned by GET, and accepted on /config GET/POST and in the serial CONFIG: JSON |
/wifi-config |
GET | - | WiFi AP settings (JSON) |
/wifi-config |
POST | ssid (1-32), pass (8-63 or empty), auth (0=WPA2/WPA3, 1=WPA2), hidden (0/1) |
Update AP credentials and mode (triggers reboot). hidden=1 stops the SSID beacon; clients must then enter the SSID manually and will probe for it by name, so the network name travels with them. Auth remains the access control |
Baseline Endpoints
| Endpoint | Method | Description |
|---|---|---|
/baseline/status |
GET | Baseline scan status (JSON) |
/baseline/stats |
GET | Baseline statistics (JSON) |
/baseline/config |
GET/POST | Baseline config (rssiThreshold, baselineDuration, ramCacheSize, sdMaxDevices, absenceThreshold, reappearanceWindow, rssiChangeDelta) |
/baseline/reset |
POST | Reset baseline |
Triangulation Endpoints
| Endpoint | Method | Description |
|---|---|---|
/triangulate/start |
POST | Start (mac, duration, rfEnv, optional wifiPwr/blePwr 0.1-5.0); 400 + reason if it cannot start |
/triangulate/stop |
POST | Stop triangulation |
/triangulate/status |
GET | Status (JSON) |
/triangulate/results |
GET | Results |
/triangulate/nodes |
GET | Connected triangulation nodes |
/triangulate/calibrate |
POST | Calibrate path loss (mac, distance) |
Randomization, Security, and Hardware Endpoints
Randomization:
| Endpoint | Method | Description |
|---|---|---|
/randomization/reset |
POST | Reset randomization detection |
/randomization/clear-old |
POST | Clear old identities (optional age) |
/randomization/identities |
GET | Tracked identities (JSON) |
Security:
| Endpoint | Method | Description |
|---|---|---|
/erase/status |
GET | Erasure status |
/erase/request |
POST | Request secure erase (confirm=WIPE_ALL_DATA, optional reason) |
/erase/cancel |
POST | Cancel erase sequence |
/secure/status |
GET | Tamper detection status |
/secure/abort |
POST | Abort tamper sequence |
/config/autoerase |
GET/POST | Auto-erase config |
/battery-saver |
GET | Battery saver (action=start/stop/status, interval) |
Hardware:
| Endpoint | Method | Description |
|---|---|---|
/gps |
GET | GPS status and location |
/sd-status |
GET | SD card status |
/drone/status |
GET | Drone detection status (JSON) |
/mesh |
POST | Enable/disable mesh |
/mesh-test |
GET | Test mesh connectivity |
/mesh-hb |
POST | Enable/disable heartbeat (enabled=true|false) |
/mesh-hb-interval |
POST | Set heartbeat interval (interval=1-60 minutes) |
/vibration |
POST | Toggle vibration sensor |
Original concept and hardware design by @TheRealSirHaXalot. Get involved -- PRs, issues, and docs contributions welcome.
This project includes code from opendroneid-core-c, licensed under the Apache License 2.0. Copyright (C) Intel Corporation and OpenDroneID contributors
Full Disclaimer
# Legal Disclaimer
AntiHunter ("AH", the "Project") comprises open-source firmware, source code, hardware designs, and associated documentation distributed for lawful, authorized defensive use only. You may operate the Project solely on infrastructure, networks, devices, radio spectrum, and datasets that you own or for which you hold explicit, written permission to assess. By downloading, compiling, flashing, assembling, energizing, or otherwise using the Project you agree to the following conditions:
- **Authorization & intent.** Use is limited to security research, blue-team training, regulatory-compliant monitoring, event security, network auditing, and other defensive activities. Offensive operations, targeted surveillance, stalking, harassment, or tracking of individuals without their informed consent are strictly prohibited. Detection, correlation, and triangulation capabilities are provided to characterize an environment you are authorized to assess, not to identify or follow persons.
- **Radio & telecommunications compliance.** You are responsible for abiding by every jurisdictional regulation governing radio frequency use, including FCC Part 15 and Part 97, CE/RED, Ofcom, and equivalent national rules; LoRa/ISM band allocations, power limits, and duty-cycle restrictions; and any licensing conditions applicable to your operating class. LoRa firmware is region-locked (868 MHz EU / 915 MHz US / 923 MHz Asia); operating a build outside its intended regulatory region may be unlawful. You must not use the Project to cause harmful interference or to interfere with authorized radio communications.
- **Interception & wiretap law.** Passive reception of radio emissions is regulated separately from network access in many jurisdictions. Depending on your location and the mode in use, capturing, storing, decoding, or disclosing frame contents, payloads, or identifiers may implicate the U.S. Wiretap Act (18 U.S.C. § 2511), the Electronic Communications Privacy Act, state two-party-consent statutes, the UK Investigatory Powers Act, or equivalent law. Determine the lawfulness of each capture mode in your jurisdiction before enabling it.
- **Privacy & data protection.** MAC addresses, device identifiers, probe request contents, BLE advertisements, and Remote ID broadcasts may constitute personal data under GDPR, UK GDPR, CCPA/CPRA, ePrivacy, and similar regimes. Collect telemetry only with a lawful basis. Obtain consent where required, minimize collection, apply retention and destruction schedules that match applicable law, and honor data-subject rights. The maintainers do not process, receive, or host your data.
- **Drone Remote ID.** Reception of broadcast Remote ID is permitted in most jurisdictions, but using Remote ID data to locate, approach, confront, or interfere with an aircraft or its operator may violate aviation, harassment, or anti-stalking law, including 18 U.S.C. § 32. Remote ID reception is not an authorization to act on what you receive.
- **Computer misuse laws.** Scanning, probing, or accessing third-party networks without permission may violate the Computer Fraud and Abuse Act, the UK Computer Misuse Act, EU Directive 2013/40, or similar statutes. Always obtain written authorization before interfacing with systems you do not control.
- **Export & sanctions.** You must ensure distribution and use complies with the U.S. EAR (including controls applicable to cryptographic and telemetry functionality), EU dual-use regulations, applicable sanctions regimes, and any contractual restrictions. The maintainers make no export classification representations and grant no export approvals.
- **Hardware, assembly & safety.** Kits, bare PCBs, and assembled units are supplied for use by persons competent in electronics assembly and operation. You are responsible for correct assembly, soldering, ESD control, antenna selection and attachment, and supply of regulated 5 V power. Operating the transceiver without a properly matched antenna may damage the hardware. Lithium cells are not supplied; sourcing, protection circuitry, charging, storage, transport, and disposal of any battery are your responsibility and carry fire and injury risk. The hardware is not certified for, and must not be used in, life-safety, medical, aviation, automotive, industrial-control, or other applications where failure could result in death, injury, or environmental damage. It is not a substitute for a certified security, alarm, or life-safety system, and detection results are advisory only, subject to false positives and false negatives.
- **Units, kits, and builds supplied by the maintainers.** These terms apply in full to units the maintainers assembled, kits and bare boards they shipped, firmware flashed with tools they published, and builds produced by following their documentation, BOM, wiring diagrams, or flashing instructions. Assembly, sale, or instruction by the maintainers creates no warranty, no certification, no fitness-for-purpose representation, and no assumption of responsibility for how a unit is later configured, deployed, or used. Once the unit is in your hands, operation and compliance are yours alone. Flashing the firmware - by the web flasher, the shell script, or any other means - is acceptance of these terms.
- **Experimental features.** Modes designated beta or experimental - including Sentinel, Triangulation, and MAC Randomization Correlation - are unvalidated, may produce inaccurate or misleading output, and must not be relied upon for operational, evidentiary, investigative, or safety decisions.
- **Operational safeguards.** Run the Project on hardened, access-controlled infrastructure. You are responsible for segregation of duties, credential management, network isolation of nodes and mesh links, and preventing unauthorized access to captured telemetry or command functions.
- **Forks and modifications.** The firmware is licensed under the GNU Affero General Public License v3.0; hardware and documentation are licensed as stated in their respective files. If you fork, redistribute, modify, manufacture, or resell the Project, you are solely responsible for supporting your derivative work, for its regulatory compliance and certification, and for any representations you make about it. The original authors and contributors are not liable for defects or legal issues introduced by third-party changes, packaging, integrations, or manufacture.
## No Warranty / Limitation of Liability
THE PROJECT - INCLUDING SOFTWARE, FIRMWARE, HARDWARE DESIGNS, AND DOCUMENTATION - IS PROVIDED "AS IS" AND "AS AVAILABLE," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, ACCURACY, DETECTION EFFICACY, OR UNINTERRUPTED OPERATION. THIS DISCLAIMER SUPPLEMENTS AND DOES NOT LIMIT THE WARRANTY DISCLAIMER AND LIABILITY LIMITATION SET OUT IN SECTIONS 15 THROUGH 17 OF THE GNU AFFERO GENERAL PUBLIC LICENSE V3.0.
TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE AUTHORS, DEVELOPERS, MAINTAINERS, AND CONTRIBUTORS SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL DAMAGES (INCLUDING, WITHOUT LIMITATION, LOSS OF DATA, PROFITS, GOODWILL, EQUIPMENT, OR BUSINESS INTERRUPTION, OR DAMAGES ARISING FROM FAILURE TO DETECT, FALSE DETECTION, OR REGULATORY ENFORCEMENT ACTION) ARISING FROM OR RELATED TO YOUR USE OF THE PROJECT, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. WHERE LIABILITY CANNOT BE FULLY DISCLAIMED, TOTAL AGGREGATE LIABILITY SHALL NOT EXCEED THE GREATER OF (A) THE AMOUNT PAID, IF ANY, FOR THE COPY OR UNIT THAT GAVE RISE TO THE CLAIM OR (B) USD $0.
NOTHING IN THIS DISCLAIMER EXCLUDES OR LIMITS LIABILITY THAT CANNOT LAWFULLY BE EXCLUDED OR LIMITED, INCLUDING LIABILITY FOR DEATH OR PERSONAL INJURY CAUSED BY NEGLIGENCE, FOR FRAUD, OR ANY NON-EXCLUDABLE STATUTORY CONSUMER RIGHTS. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES OR THE LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO SOME OF THE ABOVE MAY NOT APPLY TO YOU.
## Responsibility for Compliance
You alone are responsible for ensuring your build, deployment, and operation comply with all applicable laws, regulations, licenses, permits, equipment authorizations, organizational policies, and third-party rights. No advice or information, whether oral or written, obtained from the Project, its maintainers, or its community channels creates any warranty or obligation not expressly stated in this disclaimer. Continued use signifies your agreement to indemnify and hold harmless the authors, developers, maintainers, and contributors from claims arising out of or related to your activities with the Project.
If you do not agree to these terms, do not build, flash, assemble, deploy, or operate AntiHunter.







