VIGIL 0.1.0 — temporal counter-tracking (prototype)
Android app that detects personal item-trackers (AirTag, Tile, Samsung SmartTag, Google Find My Device / DULT) following the user over time. Sibling to OVERWATCH: OVERWATCH is spatial, VIGIL is temporal. - BLE scan + per-ecosystem wire-format parsing (Apple 0x004C/0x12, FMDN 0xFEAA, Samsung 0xFD5A, Tile 0xFEED/FEEC, DULT 0xFCB2) - Room temporal store (14-day sightings), co-movement evaluator with RSSI proximity gate - Allowlist + learned offline baseline (auto-trust household tags) - Foreground service, Compose UI (Catppuccin Mocha) - Privacy: NO INTERNET permission — fully on-device - Reuses OVERWATCH Gradle setup + committed debug keystore - docs/detection-rotation-clone.md: design for the rotation-clone presence engine (#1) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0187UiEtasyowhEBYs6s9iF5
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# VIGIL
|
||||
|
||||
**What's been following you?**
|
||||
|
||||
A native Android (Kotlin) app that watches for personal item-trackers — Apple
|
||||
AirTags, Tile, Samsung Galaxy SmartTags, and Google Find My Device / DULT tags —
|
||||
that are **travelling with you over time**. It is the temporal counterpart to
|
||||
[OVERWATCH](https://github.com/KaraZajac/OVERWATCH):
|
||||
|
||||
> **OVERWATCH is spatial** — *what surveillance is watching this place, right now.*
|
||||
> **VIGIL is temporal** — *what has been with me, across time and places.*
|
||||
|
||||
A tracker being *near* you means nothing — trackers are everywhere. The signal is
|
||||
**persistence**: the same device seen at many of *your* distinct locations, over a
|
||||
sustained window, while close enough to actually be on you. VIGIL is built around
|
||||
that idea.
|
||||
|
||||
> ⚠️ **Prototype / work in progress.** The scanning, parsing, temporal store,
|
||||
> co-movement engine, allowlist, and learned baseline are in place; the app builds
|
||||
> in CI. Wire-format offsets and thresholds are from the research brief and need
|
||||
> validation against real hardware (see [Status](#status)).
|
||||
|
||||
---
|
||||
|
||||
## Privacy first — like OVERWATCH
|
||||
|
||||
VIGIL requests **no `INTERNET` permission at all.** There is no server, no account,
|
||||
no telemetry. Every tracker, every sighting, and the entire learned baseline live
|
||||
in an on-device SQLite database and never leave the phone. It listens only — it
|
||||
never transmits, probes, or interferes with any device.
|
||||
|
||||
---
|
||||
|
||||
## What it detects
|
||||
|
||||
Four native BLE wire formats today, plus the emerging unified DULT format. VIGIL
|
||||
recognises each ecosystem and, where the ecosystem signals it, filters to the
|
||||
**separated-from-owner** state — the only state in which a following tracker is
|
||||
detectable (see the research brief).
|
||||
|
||||
| Ecosystem | BLE signature | Separated-state signal | Passive re-link window |
|
||||
|---|---|---|---|
|
||||
| **Apple Find My / AirTag** | mfg data, company `0x004C`, type `0x12` | status byte "maintained" bit cleared | ~24 h (key static per day) |
|
||||
| **Google Find My Device** | service data `0xFEAA`, frame `0x40`/`0x41` | frame `0x41` = separated (cleartext) | ~24 h once separated |
|
||||
| **Samsung Galaxy SmartTag** | service data `0xFD5A` | state byte (lost / overmature-lost) | ~24 h once overmature |
|
||||
| **Tile** | service data `0xFEED` / `0xFEEC` | none — static MAC, always findable | indefinite (static MAC) |
|
||||
| **DULT (unified, emerging)** | service data `0xFCB2` | near-owner bit (byte 14 LSB) | ~24 h separated |
|
||||
|
||||
Chipolo, Pebblebee, eufy, Motorola, etc. inherit the signature of whichever
|
||||
network (Apple or Google) their SKU joined — VIGIL detects the **network**.
|
||||
|
||||
## How it decides
|
||||
|
||||
Each parsed sighting is geotagged with a coarse fix and written to the temporal
|
||||
store. A tracker is escalated `OBSERVED → SUSPICIOUS → ALERTING` only when it
|
||||
clears the **co-movement test**:
|
||||
|
||||
- **≥ 3 sightings** (debounced to one per 15 min), across
|
||||
- **≥ N distinct places** (geohash-7 cells; N = 2/3/4 by sensitivity), over
|
||||
- **≥ T minutes** (30/45/90 by sensitivity), **and**
|
||||
- an **RSSI proximity gate** — it must have been genuinely close (on-body/in-bag)
|
||||
at least once. This is the piece AirGuard omits, and it rejects "a Tile in a
|
||||
passing car."
|
||||
|
||||
Two trust signals suppress false alarms:
|
||||
|
||||
- **Allowlist ("This is mine").** Tap a tracker to mark it approved — your own
|
||||
AirTag, your partner's Tile — and it never alerts again.
|
||||
- **Learned offline baseline.** VIGIL learns the places you dwell (home, work) as
|
||||
*anchors*, and a tracker seen at an anchor across several distinct days is
|
||||
auto-marked **Known (home)**. So the household tags that are always around you
|
||||
fall silent on their own, entirely on-device.
|
||||
|
||||
## The hard part — catching clones (problem #1)
|
||||
|
||||
Every shipping detector (AirGuard, iOS, Android's built-in) keys on **device
|
||||
identity**. A key-rotating clone (e.g. Positive Security's *Find You*: ~2,000
|
||||
Find My keys, a new one every 30 s) looks like 2,000 one-off devices and evades
|
||||
all of them — it tracked a phone for 5 days with zero alerts.
|
||||
|
||||
VIGIL's headline goal is to detect the **attack, not the device**: a rotating
|
||||
clone is one physical radio holding an unbroken, close-range, co-moving RF
|
||||
*presence* even as its identity churns thousands of times faster than any
|
||||
standards-compliant tracker is allowed to. The full algorithm — a CUSUM churn
|
||||
trigger, an identity-agnostic presence-track confirmer, and a co-movement gate,
|
||||
plus the "identity-path × churn-path squeeze" that leaves no safe rotation rate —
|
||||
is designed in **[docs/detection-rotation-clone.md](docs/detection-rotation-clone.md)**.
|
||||
The `detect/` package ships the co-movement v1; the presence engine slots in next.
|
||||
|
||||
## Architecture
|
||||
|
||||
VIGIL reuses OVERWATCH's proven scanning stack and diverges where the temporal
|
||||
mission demands it (a persistent database instead of an in-memory store).
|
||||
|
||||
```
|
||||
scan/TrackerSignatures.kt BLE hex signatures for every ecosystem
|
||||
scan/TrackerParser.kt ScanResult -> TrackerObservation (four wire formats)
|
||||
scan/BleTrackerScanner.kt filtered BLE scan (screen-off capable) -> observations
|
||||
service/ScanService.kt foreground service; geotags + persists + alerts
|
||||
data/db/VigilDatabase.kt Room: trackers, sightings (14-day), baseline places
|
||||
data/TrackerRepository.kt ingest + baseline + evaluate on every sighting
|
||||
detect/CoMovementEvaluator the temporal co-movement test + RSSI proximity gate
|
||||
detect/BaselineManager.kt learns anchor places -> auto-trusts household tags
|
||||
data/location/LocationProvider.kt fused location (ported from OVERWATCH)
|
||||
ui/ + MainActivity.kt Compose UI (Catppuccin Mocha)
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
Standard Android/Gradle. A committed debug keystore signs CI and local builds
|
||||
identically.
|
||||
|
||||
```bash
|
||||
./gradlew :app:assembleDebug
|
||||
# APK -> app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
CI (`.github/workflows/build.yml`) builds a debug APK on every push and attaches
|
||||
an APK to a GitHub Release on `v*` tags.
|
||||
|
||||
## Status
|
||||
|
||||
Prototype. In place: BLE scan + filters, per-ecosystem parsing, Room temporal
|
||||
store, co-movement evaluator with RSSI gate, allowlist, learned baseline,
|
||||
foreground service, Compose UI. **Not yet:** the rotation-clone presence engine
|
||||
(designed, not wired), GATT play-sound / DULT get-identifier, and empirical
|
||||
validation. Before trusting the parser, capture real devices with nRF Connect —
|
||||
the SmartTag2 offsets are inferred from gen-1, and the Tile/AirTag reversing is a
|
||||
couple of years old.
|
||||
|
||||
## Credits & prior art
|
||||
|
||||
Stands on the shoulders of **[AirGuard](https://github.com/seemoo-lab/AirGuard)**
|
||||
(TU Darmstadt / Seemoo-lab) and the SEEMOO Find My research, the IETF **DULT**
|
||||
working group, Adam Catley's AirTag teardown, and Positive Security's *Find You*
|
||||
clone research. VIGIL's aim is to go *beyond* AirGuard on the attacks it
|
||||
structurally cannot catch — see the design doc.
|
||||
Reference in New Issue
Block a user