Appmiral platform — full deep dive (114 apps)
Phase 2 of the comparison. Phase 1 (
01-on-device-cohort.md) compared 11 festival apps installed on a single device. This phase extends the analysis to the entire Appmiral festival-app stack scraped from https://appmiral.com/our-work, then closes the loop with a runtime probe of the 7 CrowdConnected-club apps that could be sideloaded onto a real device (Pixel 10a, Android 16, 23 Jun 2026).
🚨 Headline finding (runtime, 23 June 2026)
The TT Festival app was caught actively exfiltrating location data to AWS in the US during this analysis. With the app freshly launched on a phone sitting in NL (distance to Assen ~150 km), the CrowdConnected SDK:
- Pulled valid settings from
mobile.crowdconnected.com/prod/settings/dd54d39d/...— the static-analysis-onlyactive=falsefrom the earlier June check has flipped toactive=true. - Initialised the geofence observer with surface UUID
e52e1cbe-0260-4469-a68c-b0570da3f779. - Started the outer-zone observer at 900 s poll interval (15 min) — then immediately transitioned to “high settings” with a 30 s poll interval.
- Signed Sigv4 requests to
firehose.us-east-1.amazonaws.comand calledFirehose_20150804.PutRecordBatchcarrying 15 964 bytes of buffered positions — the first batch timed out, the SDK retried. - Also signed Sigv4 requests to
sns.eu-west-1.amazonaws.com(push registration).
The raw evidence is in data/ttfestival_runtime_logcat_2026-06-23.log (502 KB,
adb logcat -d -v threadtime, captured 23 Jun 2026 ~22:07 UTC). Key lines:
GEO_OBSERVER: Setting Zones
GEO_OBSERVER: Setting geo surface: e52e1cbe-0260-4469-a68c-b0570da3f779
GEO_OBSERVER: Started geo observer with frequency: 900 seconds
GEO_OBSERVER: Transitioning to high settings
GEO_OBSERVER: Started geo observer with frequency: 30 seconds
KINESIS : Sending batch of positions via kinesis
KINESIS : 15964 Bytes stored on disk
AWS4Signer: host:firehose.us-east-1.amazonaws.com
AWS4Signer: x-amz-target:Firehose_20150804.PutRecordBatch
AWS4Signer: 20260623/us-east-1/firehose/aws4_request
This is the first evidence that the TT app’s CrowdConnected pipeline is no longer
dormant. It also directly contradicts the privacy policy’s claim that “all personal
data is stored in EU (eu-west-1), with Cloudflare as the only exception” — the SDK is
actively signing requests for firehose.us-east-1.amazonaws.com (Virginia, US).
Runtime state per CrowdConnected-club app (11 of 12 probed)
Update — 24 June 2026. All remaining installable CC-club apps were sideloaded (full XAPK bundles pulled via a Cloudflare-bypassing Playwright/Firefox downloader, then
adb install-multiplewith the config splits) and re-probed. 11 of the 12 CC-club apps are now installed and probed — onlycom.appmiral.werchterparkliferemains uninstallable (APKPure only archives a 32-bitarmeabi_v7avariant, which the arm64-only Pixel 10a rejects withINSTALL_FAILED_NO_MATCHING_ABIS). Two more apps were caught with an active geo observer running, and two others returned an explicit serveractive=false“stopping modules” — confirming the kill-switch is live and server-controlled across the fleet. Raw logs:data/runtime_logs2/<pkg>.log, summarised indata/cc_runtime_state_round2.csv.
| App | CC SDK init? | Settings poll result | Live tracking state | Kinesis push? |
|---|---|---|---|---|
| com.appmiral.ttfestival | YES | valid active=true (23 Jun) |
🔴 LIVE — 30 s polling, sending to Firehose us-east-1 (23 Jun; quiet in 24 Jun 35 s probe) | YES (15.9 KB on disk) |
| be.appstrakt.werchter2010 | YES | observer restored | 🟠 ACTIVE-LOW — geo observer running at 1800 s (outer/30-min cadence) | attempted (0 B buffered) |
| com.appmiral.dutchgp | YES | observer restored | 🟠 ACTIVE-LOW — geo observer running at 1800 s (was dormant 23 Jun → observer restarted 24 Jun) | no |
| com.appmiral.mdlbeast | YES | active=false → stopping modules |
dormant — consent-enabled, server kill-switch OFF | no (0 B) |
| com.appmiral.rockforpeople | YES | active=false → stopping modules |
dormant — consent-enabled, server kill-switch OFF | no (0 B) |
| com.appmiral.sicknewworld | YES | Settings was null |
dormant (indoor-positioning config; crowdConnectedIndoorLocation=true) |
no |
| com.appmiral.loversandfriends | no | n/a | SDK present, not enabled within 35 s probe (consent not passed) | no |
| com.appmiral.museumnachtamsterdam | no | n/a | SDK present, not enabled within 35 s probe (consent not passed) | no |
| com.appmiral.teamnl | no | n/a | SDK present, not enabled within 35 s probe (consent not passed) | no |
| com.appmiral.tomorrowlandbrasil | no | n/a | SDK present, not enabled within 35 s probe (consent not passed) | no |
| com.appmiral.werchterboutique | no | n/a | SDK present, not enabled within 35 s probe (consent not passed) | no |
| com.appmiral.werchterparklife | — | — | not installable (32-bit-only APKPure archive) | — |
Across every app that did initialise the SDK — werchter2010, dutchgp, mdlbeast,
rockforpeople, sicknewworld, plus TT — the AWS Cognito identity pool is the same
US pool us-east-1_xsD5yIcIS seen in the TT capture (e.g.
CognitoIdentityProviderDeviceCache.us-east-1_xsD5yIcIS.* in the dutchgp/mdlbeast logs).
The push-registration leg signs sns.eu-west-1 (EU), but the location/identity pipeline
is wired to us-east-1 fleet-wide. The only thing separating “dormant” from “live
30 s exfiltration” is the server-side active boolean — which we observed in all four
states within 24 h: active=true + high-frequency (TT, 23 Jun), active=true +
outer-zone observer running (werchter2010, dutchgp, 24 Jun), explicit active=false →
stopping modules (mdlbeast, rockforpeople), and Settings was null (sicknewworld).
The “no SDK init” rows mean the app starts the AWS Cognito caching machinery (visible
in logs as CognitoCachingCredentialsProvider) but never calls
CrowdConnected.enable() because consent has not been granted by tapping through the
onboarding flow. This is not the same as “dormant” — it’s “unknown until the user
opts in”. The CC SDK and libpositioner.so are still bundled and would activate the
moment consent is given (subject to the server-side active flag at that time).
For the two apps where the SDK did initialise (TT Festival + Dutch GP), the contrast is stark:
- TT Festival → server says
active=true→ live tracking with 30 s polls and US data transfer happening right now. - Dutch GP → server says
active=false→ SDK initialised, geofencer not armed, no data leaving the device.
The two apps share identical Cognito infrastructure (User Pool us-east-1_xsD5yIcIS,
app client 31iqmf255m1dai4bpqq3an55pg), identical Kinesis Firehose target
(firehose.us-east-1.amazonaws.com), and identical SDK code. The only difference is a
single boolean in the settings JSON returned by mobile.crowdconnected.com/prod/.
That boolean is server-side and can flip at any moment, for any of the 12 CC-club
apps, without a new app release.
Methodology — runtime probe
- Pixel 10a / Android 16, ADB-attached at home (NL). Round 1: 23 Jun 2026 ~22:07 UTC. Round 2 (expanded coverage): 24 Jun 2026 ~01:00 UTC.
- Base APKs from APKPure were sideloaded after disabling Play Protect
(
settings put global package_verifier_enable 0). AAB-split apps that initially failed withINSTALL_FAILED_MISSING_SPLITwere fixed in round 2 by downloading the full XAPK bundle (base +config.arm64_v8a/ density splits) and installing them together viaadb install-multiple -r -i com.android.vending --user 0 <all .apk>. The XAPKs were fetched through a Cloudflare-bypassing Playwright/Firefox script (appmiral-deep-dive/dl_xapk2.py: warm upcf_clearancein a persistent Firefox context, thenctx.request.get()thed.apkpure.net/b/XAPK/<pkg>endpoint). - 93 Appmiral-platform apps are now installed (of 114 base APKs seen). The 19 that
still fail are 32-bit-only (
armeabi_v7a/x86) APKPure archives with no arm64 split — unrunnable on the arm64-only Pixel 10a (INSTALL_FAILED_NO_MATCHING_ABIS). - For each CC-club app:
pm grantall location/Bluetooth/notification perms in advance →adb logcat -c→am force-stop→monkey -p <pkg> -c android.intent.category.LAUNCHER 1to launch → wait 35 s →adb logcat -d -v threadtimeto dump →am force-stop. - No interaction with the onboarding/consent UI was performed — that’s why some apps show “SDK not enabled”. Apps that self-initialise (TT, Dutch GP, werchter2010, mdlbeast, rockforpeople, sicknewworld) had either already-accepted consent from prior use or are configured to start the SDK without a consent tap.
- Raw artefacts:
data/ttfestival_runtime_logcat_2026-06-23.log,data/dutchgp_runtime_logcat_2026-06-23.log,data/cc_runtime_state.csv(round 1);data/runtime_logs2/<pkg>.log(11 apps),data/cc_runtime_state_round2.csv(round 2).
Scope and method
- Universe.
https://appmiral.com/our-worklists ~212 festival apps. After filtering to apps that actually run on the Appmiral platform (packagescom.appmiral.*,be.appstrakt.*legacy,appmiral.*, plus the two confirmed exceptionscom.triple.verstappenandpl.mysticfestival), the target set is 139 apps. Non-Appmiral festival apps showcased there (Greencopper, Tomorrowland, Beatswitch, Golive, etc.) are excluded — they run on different stacks. - APK retrieval. APKs downloaded from APKPure (apkpure.net direct endpoint). 114 of 139 APKs successfully retrieved (the remaining 25 are delisted in Play Store or only available in regions APKPure does not mirror).
- Manifest analysis. Each APK unwrapped if it was an XAPK bundle, then
aapt2 dump xmltree --file AndroidManifest.xmlandaapt2 dump badgingrun on the base APK to enumerate permissions, services, foreground-service types and version info. - SDK fingerprint. All
classes*.dexfiles extracted, concatenated and scanned for ~45 known vendor class-name prefixes (Lnet/crowdconnected/,Lcom/amazonaws/,Lcom/google/firebase/, etc.). - Privacy policy. Each app probed at
https://app.appmiral.com/<slug>(<year>)?/privacy-policywith year suffixes 2023–2027 and slug variants. 100 of 114 policies recovered. HTML stripped to plain text and pattern-matched for claims (EU-only storage, US transfer, CrowdConnected, Bluetooth, 60 km, Braze/Insider/Salesforce/Tealium, etc.).
Raw artifacts live under ~/.copilot/session-state/<id>/files/appmiral-deep-dive/:
apks/, base_apks/, manifests/, badging/, dex/, privacy/,
plus the matrices perm_matrix.csv, sdk_matrix.csv, privacy_claims.csv.
TL;DR
- CrowdConnected (bluetooth+wifi+GPS crowd tracking) is a deliberate, platform-wide
Appmiral feature opted into by ~10 % of festivals. 12 of 114 apps ship the full
stack — including all four Werchter-family festivals, Tomorrowland Brasil, MDLBEAST,
TT Festival, Dutch GP, two Las Vegas events, Museumnacht Amsterdam, TeamNL Huis
(Olympic Committee), Sick New World and Rock for People. The 11 with
FOREGROUND_SERVICE_LOCATIONshare an identical permission setBGloc + BTscan + BTconn + FGloc + WIFIchg; one (loversandfriends) usesACTIVITY_RECOGNITIONinstead of FG-location. - A further 12 apps declare
ACCESS_BACKGROUND_LOCATIONwithout CrowdConnected, using Google Play Services location only — likely geofence-style nudges. Together, 24 of 114 (21 %) of Appmiral apps run background location collection. - Of the 12 CC-club apps, only 1 (Sick New World, Las Vegas) has the indoor
Bluetooth-beacon module actually wired in. The other 11 ship the full
libpositioner.sonative engine and BLE scan pipeline as dormant code: the SDK declares the permissions and the foreground service, butcrowdConnectedIndoorLocation == "false"meansCrowdConnected.enable()installsGeoModule(outdoor GPS) andBackgroundModuleonly. Indoor BLE positioning is a single resource flip away. - Live
activeflag and per-app radii cannot be read from a static APK. They come from a server-sidesettings/<appKey>JSON behind AWS Cognito User Pool auth (us-east-1_xsD5yIcIS/ app client31iqmf255m1dai4bpqq3an55pg). The matching Cognito client secret is not embedded as a plaintext string in the APK. The single ground-truth measurement we have is the TT Festival 2026 build, runtime-captured:active=false, outer radius 60 km, inner 820 m. Whether the other eleven apps use the same defaults is not statically determinable — see “Where the inner/outer radius lives” below. - Of the 12 apps that ship CrowdConnected, only 2 mention it in their privacy policy (TT Festival and Dutch GP). Nine bundle the tracker silently and three of them have no discoverable Appmiral-hosted policy at all (Werchter 2010 legacy, MDLBEAST, TeamNL).
- The Appmiral boilerplate privacy template over-discloses. 41 of 100 policies reuse the same template that names Braze, Insider, Salesforce, Tealium and CrowdConnected. Braze appears in 1 of 114 builds. Insider, Salesforce: 0–1. Tealium: 2. CrowdConnected: 12. 39 policies name CrowdConnected for apps that don’t bundle it.
- Zero policies mention the 60 km collection radius. Only 3 of 100 policies
disclose US data transfer — even though CrowdConnected streams to AWS Kinesis
Firehose in
us-east-1and AWS Cognito (US) is wired into 111 of 114 apps. - TT Festival is not a unique outlier. It is a fully representative member of
the “CrowdConnected club” and one of only two members whose policy is honest about
the tracker’s existence. Most of the substantive privacy issues identified in
docs/privacy-policy-analysis.md(account-id linkage, US data transfer, safety-framed background-location requests) replicate across every member of the club.
Cohort
| Group | Count |
|---|---|
| Total listed on appmiral.com/our-work | ~212 |
| Appmiral-stack packages | 139 |
| APKs retrieved | 114 |
| Privacy policies retrieved | 100 |
| Apps with CrowdConnected DEX | 12 |
Apps with ACCESS_BACKGROUND_LOCATION |
24 |
Apps with BLUETOOTH_SCAN |
12 |
Apps with FOREGROUND_SERVICE_LOCATION |
11 |
The “CrowdConnected club” (12 apps, 10.5 %)
All 12 apps bundle net.crowdconnected.* classes, ship the AWS Kinesis Firehose SDK
and request the full surveillance permission set. The cohort cuts across countries,
genres and operators — it is not a regional or one-promoter pattern.
| Package | Version | Country / festival | Promoter |
|---|---|---|---|
com.appmiral.ttfestival |
1.2.0¹ | NL — TT Festival | Stichting TT Week Assen |
com.appmiral.dutchgp |
5.2.0 | NL — Dutch GP (F1) | Circuit Zandvoort |
com.appmiral.museumnachtamsterdam |
4.0.0 | NL — Museumnacht | Stichting Museumnacht |
com.appmiral.teamnl |
4.0.3 | NL — TeamNL Huis | NOC*NSF |
com.appmiral.werchterboutique |
7.0.2 | BE — Werchter Boutique | Live Nation BE |
com.appmiral.werchterparklife |
1.0.2 | BE — Werchter Parklife | Live Nation BE |
be.appstrakt.werchter2010 |
16.1.0 | BE — Rock Werchter (legacy pkg) | Live Nation BE |
com.appmiral.tomorrowlandbrasil |
3.0.0 | BR — Tomorrowland Brasil | We The Fest |
com.appmiral.rockforpeople |
4.0.1 | CZ — Rock for People | Ameba Production |
com.appmiral.mdlbeast |
6.0.2 | SA — MDLBEAST Soundstorm | MDLBEAST |
com.appmiral.loversandfriends |
1.0.0 | US — Lovers & Friends (Las Vegas) | Goldenvoice |
com.appmiral.sicknewworld |
2.0.0 | US — Sick New World (Las Vegas) | Goldenvoice |
¹ This is the APKPure-mirrored version; the on-device build pulled from a phone
running TT Festival 2026 was v2.0.1 (versionCode=5). The same tracking stack is
present in both.
Identical permission shape
Eleven of twelve declare exactly:
ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
ACCESS_BACKGROUND_LOCATION
BLUETOOTH, BLUETOOTH_ADMIN, BLUETOOTH_SCAN, BLUETOOTH_CONNECT
ACCESS_WIFI_STATE, CHANGE_WIFI_STATE
FOREGROUND_SERVICE, FOREGROUND_SERVICE_LOCATION
com.appmiral.loversandfriends substitutes ACTIVITY_RECOGNITION for
FOREGROUND_SERVICE_LOCATION — same SDK, different runtime mode (likely a coarser
proximity-tracking variant for the Vegas venues).
Identical service declaration
Every member of the club registers a foreground service backed by Crowd Connected:
<service android:name="net.crowdconnected.androidcolocator.service.CrowdConnectedService"
android:foregroundServiceType="location"
android:exported="false" />
This is the smoking-gun: the service is white-labelled by Appmiral and dropped into every CC-enabled build. It is identical down to the attribute order.
Per-app CrowdConnected configuration (from each APK’s strings.xml)
Each Appmiral build ships per-festival Crowd Connected credentials as
Android string resources, plus a boolean that toggles the indoor positioning
system (Bluetooth beacon scanning + native libpositioner.so):
| Package | Festival | CC appKey | Indoor (BLE) active? | Bundled libpositioner.so? |
|---|---|---|---|---|
com.appmiral.ttfestival |
TT Festival | 0e0957e1 (apk mirror) / dd54d39d (on-device v2.0.1) |
false — IPS dormant | yes (shipped, not started) |
com.appmiral.dutchgp |
Dutch GP | 36fe15a0 |
false — IPS dormant | yes |
com.appmiral.museumnachtamsterdam |
Museumnacht Amsterdam | 30d143df |
false — IPS dormant | yes |
com.appmiral.teamnl |
TeamNL Huis | 48b6d281 |
false — IPS dormant | yes |
com.appmiral.werchterboutique |
Werchter Boutique | 8d9ea3c7 |
false — IPS dormant | yes |
com.appmiral.werchterparklife |
Werchter Parklife | 142f2a03 |
false — IPS dormant | yes |
be.appstrakt.werchter2010 |
Rock Werchter (legacy) | 490f94a3 |
false — IPS dormant | yes |
com.appmiral.tomorrowlandbrasil |
Tomorrowland Brasil | 9ccdd0a0 |
false — IPS dormant | yes |
com.appmiral.rockforpeople |
Rock for People | 4ddafa25 |
false — IPS dormant | yes |
com.appmiral.mdlbeast |
MDLBEAST | d61997e4 |
false — IPS dormant | yes |
com.appmiral.loversandfriends |
Lovers & Friends (Las Vegas) | 34c90857 |
false — IPS dormant | yes |
com.appmiral.sicknewworld |
Sick New World (Las Vegas) | 34500b15 |
true — IPS WIRED | yes |
crowdConnectedIndoorLocation is the Appmiral-wrapper flag that decides whether
CrowdConnected.enable() adds the IPSModule (Bluetooth beacon ranging) on top of
the always-present GeoModule (outdoor GPS) and BackgroundModule. Setting it to
"true" means the build is provisioned to scan for Eddystone beacons inside the
venue; setting it to "false" ships the SDK with the BLE/positioner pipeline
dormant — the binary is there but never started.
Eleven of twelve apps ship the SDK with BLE positioning dormant. Only Sick New
World (Las Vegas) has the IPS module wired in. This is consistent with Lovers
& Friends declaring ACTIVITY_RECOGNITION rather than FOREGROUND_SERVICE_LOCATION
in its manifest — those two Las Vegas Goldenvoice events share the same venue and
appear to be the only two apps where outdoor + indoor crowd analytics are both
intended at runtime.
Where the inner/outer radius lives — and why it can’t be read from the APK alone
For TT Festival the on-device runtime measurement showed an outer radius of
60 km around Assen with an inner high-frequency zone of ~820 m. Those
numbers are not hardcoded in the APK. They come from a JSON settings/<appKey>
response that the SDK fetches from https://mobile.crowdconnected.com/prod/ on
launch. The response also carries the boolean active flag, the list of
deployed Eddystone beacons, the venue’s surface (floor map) and any high-frequency
geo-zones.
Crucially, that endpoint is gated by AWS Cognito User Pool authentication
(pool us-east-1_xsD5yIcIS, app client 31iqmf255m1dai4bpqq3an55pg) and a
SigV4-signed call against execute-api. The user-pool client is configured with a
client secret that is not present in plain text inside the APK strings or
DEX — it is either constructed at runtime in the SDK code or stored in a way
that requires the SDK to run for it to be assembled. As a result:
- We cannot statically read each festival’s current
activeflag, exact outer radius, exact inner radius or current beacon roster from the APK. - We can assert that every member of the CC club shares the same backend, the same authentication shape, and the same service declaration — the only per-festival inputs are the appKey/Token/Secret, the indoor flag and the venue defaults configured by Appmiral on Crowd Connected’s side.
- The single ground-truth measurement we have is the TT Festival 2026 build
(on-device, runtime,
active=falsewith outer=60 km / inner=820 m). The default platform radius for a Dutch festival ought to scope to the venue and immediate surroundings; the 60 km figure was set by either the festival or Appmiral as the customer-side configuration. Whether the other eleven club members carry the same default radius is not statically determinable.
To establish live state for any of the other eleven apps would require either:
- installing the app on a phone, accepting consent, and capturing the
settings/<appKey>HTTPS response with a TLS-intercepting proxy (mitmproxy + bypassing the SDK’s pinning, where present); or - attaching a Frida hook to
CrowdConnected.enable(...)orSettingsPollWorkerto dump the in-memory settings object after it is deserialized.
Both are runtime techniques that require physical access to the app per festival. A static-only analysis can show that the SDK is wired, the permissions are declared and the indoor flag is set — it cannot prove that data is currently flowing.
The “background-location only” cohort (12 apps)
These apps declare ACCESS_BACKGROUND_LOCATION but do not bundle CrowdConnected.
They ship the Google Play Services location library (com.google.android.gms.location)
which is the standard primitive for geofences and the Fused Location Provider.
The most plausible use is geofence-triggered local notifications (e.g. “you arrived
at the festival” or “you parked here”), not crowd analytics. The fact that
BLUETOOTH_SCAN and FOREGROUND_SERVICE_LOCATION are missing rules out the CC
surveillance pattern.
| Package | Version | Festival |
|---|---|---|
com.appmiral.afaslive |
1.0.5 | AFAS Live (venue) |
com.appmiral.alcatrazfestival |
2.2.0 | Alcatraz Festival |
com.appmiral.beatvenues |
1.4.1 | be·at Venues |
com.appmiral.bittersweet |
2.0.0 | BitterSweet Festival |
com.appmiral.brusselsphilharmonic |
3.0.3 | Brussels Philharmonic |
com.appmiral.dmugen |
5.0.3 | DIF DM-ugen |
com.appmiral.glucksgefuhle |
4.0.4 | Glücksgefühle Festival |
com.appmiral.jellingmusikfestival |
6.0.2 | Jelling Musikfestival |
com.appmiral.sunsetbeachfestival |
2.0.2 | Sunset Beach Festival |
com.appmiral.turf |
2.0.2 | TURF |
com.appmiral.zwartecross |
9.0.2 | Zwarte Cross |
com.triple.verstappen |
1.1.0 | Max Verstappen Official |
Platform-wide SDK matrix (114 apps)
| SDK | Bundled in | % | Notes |
|---|---|---|---|
| Firebase Analytics | 113 | 99 % | Universal; only one missing app is appmiral.freaksofnature |
| Firebase Messaging | 113 | 99 % | Push delivery |
| Firebase Crashlytics | 113 | 99 % | Crash reporting |
| Google Maps | 112 | 98 % | Programme maps, venue maps |
| OkHttp + Retrofit | 113 | 99 % | HTTP stack |
| AWS Cognito | 111 | 97 % | Appmiral backend identity |
| Google Play Billing | * | — | Where in-app purchases exist |
| AWS Kinesis Firehose | 12 | 10 % | Exclusively in the CC club |
| CrowdConnected | 12 | 10 % | See above |
| Cookie Information CMP | 99 | 87 % | The Appmiral default consent manager |
| OneTrust CMP | 32 | 28 % | Used in parallel/instead of Cookie Information on some builds |
| Permutive | 30 | 26 % | Audience analytics |
| Jetpack Compose | 22 | 19 % | Modern UI |
| Adjust | 0 | 0 % | — |
| AppsFlyer | 0 | 0 % | — |
| Branch | 0 | 0 % | — |
| Mixpanel | 0 | 0 % | — |
| Amplitude | 0 | 0 % | — |
| Segment | 0 | 0 % | — |
| Airship | 0 | 0 % | — |
| Braze | 1 | <1 % | Named in 43 policies |
| Insider | 0 | 0 % | Named in 41 policies |
| Salesforce MC | 1 | <1 % | Named in 41 policies |
| Tealium | 2 | <2 % | Named in 41 policies |
| OneSignal | 1 | <1 % | — |
| Sentry | 1 | <1 % | — |
| Bluedot / Radar / Gimbal / Estimote | 0 | 0 % | No “other” beacon vendors |
What the cohort tells us
The Appmiral platform is remarkably homogeneous on the engineering side
(Firebase + AWS + OkHttp/Retrofit + Maps in 97–99 % of apps) and very narrow on
optional add-ons. There is essentially one crowd-tracking vendor on the
platform — CrowdConnected — and either Cookie Information or OneTrust as the
consent manager. The TT Festival build is unusual only in shipping the
com.appmiral.privacy.none module (no consent manager at all); 99 of 114 builds
ship Cookie Information.
Privacy policy reality check
Per the 100 policies retrieved (cross-correlated with each app’s actual code):
Boilerplate over-disclosure (the “fake processor list”)
The shared Appmiral template names a long list of optional processors. None of the named third parties — Braze, Insider, Salesforce Marketing Cloud, Tealium — are actually integrated in more than a single build. Concretely:
| Vendor | Policies that name them | Builds that actually bundle them |
|---|---|---|
| Braze | 43 | 1 |
| Tealium | 41 | 2 |
| Insider | 41 | 0 |
| Salesforce Marketing Cloud | 41 | 1 |
| CrowdConnected | 41 | 12 |
This is what the boilerplate looks like in practice. For example
com.appmiral.dranouter (a Belgian rock festival with no Bluetooth or
background-location code) names CrowdConnected, Braze, Insider and Salesforce in
its privacy policy. The policy describes processors that don’t exist in the build.
That is the opposite sin from TT Festival’s. TT Festival’s policy fails by under-disclosing (no US transfer, no 60 km, no account-id linkage); Dranouter and ~38 peers fail by over-disclosing (naming a phantom processor stack as if it were in use).
Bundle-but-silent (the dangerous gap)
Of the 12 apps that actually ship CrowdConnected:
| Package | Policy mentions CC? | Policy discloses US transfer? | Policy claims EU-only? |
|---|---|---|---|
com.appmiral.ttfestival |
yes | no | yes (false) |
com.appmiral.dutchgp |
yes | no | yes (false) |
com.appmiral.sicknewworld |
no | yes | no |
com.appmiral.rockforpeople |
no | no | no |
com.appmiral.werchterboutique |
no | no | no |
com.appmiral.werchterparklife |
no | no | no |
com.appmiral.museumnachtamsterdam |
no | no | no |
com.appmiral.tomorrowlandbrasil |
no | no | no |
com.appmiral.loversandfriends |
no | no | no |
be.appstrakt.werchter2010 |
(no policy hosted) | – | – |
com.appmiral.mdlbeast |
(no policy hosted) | – | – |
com.appmiral.teamnl |
(no policy hosted) | – | – |
Two of twelve mention CrowdConnected at all. Both of those two claim EU-only
storage despite the CC stack streaming to AWS us-east-1. One discloses any US
data transfer, and that one is silent on CC itself. Three operators don’t expose
a discoverable privacy policy at all for the app variants that ship the tracker.
60 km / radius disclosure
Zero policies mention the 60 km collection radius. This is the figure measured
on-device in the TT Festival app (docs/crowd-connected.md). It is plausibly a
platform-wide default; we have no evidence that any festival has narrowed it, nor
that any has disclosed it.
Firebase Analytics — the universal silent default
113 of 114 apps bundle Firebase Analytics. 42 of 100 policies mention “Firebase” by name. The remaining 58 policies are silent about Firebase Analytics specifically, typically referring to “third-party analytics” generically or omitting it entirely.
Where TT Festival sits
Against the Appmiral platform as a whole TT Festival is unusual in three very narrow respects:
- The shipping build replaces
com.appmiral.privacy.cookieinformationwith the stubcom.appmiral.privacy.none. 99 / 114 peer apps ship a real consent manager. - The privacy policy is more honest than 10 of its 11 CC-club peers: it names CrowdConnected by name and links to CC’s own policy. Two of twelve CC-club members do this — TT Festival and Dutch GP.
- The published “Safety” and “FAQ” pages on the festival’s own website are completely silent about location tracking, where some peers at least carry the information in the in-app policy.
On every other axis — the 60 km radius, the AWS us-east-1 data destination, the
account-id linkage via sendAlias, the SOS-framed background-location prompt, the
hardcoded credentials in resources — TT Festival is a faithful clone of the
Appmiral CC template. If anything the company-level findings apply more strongly
to the rest of the club, where the tracker is shipped with no disclosure at
all.
Implications
- The 60 km / always-on / account-linked tracking design was caught live in the
TT Festival app on 23 Jun 2026. The earlier “active=false” state observed in early
June has flipped to
active=true; the geofence transitioned to the high-frequency 30 s observer; the Kinesis Firehose pipeline began signing Sigv4 batches tous-east-1. Whatever the public-facing narrative (“alleen tijdens het festival”, “alle data in EU”) is, the production system is now doing the opposite, today, at home, 150 km from the venue. - The 60 km / always-on / account-linked tracking design is not a TT Festival decision. It is a product feature sold by Appmiral and bought by ~10 % of its customers. Any regulator looking at this should treat the data-controller responsibility as joint (festival = controller, Appmiral = processor that pre-built the surveillance stack and the boilerplate policy that fails to describe it).
- The boilerplate privacy template that names Braze, Insider, Salesforce, Tealium and CrowdConnected as processors regardless of whether the build actually uses them likely produces an Art. 13/14 information failure at scale across the platform. Either the policies overstate processing (~39 false-positive cases) or understate it (~9 silent-CC cases).
- The Werchter family alone accounts for four members of the CC club, all operated by Live Nation Belgium. That same operator runs the venues for which Belgian press has previously reported on crowd-flow analytics (Werchter, Pukkelpop, GMM). A focused regulator inquiry there has a much shorter walk than any single TT Festival case.
Next steps
- The cohort matrices (
perm_matrix.csv,sdk_matrix.csv,privacy_claims.csv) are the data backbone — link or attach when filing a regulator request. - For each CC-club app whose policy is silent about CrowdConnected we can replicate
the on-device check in
01-on-device-cohort.md(decompile + Cognito-pool + Kinesis-stream identification) and produce a short per-festival appendix. - If a complaint is filed, the strongest target is Appmiral as a processor plus the small number of festivals where the Always-Allow prompt is shown to large audiences (TT Festival, Tomorrowland Brasil, MDLBEAST, Werchter family).
- Remaining runtime gaps. Five CC-club apps (
loversandfriends,museumnachtamsterdam,teamnl,tomorrowlandbrasil,werchterboutique) did not enable the SDK within the 35 s LAUNCHER probe; to capture their trueactive/radius state we would need to tap through each onboarding/consent flow (manually or viauiautomator).com.appmiral.werchterparklifeneeds an arm64 XAPK from an alternative mirror (APKPure only archives a 32-bit build) before it can be probed.
Update (Phase 3 —
03-ondevice-egress-dutchgp-museumnacht.md): two of these gaps are now closed. Tapping through onboarding inside the geozone, Dutch GP was caught performing a live collect+upload of a precise GPS position tofirehose.us-east-1.amazonaws.com(submitAllRecords sent 2 records, keyed todeviceId 382c3ab8…). Museumnacht turned out to be already registered+consented (correcting “consent not passed”), but with the server runtime gateactiveAPIResponse=…,false,…it collected nothing during the window. The legacy apps’ real kill-switch is that server value, not the localgeoEnabledflag.