Sensitive value written to an unprotected plaintext file

ID

dart.storage.unprotected_file_storage

Severity

high

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

medium

Resource

Storage

Language

Dart

Description

Sensitive material (token, password, secret, JWT, PIN, OTP) is written to a plaintext file via dart:io File.writeAsString / writeAsBytes (sync or async). The app-private file is readable on rooted / jailbroken devices and via OS backups.

Rationale

Like SharedPreferences, files created under the app’s documents / cache / support directories live inside the OS sandbox — but the sandbox does not encrypt the file content. Any of the following exposes the bytes:

  • Rooted / jailbroken device file-system access.

  • adb backup (Android < 13 when allowBackup="true").

  • iCloud Drive / Google Drive backup of the app container.

  • Forensic image of the device.

  • A second app that exploits a separate file-permission bug.

Storing tokens, passwords, JWTs, OTPs, recovery phrases, or hard-coded API keys in plain files therefore must be considered effectively public. The rule narrows by the IDENTIFIER name being written (token / password / secret / apiKey / jwt etc.) to avoid flagging legitimate writes of non-sensitive content.

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

class WriteSecrets {
  Future<void> writePlain(File f, String token, String apiKey, String password, String secret) async {
    // VULNERABLE: Sensitive value written to an unprotected plaintext file
    await f.writeAsString(token);
    // VULNERABLE: Sensitive value written to an unprotected plaintext file
    await f.writeAsString(apiKey);
    // VULNERABLE: Sensitive value written to an unprotected plaintext file
    await f.writeAsString(password);
    // VULNERABLE: Sensitive value written to an unprotected plaintext file
    await f.writeAsString(secret, mode: FileMode.append);
    // VULNERABLE: Sensitive value written to an unprotected plaintext file
    f.writeAsStringSync(token);
    // VULNERABLE: Sensitive value written to an unprotected plaintext file
    await f.writeAsBytes(secret.codeUnits);
    // VULNERABLE: Sensitive value written to an unprotected plaintext file
    f.writeAsBytesSync(password.codeUnits);
  }
}

Remediation

Use flutter_secure_storage for small secrets, or encrypt the bytes yourself using a key derived from a user secret (Argon2id / scrypt / PBKDF2 >= 600 000 iterations) before writing. For large blobs, encrypt with AES-GCM and store the key in secure storage.

// Vulnerable
await file.writeAsString(token);
file.writeAsBytesSync(secret.codeUnits);

// Fixed: small value -> flutter_secure_storage
const storage = FlutterSecureStorage();
await storage.write(key: 'token', value: token);

// Fixed: large blob -> encrypt-before-write
final cipher = AesGcm.with256bits();
final box = await cipher.encrypt(plaintextBytes,
  secretKey: derivedKey, nonce: cipher.newNonce());
await file.writeAsBytes(box.concatenation());

Configuration

This detector does not need any configuration.

References

© 2026 Xygeni. All rights reserved