TT Festival App — Hardcoded credentials, API keys & identifiers
Scope:
com.appmiral.ttfestivalv2.0.1 (versionCode 5), decompiled APK resources + manifest. Values below are redacted unless they are already non-secret identifiers. The full values are present in the APK/resources; this document avoids re-publishing secret-looking material in git.
This inventory separates:
- True credentials/secrets: values named
secret,token, or values used to authenticate to a backend. - Public mobile API keys / identifiers: values that are expected to ship in mobile apps but still need server-side restrictions.
- Placeholders: registry/config entries that are
nullor empty and therefore not active in this TT Festival build.
1. High-risk / secret-looking values shipped in the APK
| System | Resource / location | Redacted value | Why it matters |
|---|---|---|---|
| Crowd Connected | strings.xml: crowdConnectedKey |
dd54d39d |
App/tenant key used to initialise Crowd Connected (ConfigurationBuilder.withAppKey(...)). |
| Crowd Connected | strings.xml: crowdConnectedToken |
a86f…ecb0 (32 hex chars) |
Token passed to ConfigurationBuilder.withToken(...). This is a real credential, not just an ID. |
| Crowd Connected | strings.xml: crowdConnectedSecret |
kW2g…6xJ7 (32 chars) |
Secret passed to ConfigurationBuilder.withSecret(...). Shipping this client-side means anyone with the APK can extract it. |
| Spotify | strings.xml: spotifyClientId |
a762…9e3 |
OAuth client id for Spotify integration. Client IDs are not secret, but should be monitored/restricted. |
| Spotify | strings.xml: spotifySecretId |
2517…3eb |
Client secret shipped in a mobile APK. This is the clearest hardcoded secret outside Crowd Connected. |
| Appmiral API | strings.xml: x_Protect |
64d2…bb23 (32 hex chars) |
Static x-protect header that gates the Appmiral read API (timetable stages/performances/artists, games/bierroute). The same value for every install — not per-user or per-session — so anyone with the APK can read the API as this tenant. See timetable-api-access.md. |
Crowd Connected usage path
com.appmiral.crowdconnected.CrowdConnected.enable(...) reads:
CoreApp.getCrowdConnectedApiKey()
CoreApp.getCrowdConnectedApiToken()
CoreApp.getCrowdConnectedApiSecret()
and passes them directly into:
ConfigurationBuilder()
.withAppKey(...)
.withToken(...)
.withSecret(...)
The live run then contacted mobile.crowdconnected.com; logcat also showed the
app setting the SNS push endpoint as Crowd Connected alias sns_user_id. That
proves these credentials are not dead config.
2. Public cloud/mobile keys and identifiers
These are less like passwords, but still security-sensitive because they identify projects and should be locked down by package name/SHA restrictions, API restrictions, IAM policy, and backend-side validation.
| System | Resource / location | Redacted value | Notes |
|---|---|---|---|
| Google Maps Android | AndroidManifest.xml: com.google.android.geo.API_KEY |
AIza…aQ |
Maps SDK key. Should be restricted to com.appmiral.ttfestival + signing cert SHA and Maps-only APIs. |
| Firebase / Google API key | strings.xml: google_api_key |
AIza…0E |
Firebase/Google API key used by Firebase clients. Not a secret by design, but should be API-restricted. |
| Firebase Crashlytics API key | strings.xml: google_crash_reporting_api_key |
same as google_api_key |
Crashlytics/Firebase config key. |
| Crashlytics legacy key | AndroidManifest.xml: com.crashlytics.ApiKey |
c330…5308 |
Legacy Crashlytics identifier/API key. |
| Firebase app id | strings.xml: google_app_id |
1:1057497894154:android:7bf629b172de8884 |
Firebase app identifier. |
| Firebase sender id | strings.xml: gcm_defaultSenderId |
1057497894154 |
FCM sender/project number. |
| Firebase DB URL | strings.xml: firebase_database_url |
https://appmiral-prod.firebaseio.com |
Project endpoint; access depends on Firebase rules. |
| Google OAuth web client id | strings.xml: default_web_client_id |
1057497894154-…apps.googleusercontent.com |
OAuth client identifier. |
| Firebase project id | strings.xml: project_id |
appmiral-prod |
Project id. |
| AWS Cognito/SNS identity pool | strings.xml: sns_identity_pool_id |
eu-west-1:19db…cc01 |
Lets the app obtain temporary AWS credentials for SNS push endpoint registration. Must be tightly IAM-scoped. |
3. Appmiral backend endpoints (not credentials)
| Resource | Value | Purpose |
|---|---|---|
api_url_v6 |
https://app.appmiral.com/api/v7/ |
Main Appmiral REST API. |
api_function_url |
https://app.appmiral.com/function/ |
Appmiral/AWS Lambda function API (used for gamification badge unlocks etc.). |
| Deep-link host | https://ttfestival2026.deeplink.appmiral.com/ |
Universal/App Links + QR/deeplink routing. |
These are not secrets, but they are useful for mapping data flow and attack surface.
4. Explicitly null / inactive vendor credentials
The APK contains many platform-level placeholders from Appmiral. For TT Festival v2.0.1 they are not configured:
| Vendor / feature | Resource(s) | Status |
|---|---|---|
| Cookie Information CMP | cookieInformationClientId, cookieInformationClientSecret, cookieInformationSolutionId |
null |
| Facebook SDK | facebookAppId, facebookClientToken; manifest auto-init/log-events disabled |
null / disabled |
| Braze | brazeApiKeyAndroid, brazeApiKeyIos, brazeCustomEndpoint, brazeLocationEnabled |
null |
| Permutive | permutiveApiKey, permutiveOrganisationId |
null |
| Salesforce Marketing Cloud | salesforceAccessToken, salesforceApplicationId, salesforceClientCode, salesforceMarketingCloudUrl, salesforceMid |
null |
| CM Push app key | cmPushApplicationKey |
null |
| Ticketmaster | ticketmasterConsumerKeyAndroid, ticketmasterConsumerKeyIos |
null |
| Secondary Firebase | secondaryFirebaseApiKey, secondaryFirebaseAppId, secondaryFirebaseProjectId |
empty |
| Sports Alliance | sportsAllianceKey, sportsAllianceOrigin, sportsAllianceSecret, sportsAllianceTenant |
null |
These placeholders are important because they explain why the consent registry lists SDKs that are not actually active/bundled.
5. Risk summary
| Finding | Risk |
|---|---|
| Crowd Connected app key + token + secret are embedded in the APK | Anyone can extract them and attempt to talk to Crowd Connected mobile APIs as this app/tenant. Backend-side validation and rate limiting are essential. |
x_Protect is a single static header shared by every install |
It is the only gate on the Appmiral read API (programme, games). It is not per-user/session and never rotates, so it is trivially replayed; it provides obfuscation, not access control. See timetable-api-access.md. |
| Spotify client secret is embedded in the APK | Mobile apps are public clients; a client secret in an APK should be treated as compromised and rotated/replaced with PKCE-only OAuth. |
| AWS Cognito identity-pool id is embedded and used pre-consent for SNS | Not a secret by itself, but IAM policy must limit actions to the minimum SNS endpoint operations. |
| Google/Firebase/Maps keys are embedded | Normal for Android, but they must be package/signature/API restricted. |
Many marketing/vendor secrets are null |
Good: Braze/Salesforce/Permutive/Facebook/etc. are not active in this build despite being present in the Appmiral platform registry. |
6. Recommendation
- Treat
crowdConnectedToken,crowdConnectedSecret, andspotifySecretIdas exposed secrets; rotate them if they grant meaningful backend access. - Confirm Crowd Connected credentials are tenant-scoped, write-only, rate-limited and cannot read back user/location data.
- Remove Spotify client secret from mobile clients; use Authorization Code with PKCE.
- Verify Google API keys are restricted to package name + signing certificate SHA and only the required APIs.
- Verify the AWS Cognito unauthenticated role can only perform the exact SNS endpoint registration/update calls needed by the app.