SquachWatch · Volume 7
SquachWatch Volume 7 — What It Detects and How
Two radios on one antenna, seventeen detection types, ninety-seven graded prefixes, and an honest list of what a match does and does not prove

7.1 The detection model at a glance
SquachWatch is a passive signature matcher. It does not classify radio behaviour statistically, it does not fingerprint traffic patterns, and it does not decrypt anything. It listens to 2.4 GHz WiFi management and data frames and to Bluetooth Low Energy advertisements, and it compares a small number of fields in each against fixed tables: an address prefix (OUI), a network name (SSID), a 16-bit or 128-bit service UUID, a Bluetooth SIG company identifier, an advertised device name, or a fixed-format payload. Three detections are behavioural rather than table lookups: a deauthentication burst, an evil-twin access point, and a spam flood of short-lived tracker addresses. Everything else is “this field equals that value.”
That simplicity is the point and the limitation. Every alert can be traced back to a single row in src/signatures.cpp and a single entry in docs/DETECTIONS.md, which records where the row came from and how much it should be trusted. The project’s own summary of the trade: “It’s not magic and it’s not X-ray vision. It’s a small board doing pattern-matching on radio noise, wearing a mascot costume.”
7.1.1 Two radios, one antenna
The ESP32-WROOM-32 on the 2.8” CYD has one 2.4 GHz RF path shared between the WiFi MAC and the Bluetooth controller. SquachWatch runs both at once — the WiFi side as a promiscuous sniffer, the BLE side as an indefinite NimBLE scan — and the coexistence arbiter divides the air time. The knob that sets the split is the BLE scan window within the scan interval: interval 100, window 75. The source records the bench measurement behind that choice, four minutes at each setting in the same spot on 2026-09-15:
Table 1 — The ESP32-WROOM-32 on the 2.8" CYD has one 2.4 GHz RF path shared between the WiFi MAC and the Bluetooth controller. SquachWatch runs both at once — the WiFi side as a promiscuous sniffer, the BLE side as an indefinite NimBLE scan — and the coexistence arbiter divides the air time. The knob that sets the split is the BLE scan window within the scan interval: interval 100, window 75. The source records the bench measurement behind that choice, four minutes at each setting in the same spot on 2026-09-15
| BLE scan window (of 100) | WiFi frames captured | BLE adverts captured | Verdict in the source |
|---|---|---|---|
| 99 | 44 | 2641 | Sniffer “deaf” — about one frame a second next to a router beaconing ten times a second |
| 75 (shipped) | 866 | 2458 | Twenty times the WiFi of 99 for a dip in adverts “inside run-to-run noise” |
| 50 | 1851 | 1485 | Costs half the adverts |
Note — The practical consequence is that neither radio is listening continuously. A device that transmits rarely — one beacon every few seconds, a single probe request — can fall into the other radio’s share of time. This is one reason the same device can appear, vanish and reappear in the LOG.
7.1.2 The pipeline
┌───────────────────────────────── ESP32 RF (2.4 GHz, shared) ─────────────────────────────────┐
│ │
│ WiFi promiscuous RX (mgmt + data) NimBLE scan (forever, dup filter off) │
│ hop ch 1..13 (+5 GHz on C5 boards) passive at boot; active when quiet │
└───────────────┬───────────────────────────────────────────────────────┬───────────────────────┘
│ IRAM callback │ host-task callback
▼ ▼
┌──────────────────────────────┐ ┌──────────────────────────────────┐
│ frame type/subtype switch │ │ heap seatbelt (drop if heap low) │
│ probe req → addr2 │ │ SquachMesh peer? → handle, RETURN│
│ data → addr1 + addr2 │ │ byte-order flip (printed order) │
│ beacon → Remote ID? / │ │ watch / hunt slot check │
│ pwnagotchi? / │ │ mfg ID → AirTag check → iBeacon │
│ BSSID+SSID+priv │ │ raw AirTag fallback │
│ deauth → burst queue │ │ Remote ID service data (0xFFFA) │
└──────────────┬───────────────┘ │ 16-bit / 128-bit service UUIDs │
│ mailbox (8 deep) │ name fallback │
▼ └────────────────┬─────────────────┘
┌──────────────────────────────┐ │
│ loop(): processWiFiQ() │ │
│ pwnagotchi > drone > │ │
│ EVIL TWIN > OUI > SSID │ │
└──────────────┬───────────────┘ │
└──────────────────────┬───────────────────────────────────┘
▼
┌────────────────────────────────────────────────┐
│ TYPE FILTER gate → dedupe/merge into LOG (64) │
│ → black box (flash) → SD CSV (if card) │
│ → spam watch → alert gate (ALERT FILTER, │
│ IGNORE/SNOOZE, AUTO SNOOZE, watch list) │
│ → ALERT card / LOCKED ON / quiet log │
└────────────────────────────────────────────────┘
The split between “decided in the radio callback” and “decided in loop()” matters. The WiFi callback runs in IRAM and does only byte-offset work: it copies an address, an RSSI, a channel and (for beacons) up to 32 bytes of SSID into an eight-entry mailbox. Every table lookup happens later on the loop task. A full mailbox drops frames silently, which is acceptable for a detector because the same device will transmit again.
7.1.3 The seventeen types
The FAQ counts seventeen detection types. The table below gives each one’s internal name, what it claims, which radio carries the evidence, and its base grade as set by confidenceFor() in src/signatures.cpp. “Per row” means the OUI rows carry their own grade, which overrides the base (§4.1).
Table 2 — 1.3 The seventeen types
| Type | Main-screen column | Claim | Radio | Matched on | Base grade |
|---|---|---|---|---|---|
FLOCK | FLOCK | Flock Safety ALPR camera or accessory | WiFi + BLE | 49 OUIs, SSIDs, BLE names, company ID, 128-bit UUID | High (OUI rows per row: 1 High, 48 Low) |
AXON | AXON | Axon body camera / TASER / LE gear | WiFi + BLE | 3 OUIs, 4 SSID prefixes, BLE name | High (OUI rows per row) |
META | GLASS | Camera glasses (Ray-Ban Meta, Snap) | BLE | 0xFD5F UUID, 4 company IDs | Medium |
SKIMMER | SKIM | Bluetooth card skimmer | BLE + WiFi | Module names, SPP UUID, 3 OUIs | High (OUI rows Low) |
RAVEN | RAV | Raven gunshot detector | BLE | Five service UUIDs | Medium |
AIRTAG | TRACKER | Apple AirTag / Find My | BLE | Company ID 0x004C + payload check | Medium |
SAMSUNG_TAG | (in TRACKER) | Galaxy SmartTag | BLE | 0xFD5A UUID | High |
GOOGLE_TAG | (in TRACKER) | Find My Device Network tracker | BLE | 0xFEAA UUID | Medium |
TILE | (in TRACKER) | Tile tracker | BLE | 0xFEED / 0xFEEC UUIDs | High |
DRONE | DRONE | ASTM F3411 Remote ID aircraft | BLE + WiFi | 0xFFFA service data, beacon vendor element | Medium |
ALPR | ALPR | Motorola Solutions / Genetec plate reader | WiFi | 7 OUIs | Medium (rows High) |
CAMERA | CAM | IP / covert camera | WiFi | 17 OUIs | High (rows per row) |
RING | (in CAM) | Ring doorbell / camera | WiFi | 15 OUIs | High (rows per row) |
IBEACON | BCON | Retail proximity beacon | BLE | Exact Apple iBeacon header | High — off by default |
DEAUTH | DEAUTH | Deauthentication flood | WiFi | 6 frames in 3 s | Medium |
EVILTWIN | (in HACK) | Rogue / spoofed AP | WiFi | SSID from two vendors disagreeing on encryption | High |
HACKER | HACK | Flipper Zero, Pwnagotchi, Pineapple, deauthers | BLE + WiFi | UUIDs, company ID, OUIs, beacon JSON, SSIDs | Medium base; exact signatures set High |
The main screen packs these into eleven fixed counter columns by folding three groups: GOOGLE_TAG, TILE and SAMSUNG_TAG count under TRACKER with AIRTAG; RING counts under CAM; EVILTWIN counts under HACK. Every other screen — the LOG, the alert card, the colours — keeps the types apart.

7.2 The WiFi side
7.2.1 Promiscuous capture and which address is read
The sniffer filter admits management and data frames only. The callback then reads the 802.11 header by fixed offset — address 1 at byte 4, address 2 at byte 10, address 3 (BSSID) at byte 16 — and chooses which one to post according to frame type:
Table 3 — The sniffer filter admits management and data frames only. The callback then reads the 802.11 header by fixed offset — address 1 at byte 4, address 2 at byte 10, address 3 (BSSID) at byte 16 — and chooses which one to post according to frame type
| Frame | Type / subtype | Address(es) posted | Extra fields extracted | Feeds |
|---|---|---|---|---|
| Probe request | 0 / 4 | addr2 (transmitter) | — | OUI lookup |
| Data | 2 / any | addr1 and addr2 | — | OUI lookup |
| Beacon | 0 / 8 | addr3 (BSSID), or addr2 for Remote ID / Pwnagotchi | SSID (first IE), Privacy bit (capability byte 34, bit 4) | Remote ID, Pwnagotchi, evil twin, OUI, SSID |
| Deauthentication | 0 / 12 | addr2 | — | Burst counter only |
| Anything else | — | not posted | — | Channel-busyness counter only |
The addr1 trick. Posting the receiver address of data frames is deliberate. docs/DETECTIONS.md attributes it to the Flock work: a camera that is not itself transmitting still appears as the destination of frames addressed to it, so a “sleeper” device’s MAC leaks through traffic sent to it. The same mechanism applies to every OUI type, not only Flock.
Every frame that reaches the loop task — matched or not — also feeds the per-channel activity level that drives the spectrum-style background and the hop scheduler, and is checked against the watch-list and hunt slots, so a device the user chose to WATCH is followed even if it matches no signature (Vol 8 §4).
7.2.2 Channel hopping
A promiscuous ESP32 hears one channel at a time, so the firmware cycles channels 1 to 13. The dwell is not uniform. Each channel’s frame rate is measured on every visit, smoothed (new = (3 × old + this visit) / 4), and the next sweep gives shares accordingly:
Table 4 — A promiscuous ESP32 hears one channel at a time, so the firmware cycles channels 1 to 13. The dwell is not uniform. Each channel's frame rate is measured on every visit, smoothed (new = (3 × old + this visit) / 4), and the next sweep gives shares accordingly
| Channel state | Dwell share | Rationale in the source |
|---|---|---|
| At least a quarter as busy as the busiest channel | 4 | ”Most of a building’s WiFi sits on two or three channels” |
| Any traffic at all | 2 | |
| Silent | 1 | A quick look; still longer than a ~100 ms beacon interval |
| Whole sweep | ≈ 3.9 s | Keeps the watch’s five-second window covering every channel |
On the boards built with SQW_WIFI_5G — the NM-CYD-C5 and Marauder V8, both ESP32-C5 — the sweep continues into 5 GHz after channel 13: every 5 GHz channel that has been busy recently plus two silent ones in rotation, from the list the radio accepts for the configured country. WIFI BANDS under Settings → BEHAVIOR appears only on those builds. Every other board is 2.4 GHz only.
7.2.3 OUI lookup and precedence
lookupOui() walks kOuiTable top to bottom and returns the first match, so row order is precedence. The design contract (docs/DESIGN.md §6.2) specifies the order Axon → Flock → ALPR → specific camera vendors → ESP32 family → skimmer, so that a prefix appearing in two contexts resolves to the more specific claim.
Caution —
docs/DESIGN.mdis the v1.0 contract and has not been kept fully current. Its step 5, “ESP32 family OUIs → CAMERA,” was never built:docs/DETECTIONS.mdstates that a generic “flag any Espressif OUI as a possible camera” fallback “is not built.” In the shipping table the Espressif prefixes sit underFLOCKat Low, and nowhere else.
The table currently holds 97 rows. Counting them by type and grade in src/signatures.cpp reproduces the project’s published totals of 33 High, 4 Medium and 60 Low:
Table 5 — The table currently holds 97 rows. Counting them by type and grade in src/signatures.cpp reproduces the project's published totals of 33 High, 4 Medium and 60 Low
| Type | High | Medium | Low | Total | Note |
|---|---|---|---|---|---|
FLOCK | 1 | 0 | 48 | 49 | Only B4:1E:52 is registered to Flock Safety |
AXON | 1 | 0 | 2 | 3 | 00:25:DF (Taser International) High |
ALPR | 7 | 0 | 0 | 7 | 5 Motorola Solutions, 2 Genetec |
CAMERA | 10 | 2 | 5 | 17 | Amazon’s two blocks Medium |
RING | 13 | 2 | 0 | 15 | Ring LLC’s 13 High; two inherited from CAMERA Medium |
SKIMMER | 0 | 0 | 3 | 3 | Module-vendor blocks |
HACKER | 1 | 0 | 2 | 3 | 0C:FA:22 Flipper High; two Hak5 locally-administered Low |
| Total | 33 | 4 | 60 | 97 | Matches docs/DETECTIONS.md |
An OUI match carries the row’s own grade. An SSID match, which has no per-row grade, falls back to the type’s base grade.
7.2.4 SSID matching
SSIDs are read only from beacons and matched case-insensitively. There is one exact match and eight prefixes:
Table 6 — SSIDs are read only from beacons and matched case-insensitively. There is one exact match and eight prefixes
| SSID | Match | Type | Vendor label |
|---|---|---|---|
Flock | exact | FLOCK | Flock |
flock-, FLOCK- | prefix | FLOCK | Flock-Setup |
AB2-, AB3-, AB4- | prefix | AXON | Axon-Body2/3/4 |
AXON- | prefix | AXON | Axon-Field |
Pineapple_ | prefix | HACKER | Pineapple |
pwned | prefix | HACKER | Deauther |
The bare Flock is exact precisely so that “Flockhart Family WiFi” does not match. The SSID path runs only when the OUI lookup found nothing, so an Axon camera in pairing mode on a WiFi module whose OUI is unknown is still caught by its AB3- network name.
7.3 The Bluetooth side
7.3.1 Scanner configuration
The NimBLE scanner is configured once at boot and then runs forever. Each setting in the source carries the incident that produced it:
Table 7 — The NimBLE scanner is configured once at boot and then runs forever. Each setting in the source carries the incident that produced it
| Setting | Value | Why |
|---|---|---|
| Scan mode at boot | Passive | ”The first second of an active scan in a crowded building is what killed a user’s board.” |
| Switch to active | When the advert rate is low enough and heap allows | Active scanning asks for scan responses, which is where most device names live |
| Back to passive | Above 300 adverts/s, or three heap-pressure flushes in a minute (then held passive 120 s) | An active scan holds a record per unanswered device |
| Minimum time between mode changes | 30 s | Hysteresis |
| Scan interval / window | 100 / 75 | WiFi coexistence (§1.1) |
| Duplicate filter | Off | Every sighting updates RSSI and last-seen |
| Scan-response timeout | 200 ms | Library default of 10.24 s let two thousand pending records pile up — “the college-floor crash” |
| Max retained results | 0 | NimBLE otherwise keeps every address ever seen; the leak crashed boards on commutes |
| Heap seatbelt | Drop the advert when heap is low | A string allocation on a fragmented heap throws on the host task |
Note — Active scanning is the one way the detector transmits while detecting: a scan request is a radio frame from the board. The FAQ lists it first in its “Does it transmit anything?” table and states there is no menu switch for it. Vol 9 §10 has the complete transmit inventory.
7.3.2 Match order inside the advert callback
The BLE callback stops at the first branch that assigns a type. The order is fixed in the code and explains several behaviours users report:
advert in
│
├─ heap low? ───────────────────────────────────────────► drop (counted)
│
├─ manufacturer data, company 0xFFFF + 'SQM1' magic? ───► SquachMesh peer: draw visitor,
│ feed watch/hunt, RETURN
│ (never becomes a Detection)
├─ flip address to printed order (issue #22)
├─ watch / hunt slot check (any device, matched or not)
│
├─ manufacturer data present?
│ company ID in table ──► 0x004C Apple? ──► AirTag payload check
│ ├─ pass → AIRTAG
│ └─ fail → iBeacon header? → IBEACON
│ else → nothing
│ 0x09C8 → FLOCK 0x01AB/0x058E/0x0D53/0x03C2 → META 0x0E29 → HACKER
│
├─ still nothing: raw-advert AirTag pattern anywhere? ──► AIRTAG
├─ still nothing: Remote ID in service data 0xFFFA? ────► DRONE (decode, §5)
├─ still nothing: 16-bit service UUID in list? ─────────► SKIMMER / TILE / META /
│ RAVEN / DRONE / SAMSUNG_TAG /
│ GOOGLE_TAG / HACKER
│ 128-bit Flock accessory UUID? ────────► FLOCK
└─ still nothing: advertised name lookup ───────────────► SKIMMER / FLOCK / AXON / HACKER
Grades on the BLE side are per type, not per row, with two exceptions coded at the match site: a HACKER match that arrived only by name is Medium while every exact HACKER signature is High, and a FLOCK match on a bare ten-digit name is Low.
Two details of the name path are worth knowing. The module names used for skimmers (HC-03, HC-05, HC-06, RN42, BT04-A) and two Flock accessory names (Flock_Setup, FS Ext Battery) are exact case-insensitive matches. The Flock, Penguin, Pigvision, Axon and Flipper checks are substring matches (strcasestr), so any BLE device whose name merely contains one of those words is classified. That is a wider net than the exact SSID rule above, and the source does not remark on the asymmetry.
7.3.3 What the Bluetooth side does not do
- No Bluetooth Classic inquiry. It “conflicts with NimBLE on a single radio” and has not been switched on, so a skimmer is caught only if its module also advertises over BLE.
- No connections. Nothing is read from a GATT server; every field comes from the advert or scan response.
- No Bluetooth 5 extended advertising or Coded PHY on the ESP32-WROOM (BLE 4.2). No build listens for it on the newer chips either (§5).
7.4 Signatures and confidence, type by type
This chapter follows docs/DETECTIONS.md section by section. Where the document’s per-type headline grade, the README and the code disagree, the disagreement is stated rather than smoothed over.
7.4.1 The grading rule
The confidence scheme was originally a property of the type; after an audit of every prefix against the IEEE MA-L registry it became a property of the matched signature. The document defines three grades two ways. The original definitions:
Table 8 — The confidence scheme was originally a property of the type; after an audit of every prefix against the IEEE MA-L registry it became a property of the matched signature. The document defines three grades two ways. The original definitions
| Grade | Original definition (source agreement) |
|---|---|
| High | Multiple independent sources, verified against the canonical research (Flock You, Marauder, Eye Spy) |
| Medium | One or two sources, plausible but not cross-verified |
| Low | Single source, anecdotal, may have false positives |
and the registry-based definitions that now apply to OUI rows:
Table 9 — and the registry-based definitions that now apply to OUI rows
| Grade | Registry definition (OUI rows) | Example |
|---|---|---|
| High | The block is registered to the company that makes the product | B4:1E:52 Flock Safety |
| Medium | Registered to a parent whose range is far wider than the product | Amazon blocks under CAMERA / RING |
| Low | A module or ODM vendor whose parts are in everything, or a block not in the IEEE registry | Espressif, Liteon, Silicon Labs |
A rule stated under the META section governs mixed types: “a type carrying signatures of mixed quality reports the lower one.” On screen the grade appears as HIGH CONF, MED CONF or LOW CONF, with an internal percentage of 90, 60 or 30.
Note — The grade is what makes ALERT FILTER useful. It is a minimum-confidence gate on the full-screen alert: set to HIGH ONLY, a passing Espressif device is still logged and counted but does not take over the screen. Before the per-row grading, confidence was constant per type and the filter “had nothing to filter on.”
7.4.2 FLOCK
Headline in DETECTIONS.md: one High prefix, the rest Low.
Evidence. Flock Safety cameras carry on-board WiFi (typically ESP32) that emits probe requests. Of the original 29 prefixes, exactly one, B4:1E:52, is registered to Flock Safety; sixteen are Espressif blocks, the rest Liteon, one Shenzhen Intellirocks, one SPECTRA - TEK (long mislabelled “Flock-Sierra”), one not in the registry and one locally administered. v1.24.0 added twenty more from Flock-You’s field list and a Flock camera firmware dump — thirteen Liteon, two USI, three Silicon Labs, one Espressif and Atheros’s factory-default block — for 49 prefixes with one High. 48:27:EA was deliberately left out: it is Samsung Electronics’ and would call every Samsung TV a Flock camera.
Non-OUI signatures: the exact SSID Flock; setup SSIDs flock-/FLOCK-; Flock’s 128-bit accessory GATT service e8ccbb38-9532-46a8-9fe5-1814df172e6f; BLE names Flock, Penguin, Pigvision, Flock_Setup, FS Ext Battery; BLE company ID 0x09C8 (XUNTONG, Flock’s radio supplier); and a battery pack that names itself by a bare ten-digit serial, logged Low.
Why the Low rows stay. “Flock really does build on ESP32, so an Espressif prefix really is evidence — it is just evidence shared with every dev board, smart plug and hobby project on earth, including another SquachWatch.”
Caveats. Flock has reportedly turned off Bluetooth on newer hardware, so BLE-name detection is opportunistic. The non-OUI Flock matches carry the FLOCK base grade, which confidenceFor() sets to High; DETECTIONS.md does not grade them individually.
Source: colonelpanichacks/flock-you (MIT), OUI research by @NitekryDPaul and the DeFlockJoplin community, every prefix’s registrant checked against the IEEE MA-L registry.
7.4.3 AXON
Headline: High.
Evidence. Axon body cameras broadcast AB2-, AB3-, AB4- SSIDs in pairing mode (an AXON- prefix is also matched), and use 00:25:DF (legacy Taser International) and E4:05:40. A BLE name containing Axon also matches. Grades since the audit: 00:25:DF High; E4:05:40 and 28:24:FF Low. The SSID match was graded High in v1.0 (“we catch the camera in pairing mode”), the OUI Medium in v1.0 because it depends on the camera being powered and transmitting.
Source: Axon Evidence admin documentation on body-worn-camera WiFi networks (public), the ESPHome community OUI registry, and a Gemini-compiled signature set.
7.4.4 META, shown as GLASS
Headline: Medium — and it was High before company IDs were added.
Evidence. Two independent signatures. The specific one is BLE service UUID 0xFD5F, advertised by Ray-Ban Meta glasses (“High on its own”). The broad one is four Bluetooth SIG company IDs that dedicated glasses-spotting apps key on: 0x01AB Meta Platforms, 0x058E Meta Platforms Technologies, 0x0D53 Luxottica, and 0x03C2 Snap (Spectacles).
Caveat. Meta uses the same IDs on other products, Quest headsets included, so a company-ID hit “means a Meta radio is nearby rather than a camera necessarily pointed at you.” The type was graded down deliberately under the mixed-quality rule. The vendor label distinguishes the two paths: RayBanMeta for the UUID, Meta/Meta-Tech/Luxottica/Snap for the company IDs.
Source: the Eye Spy project, the Hacker News “glasses to detect smart-glasses” project, banrays, and the company-ID list reported for the Nearby Glasses / AntiZuck apps.
7.4.5 SKIMMER
Headline: High.
Evidence. Cheap Bluetooth skimmers are built from HC-05/HC-06/HC-03-class modules (or RN42, BT04-A) that broadcast the module’s default name, and expose the Serial Port Profile UUID 0x1101. Three module OUIs (20:13:00, 98:D3:00, 00:1A:7D) also match at Low.
Grades. High for the BLE name match; Medium in v1.0 for the SPP UUID, “some skimmers use different SPP implementations.” Limit: BT Classic inquiry is off, so a skimmer whose module only speaks Classic is invisible.
Source: SparkFun Skimmer Scanner, the ESP32 Marauder “Detect Card Skimmers” wiki page, Eye Spy.
7.4.6 RAVEN
Headline: Medium.
Evidence. Raven devices advertise five proprietary service UUIDs outside the SIG-assigned range, 0x3100, 0x3200, 0x3300, 0x3400 and 0x3500, and only those five match. From v1.24.0 to v1.35.0 the firmware matched every value from 0x3100 to 0x3500, on the strength of 0x3101/0x3102 — UUIDs that belong to Raven’s GPS service rather than its advert. The wide range named an NVIDIA Shield a gunshot detector (issue #41).
Caveat in the project’s own words: “We match the UUIDs but haven’t verified them against a physical Raven device. Likely works.”
Source: the Flock You detection-datasets wiki.
Caution — The README’s “What it detects” table still describes RAVEN as “Any service UUID from
0x3100to0x3500.” That row predates the v1.36.0 fix; DETECTIONS.md and the code (five discrete values) are authoritative.
7.4.7 AIRTAG
Headline in DETECTIONS.md: High — but its own “Confidence in v1.0” line says Medium, and the code grades AIRTAG Medium. Medium is what the device shows.
Evidence. Apple’s company ID 0x004C alone is every Apple device, so a second check confirms a tag. The check, ported from nyanBOX by jbohack (MIT), scans the raw advert for either of two byte sequences:
Table 10 — Evidence. Apple's company ID 0x004C alone is every Apple device, so a second check confirms a tag. The check, ported from nyanBOX by jbohack (MIT), scans the raw advert for either of two byte sequences
| Pattern | Bytes | Meaning | What else matches |
|---|---|---|---|
| 1 | 1E FF 4C 00 | An Apple manufacturer AD structure exactly 30 bytes long | Apple’s Proximity Pairing advert (4C 00 07 19 …) — AirPods and similar |
| 2 | 4C 00 12 19 | Find My type 0x12, length 0x19 — a tag separated from its owner | — |
Why the false positive is accepted. A real AirTag test (registered to an Apple ID whose iPhone no longer exists, battery freshly reinserted) showed the tag broadcasting subtype 0x07 (Proximity Pairing), not 0x12, which starts only after a tag has been in “separated” state for a while. Rejecting 0x07 meant the detector could not see a tag “at the exact moment someone is most likely to be testing it.” The source names the proper fix — reading the device model ID inside the Proximity Pairing payload — and declines to guess model IDs from memory.
Other caveats. AirTags rotate their address frequently, so detection flickers. A raw-advert fallback catches the Find My structure even when it sits behind another AD structure or in a scan response.
7.4.8 SAMSUNG_TAG, GOOGLE_TAG and TILE
Table 11 — 4.8 SAMSUNGTAG, GOOGLETAG and TILE
| Type | Signature | Registry status | Grade | Main caveat |
|---|---|---|---|---|
SAMSUNG_TAG | Service UUID 0xFD5A | SIG-assigned to Samsung for SmartTag discovery; 0xFD59 (onboarding) and 0xFE59 (firmware update) not needed | High | None stated; “not shared with any of Samsung’s other product lines” |
GOOGLE_TAG | Service UUID 0xFEAA | Google’s long-standing Eddystone UUID, reused by the Find My Device Network | Medium | Retail, asset and museum Eddystone beacons share it — a match means “some Google-beacon-class device” |
TILE | Service UUIDs 0xFEED, 0xFEEC | Both SIG-assigned to Tile, Inc. | High | Was counted under AIRTAG in v1.0 |
Sources: the Bluetooth SIG 16-bit UUID registry; arXiv:2210.14702 (a security and privacy analysis of Samsung’s crowd-sourced location system that reverse-engineered the SmartTag protocol); Google’s Find My Device Network specification. The AirTags deep dive covers the four finding networks and their anti-stalking behaviour in depth; this device matches only the advertisement identifiers above.
7.4.9 ALPR
Headline: Medium for the type; the seven rows are High.
The correction that rewrote the section. Until v1.5.20 the only ALPR prefix was 00:0E:58, documented as Vigilant hardware. The IEEE registry assigns it to Sonos, Inc. (Goleta, California, February 2004). For eleven releases every Sonos speaker in range was logged as a licence-plate reader. It was removed, not corrected, “because there was nothing to correct it to.”
Evidence now. Blocks registered to the vendors that sell these systems, read per vendor from the registry: Motorola Solutions (which absorbed Vigilant) 00:04:7D, 00:18:85, 00:1F:92, 4C:CC:34, plus B8:E2:8C (Motorola Solutions Malaysia); Genetec (AutoVu) 00:BF:15, 0C:BF:15. Cross-checked against FlipDeFlock.
Caveat. “The blocks are certain; what is not certain is that a given device on one is a plate reader rather than some other product from a large vendor.” Motorola Solutions sells radios and much else.
7.4.10 CAMERA and RING
CAMERA — graded per row. Seventeen OUIs: Wyze, Arlo, Blink, Hikvision, Amazon, Realtek and Tuya on the consumer side; Verkada (E0:A7:00), Avigilon Alta (70:1A:D5) and Axis (00:40:8C, B8:A4:4F) on the commercial side. After the audit: Wyze, Hikvision, Verkada, Avigilon and Axis blocks High; Amazon’s two Medium; Realtek, Tuya, Arlo, Blink and Wyze’s module block Low. Two rows were relabelled: F0:27:2D (“Hikvision”) is Amazon’s; 28:57:BE (“Reolink”) is Hikvision’s. Sources: the author’s earlier Cardputer-CSI-Human-Detector (MIT), Gemini additions for Tuya/Wyze-module prefixes, and the IEEE registry via maclookup.app.
RING — High. Fifteen prefixes: Ring LLC’s full MA-L registration (13 blocks, registered 2019-03-01, cross-checked on netify.ai and maclookup.app) at High, plus FC:65:DE and 68:37:E9, inherited from CAMERA, at Medium.
Caution — An OUI match says what company registered the radio’s address block. It says nothing about whether the device is a camera, where it points, or whether it records. Consumer camera brands overwhelmingly use the vendor’s real MAC, which is why this works at all; a camera on a randomised or locally-administered address, a non-WiFi camera, or a camera that records to SD with no radio, is invisible here. The CameraDetection deep dive covers the techniques that reach those classes.
7.4.11 IBEACON
Headline: High, off by default — the only type that ships disabled.
Evidence. An exact format: manufacturer data 4C 00 02 15, then a 16-byte proximity UUID, 2-byte major, 2-byte minor and a measured-power byte — 25 bytes. The code checks the length and the four header bytes. Before this type existed, iBeacons were matched by Apple’s company ID, failed the AirTag check and were dropped, “so the most numerous tracking transmitter most people walk past all day was the one thing the detector deliberately ignored.”
What the log shows. Six hex digits of the proximity UUID, then major.minor. The UUID identifies the deployment — “the same first half in two different places is the same operator.” Major and minor are big-endian inside the block, though the company ID before them is little-endian; that is Apple’s format.
Why off. Volume, not importance: “one shop can put more beacons in range than this device would otherwise see all week,” and because the ALERT screen is gated on confidence rather than type, an exact-match type would let every shelf in a supermarket take over the display. What it claims: “High for ‘this is an iBeacon’. That is all it claims.” A beacon does not follow anyone; an app that is already installed decides to care.
7.4.12 HACKER
Headline: graded per signature. One type for Flipper Zero, Pwnagotchi, WiFi Pineapple and the ESP deauther family, because the useful statement is “there is a tool for attacking radios in this room.” It shares the red of DEAUTH and EVILTWIN rather than the cyan of the cameras.
Flipper Zero — four signatures, three exact:
Table 12 — Flipper Zero — four signatures, three exact:
| Signature | Value | Grade |
|---|---|---|
| BLE service UUID | 0x3081 / 0x3082 / 0x3083, one per case colour | High |
| BLE company ID | 0x0E29, Flipper Devices Inc. | High |
| MAC OUI | 0C:FA:22, FLIPPER DEVICES INC | High |
| Advertised name | begins Flipper | Medium — the owner can change it |
Two constants the ecosystem gets wrong. ESP32 Marauder’s source comments Flipper’s company ID as 0x0FBA, which is registered to Cosonic Intelligent Technologies (headsets); Flipper Devices is 0x0E29. Wall-of-Flippers checks 80:E1:26 and 80:E1:27, neither of which appears in the IEEE registry at all. Neither ships, and test/hacker_test.cpp pins both so a re-import fails the tests.
Pwnagotchi — High, because it volunteers. A pwnagotchi announces itself to its own kind with a plain-ASCII JSON blob in a vendor information element of its beacons, carrying name, version, uptime, handshake count and whether deauth is on. SquachWatch does not parse JSON in the promiscuous callback; two byte scans suffice — the key pwnd_tot must be present, and the "name" value is extracted. Because the name is attacker-controlled and lands in a rendered field, any byte outside printable ASCII rejects it. The detection posts the transmitter address, not the BSSID, and skips the evil-twin tracker. “Nothing else on this device is a self-declaration.”
Pineapple and deauthers — circumstantial:
Table 13 — Pineapple and deauthers — circumstantial:
| Signature | Value | Grade |
|---|---|---|
| Pineapple management AP | SSID prefix Pineapple_ | Medium |
| Deauther control AP | SSID prefix pwned | Medium |
| Hak5 MACs | 02:C0:CA, 02:13:37 | Low |
Hak5 hold no IEEE registration; their hardware is OpenWrt on ODM boards. The two addresses are locally administered (bit 1 of the first octet set), so “by construction they identify no vendor and anyone can set them.” The test suite enforces that no locally-administered row is graded above Low.
Deliberately not in this bucket: bare Espressif silicon (a nyanBOX, an ESP32 Marauder, an M5Stack and a SquachWatch “are the same chip”); receive-only and wired gear that no firmware can see — HackRF, Ubertooth One, RTL-SDR, Proxmark3, Wi-Fi Coconut, an Alfa card in monitor mode, Bash Bunny, Rubber Ducky, Key Croc, Shark Jack, LAN Turtle, Packet Squirrel, and the O.MG cable except while its AP is up; the WiFi Pineapple Pager (no published signature, “and a guess is exactly how the Sonos entry happened”); and nyanBOX, whose “Device Networking” broadcast is a genuine hook but whose format is unpublished and needs one capture from real hardware.
7.5 Remote ID decoding
DRONE is the one detection that reads the content of what it matched. ASTM F3411 Remote ID adverts carry a 25-byte message; SquachWatch decodes three message types and accumulates them per transmitter address, because a drone sends them as separate adverts in rotation.
Table 14 — 5. Remote ID decoding
| Transport | Heard? | Where the message is | Decoded |
|---|---|---|---|
| Bluetooth 4 Legacy advertising | Yes | AD type 0x16 service data under UUID 0xFFFA, one message per advert | Full: serial, airframe type, position, altitude, operator position |
| WiFi Beacon | Yes (since v1.24.0) | ASD-STAN vendor element (FA:0B:BC, type 0x0D), a pack of messages | Named by the serial in the log |
| Bluetooth 5 Long Range (Coded PHY, extended advertising) | No | — | ESP32-WROOM is BLE 4.2; “no build listens for it on the newer chips either” |
| WiFi NAN | No | Action frames, not beacons | — |
Table 15 — 5. Remote ID decoding
| Message | Fields used | Scaling (from opendroneid-core-c) |
|---|---|---|
| Basic ID | Aircraft serial (up to 20 chars), UA type (QUAD, PLANE, HELI…) | — |
| Location | Aircraft latitude, longitude, geodetic altitude | Degrees × 10⁷; altitude in half-metres offset by 1000 |
| System | Operator latitude and longitude — where the pilot is standing | Degrees × 10⁷ |
The bug that defined this section. Until v1.24.0 the check looked for 0xFFFA among the advert’s listed service UUIDs. Because the 25-byte message fills all 31 bytes of a legacy advert, a real drone has no room to list it, and “no real drone was ever reported before then.” It now reads the service data. The source also warns that a field not yet received must not be drawn as zero — “zero is a real coordinate in the Gulf of Guinea.”
Grade: Medium. Compliance is still rolling out, and the Long Range transport is unreachable on this hardware.
7.6 Behavioural detections
7.6.1 DEAUTH, the burst counter
A single deauthentication frame is normal: a phone leaving, an AP restarting. The firmware counts frames in a rolling window and fires on a burst:
Table 16 — A single deauthentication frame is normal: a phone leaving, an AP restarting. The firmware counts frames in a rolling window and fires on a burst
| Parameter | Value |
|---|---|
| Threshold | 6 frames |
| Window | 3000 ms, rolling (resets after a gap longer than the window) |
| Cooldown before re-firing | 15000 ms |
| Grade | Medium — “a real burst pattern, but the threshold/window are still heuristic” |
| Address logged | addr2 of the frame that tripped it (the attacker, or a spoofed AP address) |
hits field | Frames in the triggering window, not repeat sightings |
A second flood from the same source lands on the same LOG row rather than creating a new one. The window bookkeeping keeps running while DEAUTH is disabled, so re-enabling it does not fire on a stale count. DEAUTH keeps its own counter column because “a deauth flood is a burst rate, and plenty of things that are not a Pineapple produce one.” Note the counter is not per-source: six deauths from six different transmitters inside three seconds also trip it.
7.6.2 EVILTWIN, the encryption disagreement
The obvious test — same SSID, different BSSID — fires constantly on every mesh network and dual-band AP, and “confirmed on a real mesh here it fires constantly.” Even same-SSID-different-OUI over-fires because vendors ship across several blocks. The shipped test adds the one thing a mesh never does: disagree with itself about security.
beacon with SSID S from BSSID B, Privacy bit P
│
├─ S not on file ───────────────────────► record (S, B, P) as baseline; no alert
│ (24-entry table, round-robin eviction)
├─ S on file with same BSSID ───────────► refresh P (a real security change re-baselines)
├─ S on file, BSSID from same vendor ──► mesh node / other band; never flagged
├─ S on file, different vendor, same P ► can't tell rogue from mixed-vendor network; quiet
└─ S on file, different vendor, P differs ► EVILTWIN (name = the impersonated SSID)
The practical attack this catches is an open twin of an encrypted network — exactly what a captive “evil portal” is. The cost, stated plainly in the source: an attacker who advertises matching encryption walks past, though that attacker also cannot complete a handshake. A second limitation: whichever BSSID is seen first becomes the baseline, so if a rogue is already up when the board arrives, the real AP trips the alert. “The device is telling you two boxes claim one name and disagree about security, which is the finding; it can’t tell you which of them is lying.”
confidenceFor() grades EVILTWIN High as “a specific and hard-to-fake” pattern. The evil-twin check runs ahead of the OUI and SSID lookups and wins over them, because a rogue AP on commodity hardware would otherwise be logged as whatever its OUI matched.
7.7 Spam and flood handling
At a hacker convention on 2026-09-26, someone broadcast fake AirTags and later fake Google tags, each burst from a fresh address: 91 AirTag alerts in 22 minutes, 50 of 69 addresses heard exactly once, and the only remedy was to switch AirTags off. The response is include/spam_watch.h, built on one observation: “A real tag keeps announcing for as long as it is near. A fake is heard for one burst and never again.”
Table 17 — 7. Spam and flood handling
| Constant | Value | Role |
|---|---|---|
SHORT_MS | 10 000 ms | A log row heard for less than this, with one hit, then gone, is evidence of a fake |
TRIP16 | 112 (seven fakes × 16) | Evidence that trips the flood state |
HALF_MS | 90 000 ms | Evidence halves every ninety seconds |
BURST_TRIP | 40 | Or this many new addresses of one type in a minute |
QUIET_MS | 300 000 ms | Five minutes without evidence ends the flood |
Eight fakes in quick succession trip it; a flood of one every fifteen seconds (the convention’s rate) trips it in about ten. Once tripped, the type raises one alert carrying a “SPAM: N FAKE TAGS” banner, and every later sighting of that type is still logged but does not interrupt. The trade is explicit: real tags of that type are held along with the fakes for those five minutes, “and the alert says so.” A device on the watch list always gets through (Vol 8 §3).
7.8 False positives, blind spots and limits
Known false positives, each documented by the project:
Table 18 — Known false positives, each documented by the project
| Symptom | Cause | Grade shown | What to do |
|---|---|---|---|
| Another SquachWatch, ESP32 Marauder, nyanBOX, smart plug or dev board logged as FLOCK | 48 of 49 Flock prefixes are module-vendor blocks (17 Espressif) | Low | ALERT FILTER to MED+ or HIGH ONLY |
| AirPods or other Apple accessories as AIRTAG | Pattern 1 matches the 30-byte Proximity Pairing advert | Medium | Accepted by design; IGNORE the accessory |
| A Quest headset or other Meta product as GLASS | Company-ID path | Medium | Check vendor label: RayBanMeta is the specific path |
| An Eddystone retail or museum beacon as GOOGLE_TAG | Shared 0xFEAA | Medium | Context; IGNORE if fixed |
| Any Motorola Solutions or Genetec product as ALPR | Registry block certain, product class not | Medium type | Context — is there a pole-mounted camera? |
| An Amazon Echo, Fire TV or Kindle as CAMERA or RING | Amazon blocks | Medium | Check the vendor label |
A home network named pwned… as HACKER | SSID prefix | Medium | Context |
| A BLE device whose name contains Flock, Penguin, Axon or Flipper | Substring name matching | Type grade (Flock High, Axon High, Flipper Medium) | Inspect the name on the card |
| The real AP flagged as an evil twin | The rogue was there first and became the baseline | High | Treat the pair as the finding |
| Your own AirTag or doorbell | True positive | — | IGNORE |
What the device cannot see at all:
Table 19 — What the device cannot see at all
| Blind spot | Reason |
|---|---|
| 5 GHz and 6 GHz WiFi | ESP32-WROOM is 2.4 GHz only; the two ESP32-C5 boards add 5 GHz |
| Bluetooth Classic-only devices (some skimmers) | Inquiry not enabled alongside NimBLE |
| Bluetooth 5 Long Range Remote ID | BLE 4.2 radio; not implemented on newer chips |
| Cellular / LTE backhaul (Flock’s LTE modem, 4G cameras) | No cellular receiver |
| Sub-GHz, LoRa (except the T-Watch builds, receive-only), analog video | No radio for those bands |
| SD-only cameras, wired cameras, anything not transmitting | Nothing to hear |
| Devices on randomised or locally-administered MACs | No vendor in the address |
| Products with no signature yet (Pineapple Pager, nyanBOX) | Deliberately not guessed |
| Receive-only attack tools (HackRF, RTL-SDR, Ubertooth, Proxmark3) | They emit nothing identifying |
Caution — A detection is evidence that a radio with a particular identifier is within range, at a particular RSSI, at a particular time. It is not evidence of what the device is pointed at, who operates it, whether it is recording, or that it is following anyone. The Low grades exist because the project’s own audit found that copied constants had shipped for releases —
00:0E:58“Vigilant” was Sonos for eleven releases. The README’s own status line: Flock, Axon, skimmer and camera glasses are the reliable targets; Remote ID and iBeacon are exact-format matches; Raven, generic ALPR and the Google tracker network are best-effort.
Tip — The project asks for false-positive reports with the type, the confidence, the first half of the address, the name if any, and what the thing actually was — with PRIVACY MODE on before photographing the screen.
7.9 Cheatsheet updates from this volume
┌──────────────────────────── SQUACHWATCH — WHAT A MATCH MEANS ─────────────────────────────┐
│ GRADES HIGH = block/UUID registered to the product's maker, or exact format │
│ MED = parent company far wider than product, shared UUID, or heuristic │
│ LOW = module/ODM vendor (Espressif, Liteon…), locally-admin, not in registry │
│ OUI ROWS 97 total: 33 High / 4 Med / 60 Low. FLOCK: 49 rows, ONE High (B4:1E:52) │
│ RADIOS 2.4 GHz WiFi (hop 1-13, weighted) + BLE 4.2 adverts. 5 GHz only on ESP32-C5 │
│ WIFI probe→addr2 data→addr1+addr2 beacon→BSSID+SSID+Privacy deauth→burst ctr │
│ BLE mesh peer > mfg ID (Apple→AirTag check→iBeacon) > raw AirTag > RemoteID > │
│ 16-bit UUID / Flock 128-bit > name (some names are SUBSTRING matches) │
│ DEAUTH ≥6 frames / 3 s rolling, 15 s cooldown, MED │
│ EVILTWIN same SSID, different vendor, DISAGREE on encryption, HIGH (first seen = base) │
│ SPAM one-burst addresses → trip at ~7 fakes (or 40 new/min); 5 min quiet ends it │
│ DRONE BT Legacy 0xFFFA svc data + WiFi beacon. Decodes serial, pos, alt, OPERATOR. │
│ NOT BT5 Long Range, NOT WiFi NAN │
│ OFF IBEACON ships disabled (volume). Turn on in TYPE FILTER │
│ BLIND 5/6 GHz (most boards), BT Classic, cellular, SD-only cams, randomised MACs │
└────────────────────────────────────────────────────────────────────────────────────────────┘
Table 20 — 9. Cheatsheet updates from this volume
| Remember | Value |
|---|---|
| Confidence labels on screen | HIGH CONF / MED CONF / LOW CONF |
| Exact SSID | Flock only; everything else in the SSID table is a prefix |
| Raven UUIDs | 0x3100, 0x3200, 0x3300, 0x3400, 0x3500 — five values, not a range |
| Flipper company ID | 0x0E29 (not 0x0FBA) |
| Flipper OUI | 0C:FA:22 only (not 80:E1:26/27) |
| Removed for cause | 00:0E:58 (Sonos, was “Vigilant”); 48:27:EA (Samsung) never added |
Comments (0)