Sensitive value stored in cleartext SharedPreferences

ID

dart.storage.cleartext_storage_preferences

Severity

high

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

medium

Resource

Storage

Language

Dart

Description

Sensitive material (token, password, secret, API key, session id, PIN, OTP) is written to SharedPreferences. SharedPreferences are unencrypted XML / Plist files in the app sandbox — readable on rooted / jailbroken devices and via OS backups.

Rationale

shared_preferences is the default per-app key-value store in Flutter and is intentionally NOT encrypted. The backing files live in /data/data/<pkg>/shared_prefs/ on Android and the app’s Plist container on iOS. They are protected by the OS sandbox, but that protection fails in several common cases:

  • Rooted / jailbroken devices (consumer-installed root kits, enterprise BYOD with root for testing, malware-installed root).

  • Android cloud backup (android:allowBackup="true", the default in older targets) — preferences are exfiltrated via adb backup or bmgr.

  • iCloud / Google Drive cross-device sync of the app container.

  • Forensic acquisition (Cellebrite, Magnet AXIOM) of a seized device.

Storing authentication material here means anyone with file-system access to the device has the user’s credentials. The rule narrows by KEY NAME (token, password, apiKey, jwt, etc.) so generic preferences like theme / locale / counter don’t fire.

The following code illustrates a vulnerable pattern detected by this rule:

class StorePrefs {
  Future<void> sensitiveKeys() async {
    final prefs = await SharedPreferences.getInstance();
    // VULNERABLE: Sensitive value stored in cleartext SharedPreferences
    await prefs.setString('token', 'value');
    // VULNERABLE: Sensitive value stored in cleartext SharedPreferences
    await prefs.setString('access_token', 'value');
    // VULNERABLE: Sensitive value stored in cleartext SharedPreferences
    await prefs.setString('refresh_token', 'value');
    await prefs.setString('apiKey', 'value');
    // VULNERABLE: Sensitive value stored in cleartext SharedPreferences
    await prefs.setString('api_key', 'value');
    // VULNERABLE: Sensitive value stored in cleartext SharedPreferences
    await prefs.setString('user_password', 'value');
    // VULNERABLE: Sensitive value stored in cleartext SharedPreferences
    await prefs.setString('jwt', 'value');
    // VULNERABLE: Sensitive value stored in cleartext SharedPreferences
    await prefs.setString('session_id', 'value');
    // VULNERABLE: Sensitive value stored in cleartext SharedPreferences
    await prefs.setString('credential', 'value');
    // VULNERABLE: Sensitive value stored in cleartext SharedPreferences
    await prefs.setStringList('auth_tokens', []);
    await prefs.setBool('isAuthenticated', true);
    // VULNERABLE: Sensitive value stored in cleartext SharedPreferences
    await prefs.setInt('pin', 1234);
  }
}

Remediation

Use flutter_secure_storage for any value the user expects to be confidential. It’s backed by Keychain on iOS (with appropriate accessibility class) and EncryptedSharedPreferences / Keystore on Android. For short-lived session material, prefer in-memory storage bound to the app process lifetime.

// Vulnerable
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', authToken);
await prefs.setString('pin', userPin);

// Fixed
const storage = FlutterSecureStorage();
await storage.write(key: 'token', value: authToken);
await storage.write(key: 'pin', value: userPin);

Configuration

This detector does not need any configuration.

References

© 2026 Xygeni. All rights reserved