Silent API Key Exfiltration via Exposed Deep Link and WebView Bridge in Android Wallet
The Risk
A single tap on a crafted link silently handed an attacker the secret key the wallet app uses to talk to its own servers. With that key, the attacker could request any wallet holder's full list of addresses across 14 different cryptocurrencies from their own computer, at any time, without alerting the victim. The key did not expire. The victim never entered a password, never tapped Approve, and never saw a prompt. One tap was enough.
The Vulnerability
The wallet app exposed a deep link that opened any attacker-controlled HTTPS URL inside an internal WebView. The WebView injected a JavaScript bridge into every loaded page with no origin check and no domain allowlist. One method on that bridge returned a JSON object containing every wallet address, the persistent device identifier, and the HMAC-SHA256 signing key used to authenticate requests to the wallet's own backend gateway.
The URL parameter on the deep link was validated only against Android's generic URL pattern, which accepts any HTTPS URL. A second deep link handler routed any HTTPS URL into the app's DApp browser, which exposed a separate set of bridges with transaction signing capability across six blockchain ecosystems. Neither deep link had auto-verify set, so any co-installed app or plain web link could trigger them with no disambiguation dialog.
The Attack
Stage 1 is the core finding. Zero user interaction beyond the initial tap.
- Victim taps a link pointing to the attacker's page, wrapped in the wallet deep link scheme.
- Wallet app opens the page in its internal WebView.
- Attacker JavaScript calls the bridge method and receives the wallet addresses, device ID, and API signing key.
- JavaScript posts the captured data to the attacker's server. The attack is already complete.
Using the captured signing key, fresh authenticated requests were forged from an external machine against the wallet vendor's backend gateway. The forged requests returned the victim's complete wallet data across 14 chains including BTC, ETH, SOL, TRX, BNB, Base, Linea, Scroll, Avalanche, and Polygon, along with device profile and notification endpoints. The key did not expire, was not rotated on app restart, had no rate limit, and worked from any IP.
A second stage demonstrated escalation to arbitrary transaction signing via the separate DApp browser bridge, but it required the user to approve a signing prompt. Stage 1 alone is sufficient for Critical severity because the backend API key is a credential the user never sees, never consents to sharing, and cannot revoke.
The Impact
The stolen API signing key grants persistent authenticated access to the wallet's backend from any machine. Confirmed capabilities from the forged requests:
- Full wallet enumeration across 14 blockchains for the target device.
- Device profile: model, platform, country, app version, push notification status.
- Notification endpoints and reward point balances.
- All of the above repeatable indefinitely with no further victim interaction.
The linked transaction signing bridge in the second stage, if combined with a typed-data signing request instead of a plain signature, would allow draining all token balances via an approved allowance.
Remediation
- Enforce a strict domain allowlist on the WebView deep link. Only the wallet vendor's own quest and rewards origins should be allowed to load.
- Gate the JavaScript bridge on the loaded page's origin. The method that returns the API signing key should refuse to run unless the page is served from an allowlisted vendor origin.
- Stop exposing the backend API signing key to any WebView context. The app should call the backend itself and pass sanitized data to the page, not hand the page the credentials.
- Apply the same domain allowlist to the DApp browser deep link and its signing bridges.
- Set auto-verify on all deep link handlers that can trigger WebView loads, so co-installed apps cannot quietly intercept them.
- Rotate the signing key format so stolen keys expire on a short clock and can be revoked server-side on abuse.