Skip to content

[Security] Ethereum private key stored in plaintext via Capacitor Preferences (unencrypted device storage) #3372

Description

@numbers-official

Summary

The user's Ethereum private key — which controls their blockchain identity and potential cryptocurrency assets (NUM tokens) — is stored as a plaintext string using Capacitor's Preferences API. On Android this maps to SharedPreferences (an XML file in the app's data directory) and on iOS to UserDefaults, both of which are unencrypted key-value stores.

Affected Files

Primary: Plaintext key storage

  • src/app/shared/collector/signature/capture-app-web-crypto-api-signature-provider/capture-app-web-crypto-api-signature-provider.service.ts
    • Line 22: Private key exposed as observable via this.preferences.getString$(PrefKeys.PRIVATE_KEY)
    • Lines 65-69: Private key written to Preferences in plaintext after account creation
    • Lines 88-93: getPrivateKey() retrieves from unencrypted storage

Storage layer (no encryption)

  • src/app/shared/preference-manager/preferences/capacitor-storage-preferences/capacitor-storage-preferences.ts
    • Lines 127-131: storeValue() writes raw value to Capacitor Preferences with no encryption layer

Also affected

  • src/app/shared/dia-backend/auth/dia-backend-auth.service.ts, lines 446-448: Auth tokens and JWT refresh tokens stored in the same unencrypted storage

Impact

  • Severity: Critical
  • On a rooted/jailbroken device, via ADB backup, or through a filesystem exploit, an attacker can trivially extract the private key
  • This key controls: digital signatures on all captures (integrity wallet), potential NUM token assets, and the user's Numbers Protocol blockchain identity
  • Combined with the existing issue of private key transmission ([Security] Private key transmitted to backend via unprotected API calls in wallet service #3370) and postMessage handlers lacking origin validation, this creates a compounding chain of vulnerabilities around the most sensitive data in the app

Suggested Fix

  1. Replace Capacitor Preferences with platform-specific secure storage for all sensitive data:
    • Android: Use Android Keystore / EncryptedSharedPreferences
    • iOS: Use iOS Keychain
  2. Consider a Capacitor plugin like capacitor-secure-storage-plugin which wraps platform keystores
  3. Migrate existing plaintext keys on first app launch after the update
  4. Also migrate auth tokens and JWT refresh tokens to secure storage

References

Generated by Health Monitor with Omni

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    securityPull requests that address a security vulnerability

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions