Mobile Security
Mobile Security
Project:
{{PROJECT_NAME}}Drop — Fintech Payment App Version:{{VERSION}}0.1.0 Date:{{DATE}}2026-02-23 Author:{{AUTHOR}}John (AI Director, ALAI) Status: Draft| In Review | ApprovedReviewers:{{REVIEWERS}}Alem Bašić (CEO)
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | Initial draft — current state analysis + Phase 2 security roadmap |
1. Mobile Threat Model
Context: Drop is a PSD2-regulated payment app. It handles real money movements from users' bank accounts. Drop never stores funds, but it stores authentication tokens that could initiate payments if compromised.
| Threat Actor | Goal | Attack Vector | Likelihood | Impact | Mitigation |
|---|---|---|---|---|---|
| Malicious app on device | Steal |
Shared |
Medium | ||
| Network interceptor (MITM) | Intercept payment API calls | Rogue WiFi, proxy | Medium | ||
| Reverse engineer | Extract API keys, business logic | APK/IPA decompilation | Medium | Medium | |
| Stolen/lost device | Physical access + stored token | High | |||
| Rooted/jailbroken device | Bypass security controls | OS privilege escalation | Low | High | Root/jailbreak detection Phase 2 |
| Low | |||||
| Social engineering | Steal BankID credentials | Phishing | Medium | Critical | User education; BankID handles its own auth |
Assets to protect:protect (priority order):
AuthBearertokenstoken (accessallows+paymentrefresh)initiation from user's bank account)- User PII (name, email,
paymentphoneinfo)— BankID-verified identity) APITransactionkeyshistoryand(financialsecretsdata)CachedExchangesensitiveratedataAPI access- App integrity (prevent tampered builds)
2. Authentication
2.1 Biometric Authentication
Current state (Phase 1): Not implemented. Login is email + password only.
Implementation:Phase 2 implementation: {{expo-local-authentication | react-native-biometrics}}
// Phase 2 — lib/biometrics.ts
import * as LocalAuthentication from 'expo-local-authentication';
export async function authenticateWithBiometrics(): Promise<boolean> {
const hasHardware = await LocalAuthentication.hasHardwareAsync();
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (!hasHardware || !isEnrolled) {
return promptPINFallback();
}
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'VerifyBekreft youridentiteten identity'din',
fallbackLabel: 'UseBruk PIN',
cancelLabel: 'Cancel'Avbryt',
disableDeviceFallback: false,
});
return result.success;
}
UsePlanned biometric use cases for(Phase biometric auth:2):
- App unlock after backgrounding >
{{5minutes}} View sensitive data (payment info, full account number)minutes- Confirm high-value remittance transactions (amount >
{{$100}})5000 NOK) - Change security settings
- View full transaction history
Fallback: 4-digit PIN code(same as web registration flow) → hash stored {{expo-secure-store}}hashin expo-secure-store (bcrypt, not reversible)
2.2 Session Management
| Property | Phase 2 Target | |
|---|---|---|
| Access token lifetime | |
15 |
| Refresh token |
| |
| Session timeout | Never ( |
App lock after min |
| Max concurrent sessions | |
3 |
| Force logout triggers | Manual logout only | Password change, suspicious activity, admin |
Token storage:storage (current Phase 1):
// CURRENT — lib/api.js — in-memory + AsyncStorage
// WARNING: AsyncStorage is not encrypted — acceptable for MVP, not for production
let token = null; // Module-level in-memory
await AsyncStorage.setItem('auth_token', token); // Persisted (unencrypted)
Token storage (Phase 2 target):
// PHASE 2 — lib/auth.ts
import * as SecureStore from 'expo-secure-store';
// CORRECT — encrypted secure storage
await SecureStore.setItemAsync('access_token', token, {
keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
// WRONG — neverDO useNOT AsyncStorageUSE orFOR MMKV for tokensTOKENS
// await AsyncStorage.setItem('access_token', token); // DO NOT USEUnencrypted!
2.3 TokenBankID Storage (Secure Enclave)Authentication
| BankID ||
not see or | store
CannotMobile beBankID backedflow up:(ADR-011):
1. mechanismsGET /v1/auth/bankid/initiate?platform=mobile → { redirectUrl, state }
2. Open BankID in expo-web-browser (secure, isolated browser)
3. BankID redirects to drop://auth/callback?code=&state=
4. expo-linking catches the deep link
5. POST /v1/auth/bankid/callback → Bearer token (7-day)
6. Token stored (AsyncStorage Phase 1, expo-secure-store Phase 2)
Security properties of BankID:
- Norwegian national ID system — eID Level 3 (substantial assurance)
- All authentication happens in BankID's secure environment
- Drop never sees BankID PIN or biometric data
- BankID tokens are
device-boundsingle-use—andtokenstime-limited
3. Data Protection
3.1 Secure Storage Policy
| Data Type | Phase 2 Storage | Forbidden |
|
|---|---|---|---|
| Hardcoded | |||
| Refresh token | |||
| User PII | Not cached locally | SQLite ( |
AsyncStorage |
| Non-sensitive prefs | AsyncStorage | AsyncStorage | — |
| Biometric key hash | Not implemented | expo-secure-store | Any other storage |
Phase 1 risk: Bearer token stored in AsyncStorage (unencrypted) is the primary security gap. Mitigation: 7-day token expiry limits exposure window. Production apps should implement Phase 2 before launch.
3.2 Data Encryption at Rest
Phase 1: No local database. Auth token in AsyncStorage (unencrypted).
Phase 2 — Database encryption: {{SQLCipher for SQLite | Realm Encryption}}
// SQLCipherPhase initialization2 with— lib/database.ts
import * as SQLite from 'expo-sqlite';
import * as SecureStore from 'expo-secure-store';
async function openDatabase() {
// Generate encryption key fromon Keystorefirst constlaunch
encryptionKeylet dbKey = await SecureStore.getItemAsync('db_encryption_key');
constif db(!dbKey) {
dbKey = generateRandomKey(256); // 256-bit random key
await SecureStore.setItemAsync('db_encryption_key', dbKey);
}
return await SQLite.openDatabaseAsync('app.drop.db', { key: encryptionKey,dbKey });
}
Key generation: On first launch, generate 256-bit random key, store in Keychain/Keystore.
Encrypted fields (beyond DB encryption):
3.3 Sensitive Data in Memory
| Rule | Phase 1 Status | Phase 2 Implementation |
|---|---|---|
| Clear passwords after use | Partially (useState cleanup) | Overwrite |
| No |
Implemented — no console.log(user) in production paths |
Custom logger strips PII fields |
| No |
TBD — no crash reporting in Phase 1 | Sentry beforeSend hook scrubs payload |
| No |
Hash user |
3.4 Screenshot Prevention
Drop displays financial data (balance, transaction amounts). Screenshots should be prevented on sensitive screens.
// iOSPhase 2 — prevent screenshots programmatically
// (Note: React Native does NOT expose this API — native module required)
// Android — prevent screenshots andsensitive screen recording
import { Platform } from 'react-native';protection
import { preventScreenCapture, allowScreenCapture } from 'expo-screen-capture';
// Screens requiring screenshot prevention:
// - Dashboard (shows bank balance)
// - Send Money (shows account balance, transaction amounts)
// - Transaction History (financial data)
useEffect(() => {
if (Platform.OS === 'android') {
preventScreenCapture();
}
return () => allowScreenCapture();
}, []);
ScreensNote: requiringiOS screenshot prevention:
- via
expo-screen-capturePaymentshows/acard detailsblank screen- in
Accountthebalanceapp switcher but cannot prevent all screenshots. Android FLAG_SECURE prevents screenshots and screen Personal document viewer{{OTHER_SENSITIVE_SCREEN}}
recording.
3.5 Clipboard Protection
- Password fields:
secureTextEntry={true}in React Native TextInput — disables clipboard by default on iOS -
SensitiveTransactiondataamountsprogrammaticallycopiedcopied:to clipboard: clearclipboardafterseconds (Phase 2){{60seconds}} -
CustomAccount numbers: never copied to clipboardhook:byshown as maskeduseSensitiveCopy(value,appclearAfterMs:—60000)
4. Network Security
4.1 Certificate Pinning
Current (Phase 1): Not implemented. Standard HTTPS/TLS only.
Library:Phase 2 implementation: {{react-native-ssl-pinning | OkHttp certificate pinner (Android native) | URLSession (iOS native)}}
// UsingPhase 2 — requires react-native-ssl-pinning or OkHttp pinner
// Pinned endpoints:
// - drop-app.vercel.app (production API)
// - BankID OIDC endpoints
const response = await fetch('https://api.domain.com/endpoint'drop-app.vercel.app/api/transactions', {
method: 'POST'GET',
pkPinning:// true,Certificate sslPinning:pinning {configuration certs: ['api_cert_sha256_fingerprint'],
},here
headers: { /*Authorization: ...`Bearer */${token}` },
body: JSON.stringify(payload),
});
Pinned endpoints:
api.{{domain.com}}— primary APIauth.{{domain.com}}— authentication
Certificate rotation process:
- Pin
BOTHboth current cert and backup cert simultaneously - Deploy app update with new cert added (OTA via EAS Update)
- After old cert expires, remove old cert from pins
- Test rotation in staging
firstbefore production
Handling pin failures:
Log as security event to backendShow user-facing error: "Secure connection failed"Do NOT fall back to unpinned connection
4.2 SSL/TLS Configuration
| Requirement | Value |
|---|---|
| Minimum TLS version | TLS 1.2 |
| Preferred TLS version | TLS 1.3 |
| HTTP allowed | No ( |
| Cipher suites |
iOS App Transport Security (ATS):
- No
NSAllowsArbitraryLoads: true - All API calls go to
https://drop-app.vercel.app(HTTPS enforced)
Android Network Security Config:
<!-- android/app/src/main/res/xml/network_security_config.xml — Phase 2 -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">getdrop.no</domain>
<domain includeSubdomains="true">drop-app.vercel.app</domain>
</domain-config>
</network-security-config>
4.3 Network Request EncryptionSecurity
- All requests over HTTPS (enforced
viabyATS + Android network security config) Sensitive payloads encrypted at application layer (in addition to TLS):Vercel){{Yes/No}}- Request signing (HMAC) — not implemented (
HMAC):Phase2){{Yes/No}} - Application-layer encryption for payment payloads — TBD (Phase 2)
5. Application Security
5.1 Code Obfuscation
| Platform | Tool | Status |
|---|---|---|
| Android | Done (Expo default) |
|
| iOS | Done (Expo default) |
|
| JavaScript | Hermes bytecode compilation | |
| default) |
ProGuard rules:Hermes: Expo android/app/proguard-rules.proTODO:SDK Review54 ProGuarduses rulesHermes by default. JavaScript is compiled to ensurebytecode, nomaking over-strippingreverse ofengineering reflection-dependentsignificantly code.harder than raw JS.
5.2 Root / Jailbreak Detection
Current (Phase 1): Not implemented.
Library:Phase 2 implementation: {{expo-device | react-native-jail-monkey | custom}}
// Phase 2 — lib/integrity.ts
import JailMonkey* as Device from 'jail-monkey'expo-device';
export function checkDeviceIntegrity(): SecurityResult{ compromised: boolean; reason: string } {
const isJailbroken = JailMonkey.isJailBroken();
const isDebuggedBuild = JailMonkey.isDebuggedMode();
const onExternalStorage = JailMonkey.isOnExternalStorage();
// Androidexpo-device provides basic device info
// For more robust detection: react-native-device-info or jail-monkey
const isEmulator = !Device.isDevice;
return {
compromised: isJailbrokenisEmulator ||&& isDebuggedBuild,!__DEV__,
reason: isJailbrokenisEmulator ? 'jailbroken' : isDebuggedBuild ? 'debug'emulator_in_production' : 'clean',
};
}
Response to compromised device:device (Phase 2):
{{Warnuseruser:and"Dropallower ikke tilgjengelig på enheter med modifisert programvare"- Allow use with
reducedwarningfunctionality(not|blockingBlockinaccessPhaseentirely2)|—Loghighersilently}}blocking for payment flows Justification:Logevent to backend{{Explainsecuritydecision}}
5.3 Tamper Detection
App bundle signature verification on launchResource hash verification for critical assetsBackend validates app version — block known compromised versions
5.4 Debug Detection
// Detect if app is running under debugger in production
if (__DEV__ === false && detectDebugger()) {
logSecurityEvent('debugger_attached');
terminateSession();
}
Production builds must have:
-
__DEV__mode disabled (Expo release build) -
Console.console.logstripped (BabelExpo/Metroplugin:defaulttransform-remove-console)in release builds) - Flipper disabled (Expo default in release builds)
- Source maps NOT bundled with the app
binary—(upload to Sentryseparately)separately (Phase 2)
5.54 Reverse Engineering Prevention
| Measure | Phase 2 | |
|---|---|---|
| No hardcoded secrets | API keys fetched |
|
| No plain API keys in bundle | Done | |
| Sensitive logic on server | Done | |
| Binary protection | Hermes bytecode (done) | ProGuard |
TODO: Run MobSF static analysis before each major release and review findings.
6. OWASP Mobile Top 10 Checklist
| # | Risk | Phase 1 Status | Notes |
|---|---|---|---|
| M1 | Improper Credential Usage | Partial |
|
| M2 | Inadequate Supply Chain Security | Unknown |
npm audit not run in CI yet |
| M3 | Insecure Authentication/Authorization | Pass |
BankID + JWT validated server-side |
| M4 | Insufficient Input/Output Validation | Pass |
|
| M5 | Insecure Communication | Pass |
|
| M6 | Inadequate Privacy Controls | Partial |
Data |
| M7 | Insufficient Binary Protections | Pass |
|
| M8 | Security Misconfiguration | Pass |
No dev configs in prod __DEV__ disabled |
| M9 | Insecure Data Storage | Fail |
|
| M10 | Insufficient Cryptography | Pass |
Critical action before production: M9 (Insecure Data Storage) — migrate Bearer token from AsyncStorage to expo-secure-store. This is the single most important security fix before v1.0 production launch.
7. Security Testing Tools
| Tool | Type | When | Output |
|---|---|---|---|
| MobSF (Mobile Security Framework) | Static analysis | Pre-release | Risk report |
npm audit / pnpm audit |
|||
| Burp Suite | Network proxy | Penetration test | API vulnerabilities |
| Frida | Dynamic analysis | Penetration test | Runtime behavior |
| OWASP ZAP | Automated API scan | CI/CD | Vulnerability |
|
Penetration test cadence: then annually.
Last pentest date: {{Annual | Before eachv1.0 majorproduction releaselaunch |(pre-launch), Quarterly}}Not yet conducted.
Next pentest date: {{DATE}}Before production launch.{{DATE}}
8. Compliance Requirements
| Requirement | Applicable | Status | Notes |
|---|---|---|---|
| GDPR ( |
Yes |
Privacy policy at /privacy; data |
|
| PSD2 (Payment Services Directive 2) | Yes | Pass | PISP/AISP model; Open Banking; pre-payment disclosure implemented |
| Finanstilsynet (Norwegian FSA) | Yes | In progress | Regulatory compliance ongoing |
| Hvitvaskingsloven (AML) | Yes | Pass | 5-year data retention; KYC via BankID |
| CCPA |
No |
Norwegian |
|
| COPPA |
No |
18+ |
|
| PCI DSS |
No |
Drop never |
|
| App Store |
Yes | Must |
|
| Play Store |
Yes | Must |
|
| HIPAA |
No |
No |
9. Security Roadmap
Phase 1 (Current — MVP)
- HTTPS/TLS enforced
- BankID authentication via expo-web-browser
- Hermes bytecode (basic obfuscation)
- CRITICAL: Migrate Bearer token to expo-secure-store (before production)
Phase 2 (Pre-Launch)
- expo-secure-store for all tokens
- Certificate pinning for API endpoints
- Biometric authentication (Face ID / Touch ID)
- Screenshot prevention on financial screens
- Root/jailbreak detection with user warning
- Mobile penetration test (MobSF + Burp Suite)
- Source maps configured (Sentry, not bundled)
Phase 3 (Post-Launch)
- App attestation (Play Integrity API / App Attest)
- Transaction anomaly detection
- Session device binding
- Refresh token rotation
- Self-service account deletion (GDPR)
Approval
| Role | Name | Date | Signature |
|---|---|---|---|
| Author | John (AI Director) | 2026-02-23 | |
| Mobile Lead | |||
| Security Lead | |||
| Legal |