On-device data divides cleanly into three tiers: things that must never touch the disk, things that may with platform protection, and things that can be cached freely provided they are labelled with their age. The useful insight is that the dangerous storage is rarely the storage you wrote. Four channels put banking data on disk without anyone deciding to — the operating system backup, the app-switcher snapshot, the crash reporter and leftover debug logging — and all four are on by default or easy to leave on.
Ask a mobile team what their banking app stores locally and you will get an accurate answer about the cache they built. It will usually be a careful answer, and it will usually be incomplete, because the things that put customer data on a phone are mostly not things anyone wrote.
This is a companion to the hardening side of the problem — pinning, root detection and tamper resistance — which is about the network and the binary. This one is about what is sitting on the disk when the handset is in someone else's hands.
Three tiers
The division that holds up in review is by consequence rather than by data type.
Never. Credentials, full card numbers, anything whose disclosure is itself the incident. The test is simple: if finding this in a file would be a reportable event, it does not go in a file. There is no encryption argument that rescues this tier, because the key has to live on the same device.
Only under platform protection. Session tokens, refresh tokens, the device registration identifier, biometric enrolment state. These have to persist or the product is unusable, so they go into the protected store the platform provides — Keychain on iOS, the Keystore system on Android — not into preferences or a local database. Two settings matter more than the choice of store: keep the item to this device only so it cannot travel in a backup, and gate the sensitive entries behind device or biometric authentication where the flow allows.
Freely, with a timestamp. Balances, recent transactions, payee lists, the shape of the interface. These make the app usable on a train, and the only firm rule is that they carry their age in the interface. A cached balance presented identically to a live one is the defect discussed in the architecture piece: it lets a customer act on a number that was true yesterday.
The four that happen by default
Operating system backup. Both platforms will sweep application data into the customer's cloud account unless told otherwise. The failure mode to picture is not a hacker; it is a customer restoring a new handset and finding themselves already signed in to a state that should have required re-authentication. Decide this explicitly, exclude what should not travel, and use the this-device-only setting on anything in the protected store.
The app-switcher snapshot. When a banking app is backgrounded the operating system captures the current screen so it can draw a preview in the task switcher. If the account page was open, that image is the account page, and it persists outside your control. The remedy is small and well understood — cover the window at the moment of backgrounding, and on Android set the window flag that both excludes the preview and blocks screenshots on sensitive screens. It is a two-line fix that is missing far more often than it should be.
The crash reporter. Crash and performance tooling is genuinely valuable and the default configuration is rarely right for a banking app. Breadcrumbs capture screen names and sometimes parameters; some tools attach portions of memory. Review what the reporter actually transmits, scrub the payload, and remember that this is a third-party processor — so it also belongs in your privacy notice and your vendor assessment, not only in your build configuration.
Logging. A log line that prints a response body is normal during development and is still there at release more often than teams expect. On Android, logs written to the system log have historically been readable in ways developers assume they are not. The reliable discipline is not "be careful what you log" but "strip logging in release builds", because the first relies on everyone remembering forever.
Offline: read yes, write no
Offline support is worth building and is almost always scoped wrongly. Read-only caching is a real improvement — opening the app underground and seeing last night's balance, clearly labelled, is better than a spinner.
Queued writes are a different proposition. A transfer the customer believes is submitted, held on a handset, creates an obligation the bank never accepted. Then come the questions: what if the balance has moved, what if the payee was removed, what if the app is reinstalled before it syncs, what does the customer see meanwhile. These are not insurmountable, but they are a reconciliation problem of exactly the kind we describe in core banking integration, and the honest cost is much higher than "add a queue".
Refuse the write, explain why, and keep the trust.
When the phone is gone
Everything above reduces what an attacker reaches. None of it is the control that matters, which is server-side revocation of the session and the device registration.
Worth stating plainly because it is often assumed backwards: remote wipe is not yours. It belongs to the handset owner or their employer. Your app cannot be relied on to receive a message and delete itself, because an attacker will not helpfully provide network access. The only thing you control is whether that device's tokens and registration still mean anything on your side, and the answer should be reachable from a support desk within minutes.
This is also where the device registration design from the platform layer becomes visible to customers. If reinstalling generates a new device identity — as it does in OBDX — then "I lost my phone" and "I reinstalled the app" look similar to your systems and must not feel similar to the user.
What to actually test
- Take a backup, restore to a second device, and open the app. What is already there that should not be?
- Background the app on an account screen and look at the task switcher. This takes ten seconds and catches a real defect.
- Read what the crash reporter sends for one real crash, in full, rather than trusting the configuration.
- Grep a release build's logs while performing a transfer.
- Pull the app's data directory on a device you control and look at every file, including framework caches you did not create.
- Revoke a session server-side and confirm the app discovers it on next use rather than continuing from cache.
Five of those six are quick. They are also the ones that come back as findings when somebody else runs them first, which is the argument for running them yourself.
Questions we get asked
Can a banking app store the customer's balance on the device?
Yes, as a cached read, provided two things hold. It must be shown with its age so a customer can never mistake a stale figure for a live one, and it must never be treated as authoritative by any logic in the app. A cached balance is a convenience for a user opening the app on a train. It is not a number anything should calculate from.
Where should authentication tokens be stored on a phone?
In the platform's own protected store — Keychain on iOS, the Android Keystore system — rather than in preferences, a database or a file. Use the accessibility setting that keeps the item on this device only, so it does not travel in a backup to a new handset, and gate the sensitive items behind biometric or device authentication where the flow allows it.
How do banking apps leak data without storing it deliberately?
Through four defaults nobody chooses. Operating system backups can sweep app data to a cloud account. The app-switcher snapshot captures whatever was on screen when the app was backgrounded. Crash reporters attach breadcrumbs and sometimes memory. And logging written for debugging survives into release builds. All four are on by default or easy to leave on.
Should a banking app work offline?
Read-only, yes, and it is worth doing. Writes, no. A queued transfer creates an expectation the bank has not accepted, and reconciling that against what the core believes is a far harder problem than it looks. The honest offline mode shows what was last retrieved, says when, and refuses anything that moves money.
What protects a customer if their phone is stolen?
Server-side revocation of the session and the device registration. Remote wipe belongs to the handset owner or their employer, not to you, and anything that depends on your app cooperating is not a control at all. The device-side measures reduce what an attacker can reach; only the server can decide that this handset no longer counts.
Can we exclude banking data from phone backups?
Yes, and you should decide it deliberately rather than inherit the default. Both platforms provide a way to exclude files and data from automatic backup, and the protected credential stores offer a this-device-only setting. The failure mode worth avoiding is a customer restoring a new phone from a backup and finding app state they should have had to re-authenticate for.
If an app is heading into a bank's security review, the six tests above are the cheapest preparation available — they take an afternoon and they find most of what a reviewer will find. It is the same afternoon we spend early on enterprise mobile projects, for the same reason.