Flutter's appeal is that one Dart codebase ships to both the App Store and Play Store. Its security risk is the same fact from the other side: one mistake ships to both, and Dart's ahead-of-time compilation does not hide anything you thought it did. Strings compiled into a release binary — API keys, tokens, backend URLs — are recoverable from an extracted .ipa or .apk, so a hardcoded secret is a shipped secret on every device. Two more gaps recur across Flutter apps: storing sensitive data in shared_preferences, which is plaintext, and silencing TLS errors by making the certificate callback return true. The supply chain has its own history, too: CVE-2026-27704 is a path-traversal (zip-slip) flaw in Dart's Pub package extraction, where a malicious package could use symlinks to write files outside the pub cache, fixed in Dart 3.11.0 and Flutter 3.41.0. And the Android platform beneath Flutter has seen task-hijacking attacks like StrandHogg 2.0 (CVE-2020-0096). This guide covers what you own in Dart and Flutter.
Where should a Flutter app store secrets and tokens?
In the platform secure store, never in shared_preferences and never hardcoded. shared_preferences is an unencrypted plist on iOS and an XML file on Android, readable on a rooted or jailbroken device. The flutter_secure_storage package wraps the iOS Keychain and the Android Keystore/EncryptedSharedPreferences:
// DANGEROUS -- plaintext on disk
final prefs = await SharedPreferences.getInstance();
await prefs.setString('authToken', token);
// SAFE -- Keychain on iOS, Keystore-backed on Android
const storage = FlutterSecureStorage();
await storage.write(key: 'authToken', value: token);
For secrets the app needs at build time, inject them with --dart-define from your CI secret store rather than committing them, and understand that any value present in the binary is still extractable — the real fix for high-value secrets is to keep them server-side and never ship them at all.
How do I keep TLS validation intact in Dart?
By never overriding the certificate callback to accept everything. dart:io's HttpClient exposes badCertificateCallback, and the single most damaging Flutter networking mistake is returning true from it to make a self-signed dev certificate work:
// DANGEROUS -- accepts every certificate, defeats TLS entirely
httpClient.badCertificateCallback = (cert, host, port) => true;
Leave the default validation in place. If you need to trust a private certificate authority, add its certificate to a SecurityContext rather than disabling the check, and for high-risk apps pin the expected public key. Any code path that blanket-trusts certificates turns HTTPS into an unauthenticated channel that a network attacker can read and modify.
What about WebViews and the package supply chain?
A webview_flutter instance that registers a JavaScriptChannel and then loads untrusted content exposes Dart callbacks to that page — only register channels for content you fully control. The larger surface is pub.dev. Flutter apps pull dozens of transitive packages, and pubspec.lock records the exact resolved versions; commit it, pin direct dependencies to explicit versions, and review new packages before they ship. CVE-2026-27704 is the reminder that even the tooling that installs packages has been a target: keep your Dart and Flutter SDKs current (3.11.0 / 3.41.0 or later) so the fixed, symlink-hardened extractor is the one running on your build machines and developer laptops.
Dart and Flutter security checklist
| Practice | Why it matters |
|---|---|
Store secrets in flutter_secure_storage, not shared_preferences | Preferences are plaintext and recoverable off-device |
| Never hardcode secrets in Dart source | AOT binaries still ship extractable strings |
Never return true from badCertificateCallback | Blanket trust defeats TLS |
Add private CAs to a SecurityContext; pin for high-risk apps | Keeps validation on while trusting your own CA |
| Register WebView JS channels only for trusted content | Blocks the native-callback bridge |
Commit and pin pubspec.lock; review new packages | Locks the pub.dev graph |
| Keep Dart/Flutter SDKs patched | Fixes tooling flaws like CVE-2026-27704 |
How Safeguard Helps
Safeguard's software composition analysis resolves the full pubspec.lock graph behind a Flutter app — the transitive packages you never added directly included — and maps known advisories to the exact versions you ship, so a vulnerable package surfaces before a release build consumes it. Griffin, our AI analysis engine, inspects new package versions for the behavioral signatures of a hijacked or typosquatted dependency, the kind of check pub.dev's popularity metrics alone will not catch. When a safe upgrade exists, auto-fix opens a tested pull request with the bump applied, and developers can run the same scan locally with the Safeguard CLI before pushing. Secure storage, intact TLS, and careful WebView bridging stay your responsibility; Safeguard secures the package layer beneath the app.
Bring your Flutter supply chain under control — start for free or read the documentation.
Frequently Asked Questions
Are secrets safe if I compile them into a Flutter release build?
No. Dart's ahead-of-time compilation produces a native binary, but string constants — API keys, tokens, URLs — remain recoverable from an extracted .ipa or .apk. Inject build-time values with --dart-define from a CI secret store rather than committing them, and keep genuinely sensitive secrets server-side so they never ship on the device at all.
Why is shared_preferences a bad place for a login token?
It stores data as an unencrypted property list on iOS and an XML file on Android, both readable on a rooted or jailbroken device and captured in some backups. Use flutter_secure_storage, which stores values in the iOS Keychain and the Android Keystore-backed encrypted store, for tokens and any credential.
Is it ever okay to override badCertificateCallback?
Not to return true unconditionally, which disables TLS validation for every request. If you need to trust a private certificate authority, add its certificate to a SecurityContext so normal validation continues against that CA, and consider public-key pinning for high-risk apps. Blanket-accepting certificates exposes the app to network interception.
How do I know if a pub.dev package is vulnerable?
Track the exact versions in pubspec.lock and run them through a software composition analysis tool that maps advisories to those versions, including transitive ones. Reachability analysis then tells you whether your app actually calls the vulnerable code, so you prioritize the packages that matter rather than every advisory in the graph.