Summary
A mobile application trusted secrets shipped inside the APK. Combined with weak backend authorization, the exposed material allowed access to cloud data and created a path toward account takeover.
Why it mattered
The issue was not the presence of a mobile API key alone. The risk came from the full chain: recoverable client-side credentials, insufficient per-user access control and credential reuse across backend services.
Technical workflow
The APK review was performed offline first, so no production endpoint was contacted during extraction:
apkanalyzer manifest print app-release.apk
strings app-release.apk | rg -i 'firebase|google_api|client_secret|token'
sha256sum app-release.apk
A lightweight CI check can flag likely secrets without treating every public identifier as a credential:
import re
from pathlib import Path
SECRET_PATTERNS = [
re.compile(r"AIza[0-9A-Za-z_-]{20,}"),
re.compile(r"(?i)(client_secret|private_key|access_token)\s*[:=]")
]
def suspicious_strings(path: str) -> list[str]:
text = Path(path).read_bytes().decode("utf-8", errors="ignore")
return [line for line in text.splitlines()
if any(pattern.search(line) for pattern in SECRET_PATTERNS)]
The authorization phase used separate synthetic accounts and compared anonymous, same-user and cross-user reads against the expected policy. Firebase rules and backend APIs must derive the subject and tenant from verified identity, not from mobile parameters.
Defensive lessons
- Treat every mobile binary as public and keep privileged secrets server-side.
- Enforce authorization on every object and tenant boundary in the backend.
- Rotate exposed credentials before closing the original disclosure.
- Test cloud rules with anonymous, user and cross-account identities.
Expected result: public configuration contains no privileged secret, rotated material is rejected, and cross-account reads return a uniform denial without leaking object existence.
This write-up is intentionally sanitized; identifying data and production endpoints are withheld.