Skip to main content

Mobile Security

Mobile Security

Project: Drop — Fintech Payment App{{PROJECT_NAME}} Version: 0.1.0{{VERSION}} Date: 2026-02-23{{DATE}} Author: John (AI Director, ALAI){{AUTHOR}} Status: Draft | In Review | Approved Reviewers: Alem Bašić (CEO){{REVIEWERS}}

Document History

Version Date Author Changes
0.1 2026-02-23{{DATE}} John{{AUTHOR}} 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.

in
Threat Actor Goal Attack Vector Likelihood Impact Mitigation
Malicious app on device Steal Bearerauth tokentokens Shared AsyncStoragestorage access Medium CriticalHigh PhaseSecure 2:storage migrate token to expo-secure-store(Keychain/Keystore)
Network interceptor (MITM) Intercept payment API calls Rogue WiFi, proxy Medium CriticalHigh TLS 1.3 enforced; certificateCertificate pinning Phase 2
Reverse engineer Extract API keys, business logic APK/IPA decompilation Medium Medium HermesCode bytecode;obfuscation, no hardcoded secrets
Stolen/lost device InitiateAccess paymentsuser as victimdata Physical access + stored token High CriticalHigh Phase 2: biometricBiometric lock, tokendata bindingencryption, remote wipe
Rooted/jailbroken device Bypass security controls OS privilege escalation Low High Root/jailbreak detection Phase 2
SessionMalicious hijackinginsider ReplayAccess stolenuser tokendata NetworkSource capturecode access Low CriticalHigh TokenSecrets expiryin (7vault, days);no HTTPSPII enforced
Social engineeringSteal BankID credentialsPhishingMediumCriticalUser education; BankID handles its own authlogs

Assets to protect (priority order):protect:

  1. BearerAuth tokentokens (allowsaccess payment+ initiation from user's bank account)refresh)
  2. User PII (name, email, phonepayment — BankID-verified identity)info)
  3. TransactionAPI historykeys (financialand data)secrets
  4. ExchangeCached ratesensitive API access
  5. App integrity (prevent tampered builds)data

2. Authentication

2.1 Biometric Authentication

Current state (Phase 1): Not implemented. Login is email + password only.

PhaseImplementation: 2{{expo-local-authentication implementation:| 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: 'BekreftVerify identitetenyour din'identity',
    fallbackLabel: 'BrukUse PIN',
    cancelLabel: 'Avbryt'Cancel',
    disableDeviceFallback: false,
  });

  return result.success;
}

PlannedUse cases for biometric use cases (Phase 2):auth:

  • App unlock after backgrounding > {{5 minutesminutes}}
  •  View sensitive data (payment info, full account number)
  • Confirm high-value remittance transactions (amount > 5000 NOK){{$100}})
  • Change security settings
  •  View full transaction history

Fallback: 4-digit PIN (same as web registration flow)codehash{{expo-secure-store}} stored in expo-secure-storehash (bcrypt, not reversible)


2.2 Session Management

Property Current (Phase 1)Phase 2 TargetValue
Access token lifetime 7 days (Bearer){{15 minutes (access) + 7 days (refresh)minutes}}
Refresh token lifetime Not{{30 implementeddays}}
Refresh token rotation RotateYes — single use, new token issued on each use
Session timeoutNever (token-based)background) App lock after {{5 minminutes}} backgroundinactive
Max concurrent sessions Not limited{{3 devices maxdevices}}
Force logout triggers Manual logout onlyPassword change, suspicious activity, admin revokerevocation

Token storage (current Phase 1):storage:

// 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 — DOnever NOTuse USEAsyncStorage FORor TOKENSMMKV for tokens
// await AsyncStorage.setItem('access_token', token); // Unencrypted!DO NOT USE

2.3 BankIDToken AuthenticationStorage (Secure Enclave)

PlatformMechanismAccess Level
iOSKeychain ServiceskSecAttrAccessibleWhenUnlockedThisDeviceOnly
AndroidAndroid KeystoreBIOMETRIC_STRONG or DEVICE_CREDENTIAL

BankIDCannot isbe Drop'sbacked primaryup: strongBoth authenticationmechanisms mechanism.are The BankID flow is handled by the BankID OIDC providerdevice-boundDroptokens doesdo not see or store BankID credentials.

Mobile BankID flow (ADR-011):

1. GET /v1/auth/bankid/initiate?platform=mobile → { redirectUrl, state }
2. Open BankID in expo-web-browser (secure, isolated browser)
3. BankID redirectstransfer to drop://auth/callback?code=&state=new 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:devices.

  • 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 single-use and time-limited

3. Data Protection

3.1 Secure Storage Policy

MMKV,
Data Type Phase 1 StoragePhase 2Allowed Storage Forbidden Storage
BearerAccess token AsyncStorage (unencrypted)Keychain/Keystore expo-secure-storeAsyncStorage, (Keychain/Keystore) HardcodedSQLite
Refresh token Not implementedKeychain/Keystore expo-secure-storeAsyncStorage, MMKV
Encryption key AsyncStorageKeychain/KeystoreAny other storage
User PII Not cached locallySQLite (encrypted with SQLCipher)encrypted) AsyncStorage
Non-sensitive prefs AsyncStorage AsyncStorage/ MMKV
Biometric key hashNot implementedexpo-secure-storeAny 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}}

// PhaseSQLCipher 2initialization with lib/database.ts
import * as SQLitekey from 'expo-sqlite';Keystore
importconst * as SecureStore from 'expo-secure-store';

async function openDatabase() {
  // Generate encryption key on first launch
  let dbKeyencryptionKey = await SecureStore.getItemAsync('db_encryption_key');
ifconst (!dbKey) {
    dbKeydb = generateRandomKey(256);  // 256-bit random key
    await SecureStore.setItemAsync('db_encryption_key', dbKey);
  }

  return await SQLite.openDatabaseAsync('drop.app.db', {
  key: dbKeyencryptionKey,
});
}

Key generation: On first launch, generate 256-bit random key, store in Keychain/Keystore.

Encrypted fields (beyond DB encryption):

FieldEncryptionKey Source
Payment card (last 4 only stored)N/A — tokenizedStripe/Braintree
SSN / national IDAES-256-GCMKeychain/Keystore
Private messagesEnd-to-end (Signal protocol)Derived keys

3.3 Sensitive Data in Memory

Rule Phase 1 StatusPhase 2 Implementation
Clear passwords after use PartiallyOverwrite (useState cleanup)Overwritestring variable, trigger GC
No PIIsensitive data in logsImplemented — no console.log(user) in production paths Custom logger strips PII fields
No PIIsensitive data in crash reportsTBD — no crash reporting in Phase 1 Sentry beforeSend hook scrubs payload
No PIIsensitive data in analytics TBD — no analytics in Phase 1HashMap user IDsID → hashed ID before sending

3.4 Screenshot Prevention

Drop displays financial data (balance, transaction amounts). Screenshots should be prevented on sensitive screens.

// Phase 2iOSsensitiveprevent screenshots programmatically
// (Note: React Native does NOT expose this API — native module required)

// Android — prevent screenshots and screen protectionrecording
import { Platform } from 'react-native';
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();
}, []);

Note:Screens iOSrequiring screenshot preventionprevention:

via
  •  Payment / card details screen
  •  Account balance screen
  •  Personal document viewer
  • expo-screen-capture{{OTHER_SENSITIVE_SCREEN}}
  • shows
a blank screen in the app switcher but cannot prevent all screenshots. Android FLAG_SECURE prevents screenshots and screen recording.


3.5 Clipboard Protection

  • Password fields: secureTextEntry={true} in React Native TextInput — disables clipboard by default on iOS
  • TransactionSensitive amountsdata copiedprogrammatically to clipboard:copied: clear clipboard after {{60 seconds (Phase 2)seconds}}
  • Account numbers: never copied toCustom clipboard byhook: appuseSensitiveCopy(value, clearAfterMs: shown as masked60000)

4. Network Security

4.1 Certificate Pinning

Current (Phase 1): Not implemented. Standard HTTPS/TLS only.

PhaseLibrary: 2{{react-native-ssl-pinning implementation:| OkHttp certificate pinner (Android native) | URLSession (iOS native)}}

// Phase 2 — requiresUsing react-native-ssl-pinning or OkHttp pinner
// Pinned endpoints:
// - drop-app.vercel.app (production API)
// - BankID OIDC endpoints
const response = await fetch('https://drop-app.vercel.app/api/transactions'api.domain.com/endpoint', {
  method: 'GET'POST',
  //pkPinning: Certificatetrue,
  pinningsslPinning: configuration{
    herecerts: ['api_cert_sha256_fingerprint'],
  },
  headers: { Authorization:/* `Bearer... ${token}`*/ },
  body: JSON.stringify(payload),
});

Pinned endpoints:

  • api.{{domain.com}} — primary API
  • auth.{{domain.com}} — authentication

Certificate rotation process:

  1. Pin bothBOTH current cert and backup cert simultaneously
  2. Deploy app update with new cert added (OTA via EAS Update)
  3. After old cert expires, remove old cert from pins
  4. Test rotation in staging before productionfirst

Handling pin failures:

  • Log as security event to backend
  • Show 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 (VercelATS enforcesenforced HTTPS)on iOS, enforce on Android)
Cipher suites TLSForward-secrecy 1.3 defaultonly (forward-secrecy)ECDHE, DHE)

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 SecurityEncryption

  • All requests over HTTPS (enforced byvia Vercel)ATS + Android network security config)
  •  Sensitive payloads encrypted at application layer (in addition to TLS): {{Yes/No}}
  • Request signing (HMAC) — not implemented (PhaseHMAC): 2)
  •  Application-layer encryption for payment payloads — TBD (Phase 2){{Yes/No}}

5. Application Security

5.1 Code Obfuscation

default)
Platform Tool Status
Android ProGuard/ProGuard / R8 via(enabled Expoby releasedefault buildin release) Done (Expo default){{Done/TODO}}
iOS Bitcode + Swift compiler optimizations Done (Expo default){{Done/TODO}}
JavaScript Hermes bytecode compilation Done{{Done/TODO}}
JavaScript (Expoextra) {{metro-react-native-babel-preset obfuscation}}{{Done/TODO}}

Hermes:ProGuard rules: Expoandroid/app/proguard-rules.pro SDKTODO: 54Review usesProGuard Hermes by default. JavaScript is compiledrules to bytecode,ensure makingno reverseover-stripping engineeringof significantlyreflection-dependent hardercode.

than raw JS.


5.2 Root / Jailbreak Detection

Current (Phase 1): Not implemented.

PhaseLibrary: 2{{expo-device implementation:| react-native-jail-monkey | custom}}

// Phase 2 — lib/integrity.ts
import * as DeviceJailMonkey from 'expo-device'jail-monkey';

export function checkDeviceIntegrity(): SecurityResult {
  compromised:const boolean;isJailbroken reason:= stringJailMonkey.isJailBroken();
  }const {isDebuggedBuild = JailMonkey.isDebuggedMode();
  const onExternalStorage = JailMonkey.isOnExternalStorage(); // expo-device provides basic device info
  // For more robust detection: react-native-device-info or jail-monkey
  const isEmulator = !Device.isDevice;Android

  return {
    compromised: isEmulatorisJailbroken &&|| !__DEV__,isDebuggedBuild,
    reason: isEmulatorisJailbroken ? 'emulator_in_production'jailbroken' : isDebuggedBuild ? 'debug' : 'clean',
  };
}

Response to compromised device (Phase 2):device:

  • {{Warn user:user "Dropand er ikke tilgjengelig på enheter med modifisert programvare"
  • Allowallow use with warningreduced (notfunctionality blocking| inBlock Phaseaccess 2)entirely | higherLog blocking for payment flowssilently}}
  • LogJustification: security{{Explain event to backenddecision}}

5.3 Tamper Detection

  •  App bundle signature verification on launch
  •  Resource hash verification for critical assets
  •  Backend 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.log stripped (Expo/MetroBabel defaultplugin: in release builds)transform-remove-console)
  • Flipper disabled (Expo default in release builds)
  • Source maps NOT bundled with the app binary (upload to Sentry separately (Phase 2)separately)

5.45 Reverse Engineering Prevention

Obfuscated Android),
Measure Phase 1Phase 2Implementation
No hardcoded secrets DoneAll — API URL only, no keysAPI keyssecrets fetched from vault at runtime fromor secureinjected configvia CI
No plain API keys in bundle Done Done+ fetched from secure config endpoint
Sensitive logic on server DoneBusiness logic Droprequiring issecurity pass-through;stays payments initiated server-backend-sideDone
Binary protection HermesProGuard bytecode/ R8 (done) ProGuardBitcode rulesstripping review(iOS)

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{{Pass/Fail/N/A}} Bearer tokenTokens in AsyncStorageKeychain, (unencrypted)no hardcoded Phase 2 fixcreds
M2 Inadequate Supply Chain Security Unknown{{Pass/Fail}} Dependency audit npm audit not run in CI yet
M3 Insecure Authentication/Authorization Pass{{Pass/Fail}} BankID + JWT validated server-side
M4 Insufficient Input/Output Validation Pass{{Pass/Fail}} ReactZod Nativevalidation TextInputon handlesall basic validation; XSS not applicable to React Nativeinputs
M5 Insecure Communication Pass{{Pass/Fail}} HTTPSTLS enforced;+ cert pinning Phase 2
M6 Inadequate Privacy Controls Partial{{Pass/Fail}} Data minimization done;minimization, GDPR consent in web app — mobile privacy notice TBD
M7 Insufficient Binary Protections Pass{{Pass/Fail}} HermesObfuscation, bytecode;no Expo releasedebug build in prod
M8 Security Misconfiguration Pass{{Pass/Fail}} No dev configs in prod build; __DEV__ disabledbuild
M9 Insecure Data Storage Fail{{Pass/Fail}} BearerKeychain/Keystore token+ inencrypted unencrypted AsyncStorage — fix before productionDB
M10 Insufficient Cryptography Pass{{Pass/Fail}} TLSAES-256, 1.3;SHA-256, no MD5/SHA-1 usage

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 auditFrida DependencyDynamic scananalysis EveryPenetration PRtest CVERuntime reportbehavior
Expo Security ChecksObjection Build-timeRuntime analysis EASPenetration Buildtest SecurityBypass warningschecks
Burp Suite Network proxy Penetration test API vulnerabilities
FridaDynamic analysisPenetration testRuntime behavior
OWASP ZAP Automated API scan CI/CD Vulnerability report
npm audit / yarn auditDependency scanEvery PRCVE report

Penetration test cadence: {{Annual | Before v1.0each productionmajor launchrelease (pre-launch),| then annually.Quarterly}} Last pentest date: Not yet conducted.{{DATE}} Next pentest date: Before production launch.{{DATE}}


8. Compliance Requirements

DoParentalUseAppGoogleBAA
Requirement Applicable StatusNotes
GDPR (EU/EEAEU users) Yes{{Yes/No}} PartialPrivacy policy at /privacy;User data deletiondeletion, viaexport support; consent management in web app
PSD2 (Payment Services Directive 2)YesPassPISP/AISP model; Open Banking; pre-payment disclosure implemented
Finanstilsynet (Norwegian FSA)YesIn progressRegulatory compliance ongoing
Hvitvaskingsloven (AML)YesPass5-year data retention; KYC via BankIDendpoints
CCPA (California users) No{{Yes/No}} N/A NorwegianNot marketSell only — no California users targetedoption
COPPA (under 13) No{{Yes/No}} N/A 18+consent minimum age (BankID requirement)flow
PCI DSS (payment data) No{{Yes/No}} N/A DropPCI-compliant SDK (Stripe/Braintree) — never storesstore card data — pass-through model
App Store Privacyprivacy Labellabel Yes TODO MustStore completeConnect beforeprivacy submissionquestionnaire
Play Store Datadata Safetysafety Yes TODO MustPlay completeConsole beforedata submissionsafety form
HIPAA (health data) No{{Yes/No}} N/A Nowith healthvendors, dataencryption at rest+transit

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