09 · Active config — km range + remote kill-switch (live, 2026-06-28)
The investigation’s headline question, answered across the whole installed cohort instead of one app: for every Appmiral app that carries the Crowd Connected location SDK, what geofence km range is armed, and is the remote kill-switch ON or OFF right now? This reads each app’s own server-pushed config out of its private on-device storage (root) — we never contact Crowd Connected. It extends the single-app
04-live-capture-2026-06-26.mdto the live fleet.
Evidence header
=== EVIDENCE CAPTURE HEADER (APK harvest) ===
Captured by: adb over USB, on-device pull
Host wall-clock: 2026-06-28T15:43:11+02:00
Device time: Sun Jun 28 15:43:12 CEST 2026
Device model: Pixel 10a (Android 16)
Device build: google/stallion/stallion:16/CP1A.260505.005/15081906:user/release-keys
ADB serial: 5B161JEA308016
Source data (this folder):
data/cc_live_config_2026-06-28.csv /
.json. Raw Room DBs + CrowdConnected.xml
prefs are private (they contain device/account identifiers); only the
redacted decoded config is published. Reader:
../../tools/apk-harvest/read-cc-config.py.
Result — the 6 apps with a populated Crowd Connected config
Of the 140 installed apps, 6 had a live, cached Crowd Connected config at capture time (an app only has one after it has been launched and the SDK has polled its backend). Identifiers redacted; all values are server-pushed.
| App | Kill-switch | Active km range (radius2) |
Inner ring (radius1) |
Geofence centre | Poll / high / med (s) | Schema |
|---|---|---|---|---|---|---|
com.appmiral.ttfestival |
🔴 ON | 60 km | 820 m | 52.99, 6.56 (Assen) | 120 / 30 / 1800 | new Settings.active=1, SDK 2.1.0, BACKGROUND |
com.appmiral.mdlbeast |
🟢 OFF | 60 km (armed) | 282 m | 21.49, 39.19 (Jeddah, SA) | 120 / 30 / 1800 | new Settings.active=0, SDK 2.1.0, BACKGROUND |
com.appmiral.rockforpeople |
🟢 OFF | 40 km (armed) | 1354 m | 50.25, 15.84 (Hradec Králové, CZ) | 120 / 30 / 1800 | new Settings.active=0 |
com.appmiral.teamnl |
🟢 OFF | 50 km (armed) | 183 m | 45.45, 9.16 (Milan, IT) | 120 / 30 / 300 | new Settings.active=0, SDK 2.1.0, BACKGROUND |
com.appmiral.museumnachtamsterdam |
🟢 OFF | 80 km (armed) | 10155 m | 52.36, 4.89 (Amsterdam) | 120 / 30 / 1800 | legacy activeAPIResponse=120,false,… |
com.appmiral.dutchgp |
🟢 OFF | — (no zone cached this run) | — | — | 120 / — / — | legacy activeAPIResponse=120,false,… |
What this shows
- Only TT Festival was live.
Settings.active=1with a 60 km collection ring centred on Assen and the SDK in background mode — armed far beyond the festival grounds. This corroborates04-live-captureand the transmission-to-firehose.us-east-1evidence there. - The kill-switch is a remote decision. Every other app shipped an armed
geofence (40–80 km) yet was toggled OFF by Crowd Connected’s backend at
capture time. Same SDK, same local
geoEnabled=1/sendPositionData=1flags — the only thing that differs is the server-pushed switch. The operator can flip any of these ON remotely, exactly as TT Festival is. - The geofences are city/region-scale, not venue-scale. 40–80 km rings cover whole metropolitan areas (an 80 km ring on Amsterdam reaches the German border side of the Randstad; 60 km on Assen covers most of the north-east Netherlands).
Where the switch lives (two schemas)
The “on/off” is not the local geoEnabled/sendPositionData flags. It is the
server-pushed activation, stored in one of two places depending on SDK age:
- New SDK (2.1.0) — a
Settings.activecolumn in theCrowdConnected-RoomDatabaseRoom database.active=1→ ON,active=0→ OFF. (TT Festival, mdlbeast, rockforpeople, teamnl.) - Legacy SDK — no
activecolumn; the activation is a heartbeat stringactiveAPIResponseinshared_prefs/CrowdConnected.xml, formatted<pollSec>,<active>,<…>,<ts>.120,false,…→ OFF. (museumnachtamsterdam, dutchgp.) The decoder reads the prefs file for these apps.
The km range (GeoZone.radius2, in metres ÷ 1000 = km) and centre live in the
Room DB for both schemas; radius1 is the inner high-frequency ring.
Why this is broader than “12 apps”
The static scan (see 08-integration-matrix) shows the
Crowd Connected capability — AWS Cognito + Kinesis Firehose + Google Fused
Location — is bundled in ~139/140 apps, even though the literal
com.crowdconnected package survives obfuscation in only 9. A given app only
produces a live km/kill-switch reading after it has been launched and has
polled config; the 6 above are simply the ones that had been opened. Any of the
other CC-capable apps would populate the same config once launched. So the live
table is a floor, not a ceiling, on how many apps can be remotely armed.
Determinism / caveats (for replication)
- Server config drifts. Re-running later may show different switch states or different km values — the operator changes them server-side. A non-matching switch is expected and itself proves remote control.
- Only launched apps have a DB. Installed-but-never-opened apps have no cached
config (recorded as “no runtime config / not launched”).
dutchgphad a heartbeat but an emptyGeoZonetable this session → no km value. - Redaction.
deviceIdis reduced to a salted prefix; alias values (app_user_id,sns_user_id) are dropped, keys kept. TheappKey(a tenant key, not personal) is retained for cross-referencingcrowd-connected-endpoints.md.
Re-verify one app (root, ~1 min)
$pkg = "com.appmiral.ttfestival"
adb shell su -c "cp /data/data/$pkg/databases/CrowdConnected-RoomDatabase /sdcard/cc.db"
adb pull /sdcard/cc.db
python -c "import sqlite3;c=sqlite3.connect('cc.db');print('Settings',[r for r in c.execute('select active,geoEnabled,sendPositionData from Settings')]);print('GeoZone',[r for r in c.execute('select lat,lng,radius1,radius2 from GeoZone')])"
# legacy apps (no `active` column) — read the heartbeat instead:
adb shell su -c "cat /data/data/com.appmiral.museumnachtamsterdam/shared_prefs/CrowdConnected.xml" | Select-String activeAPIResponse
Full automated reader + reproduction:
10-apk-harvest-reproduction.md.