Files
VIGIL/README.md
T
Kara Zajac ad81964398
Build APK / build (push) Waiting to run
Implement rotation-clone presence engine (#1) + unit tests + CI tests
- detect/PresenceEngine.kt: identity-agnostic streaming detector for key-rotating clones (novel-churn gate, seamless-handover chain, co-movement gate, coherence). Full design in docs/detection-rotation-clone.md.
- Wired into ScanService as a parallel signal; CONFIRMED verdict raises a distinct rotating-tracker alert (softer tiers stay silent pending real-capture validation).
- PresenceEngineTest: synthetic clone (moving -> CONFIRMED), stationary clone (gated), single real tracker + far-ambient (CLEAR).
- CI now runs :app:testDebugUnitTest so the engine has behavioural verification, not just a compile check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0187UiEtasyowhEBYs6s9iF5
2026-07-15 00:35:37 -04:00

146 lines
7.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)**.
It's now implemented as a first version in `detect/PresenceEngine.kt` (unit-tested
against synthetic clone/ambient traces) and wired into the scan service to raise a
distinct rotating-tracker alert; field-tuning against real captures is what remains.
## 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)
```
## Paper
A full technical write-up — the per-ecosystem detection methodology (how VIGIL
tracks each tracker type) and the harder modeling engine for what AirGuard and the
built-in detectors miss — is in **[paper/vigil.md](paper/vigil.md)**.
## 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, the
rotation-clone presence engine (v1, unit-tested), foreground service, Compose UI.
**Not yet:** field-tuning the clone engine on real captures, 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.