Skip to content

[Security] walletVault creates an unencrypted MMKV instance — secret keys stored in plaintext on device #122

Description

@cybermax4200

Why this matters now

The v0.3 roadmap gates ECO reward payouts on in-app wallets. Any path that encrypts on-chain funds is only as secure as the key store. Right now walletVault opens a plain new MMKV({ id: 'wallet-vault' }) with no encryptionKey, meaning every secret key (Stellar S… seed) is written as a cleartext string to the device's MMKV flat-file. A rooted device, ADB pull, or backup extraction yields every key with no further effort.

Problem / What

src/services/walletVault.ts instantiates MMKV without the encryptionKey option:

const storage = new MMKV({ id: 'wallet-vault' });

MMKV supports AES-256 encryption when an encryptionKey is provided. The key should be derived per-device using react-native-keychain (iOS Keychain / Android Keystore) so it is hardware-bound and never leaves the secure enclave. The flow is: on first launch, generate a random 32-byte key → store it in the OS secure enclave → pass it as encryptionKey to MMKV on every subsequent open.

Key Challenges

  • react-native-keychain is not yet a dependency; it must be added and linked.
  • The migration path: existing users who already have a secret stored in the unencrypted vault must be transparently migrated (read the old value → re-write to the encrypted instance → delete from the plain instance) without losing their key.
  • The encryption key itself must never be serialised to JS state, logs, or Zustand stores.
  • On Android, Keystore key generation may throw on API < 23; a fallback (or minimum SDK guard) is needed.

Acceptance Criteria

  • walletVault opens MMKV with a device-bound encryptionKey sourced from the OS secure enclave.
  • react-native-keychain is added as a pinned dependency and linked for both platforms.
  • A one-time migration function runs on first launch after the update and is covered by a Jest unit test (mock keychain + mock MMKV).
  • The plain-instance MMKV file is deleted after successful migration.
  • Existing walletVault.test.ts tests continue to pass (mock the keychain in the test environment).
  • No secret key value ever appears in console output, Zustand devtools, or persisted store snapshots.

Relevant files / functions

  • src/services/walletVault.ts — entire file
  • src/__tests__/walletVault.test.ts — extend to cover migration
  • package.json — add react-native-keychain

Out of scope

  • Changing how secrets are used after retrieval (signChallengeXDR, buildPaymentXDR).
  • Biometric gate (Touch ID / Face ID) before key retrieval — that is a separate UX feature.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions