OWASP Top 10 For Flutter – M8: Security Misconfiguration in Flutter & Dart
Welcome back to our deep dive into the OWASP Mobile Top 10 for Flutter developers. OWASP (Open Worldwide Application Security Project) maintains this industry-standard risk ranking to help developers prioritize mobile security efforts.
In earlier parts, we tackled:
Each is a critical piece of the mobile security puzzle.
In this eighth article, we focus on M8: Security Misconfiguration, a vulnerability that often hides in plain sight. Unlike the complex code vulnerabilities we discussed earlier, misconfigurations are usually simple oversights. A flag left enabled. A permission not restricted. A default setting unchanged. These mistakes are easy to make. They also go unnoticed.


Majid Hajian Azure & AI advocate@Microsoft Dart & Flutter community leader Organizer@FlutterVikings, Author of http://flutterengineering.io
This hits Flutter devs hard. We ship multiple configuration layers at once. Dart code, Android’s AndroidManifest.xml, iOS’s Info.plist, Gradle files, Xcode settings, and more. Each layer adds its own failure modes. A review that only covers Dart misses a big chunk of the attack surface.
I’ve reviewed many Flutter projects. Security misconfigurations are among the most common issues I see. The good news is simple. They’re also among the easiest to fix.
Let’s break down what security misconfiguration means for Flutter apps. Let’s harden your configs.
Source code: All examples from this article are available as a runnable Flutter project on GitHub:
Understanding Security Misconfiguration
What is Security Misconfiguration?
Security misconfiguration occurs when security settings are defined, implemented, or maintained incorrectly. Think of it like leaving your front door unlocked. It’s not a flaw in the lock itself. It’s how you’re using (or not using) the security mechanism.
In mobile apps, security misconfiguration takes many forms:
Insecure default settings left unchanged
Debug features enabled in production
Unnecessary permissions requested
Exposed components (activities, services, content providers)
Weak network security configurations
Improper backup settings that leak data
Missing security headers or policies
For the official definition and attack scenarios, see the OWASP M8 page.
The following diagram illustrates the various categories of security misconfiguration:

Why Flutter Developers Must Pay Extra Attention
Here's a reality check for Flutter developers: you're responsible for configuring two completely different platforms correctly, plus managing Dart-level configurations. A single misconfiguration on either Android or iOS can compromise your entire app, regardless of how secure your Dart code is.
Let me break down what you’re actually managing:
App manifest
AndroidManifest.xml
Info.plist
N/A
Build config
build.gradle
Xcode build settings
pubspec.yaml
Network security
network_security_config.xml
ATS in Info.plist
HTTP client config
Permissions
<uses-permission>
NS…UsageDescription
permission_handler
Signing
Keystore + Gradle
Provisioning profiles
N/A
That's a lot of surface area to secure. And here's the thing: most Flutter tutorials and documentation focus on the Dart layer. The platform-specific security configurations are often an afterthought, or worse, copy-pasted from StackOverflow without understanding what they do.
Business and Technical Impact
What can actually go wrong with security misconfigurations? According to OWASP, the consequences include:
Unauthorized data access: Attackers exploit exposed components and read user data.
Account hijacking: Weak session or auth configs let attackers take over accounts.
Data breaches: Backups extracted from unprotected storage end up leaked or sold.
Reputation damage: Public disclosure erodes user trust.
So let's get into the specific misconfigurations to watch out for.
Android Security Misconfigurations in Flutter
Android configuration is particularly prone to security issues because of its flexibility and the sheer number of settings available. Let's walk through the most critical misconfigurations I encounter when reviewing Flutter apps.
1. Debug Mode in Production
This one is the classic "oops" moment. Releasing an app with android:debuggable="true" is like shipping a car with the hood permanently unlatched. Anyone can look inside.
What it looks like:
Why this is catastrophic:
When debug mode is enabled, attackers can:
Attach debuggers to your running app and step through code
Inspect and modify memory contents at runtime
Set breakpoints to bypass security checks
Extract sensitive data directly from memory
I've seen production apps with this flag enabled—it happens more often than you'd think, especially when developers manually override build configurations for testing and forget to revert.
The good news: Flutter's build system handles this correctly by default. The debuggable flag is set to true only in debug builds. However, you should still verify your release builds, especially if you've ever modified build configurations manually:
Expected output for a correctly configured release build:
If you see value='true' or debuggable="true", stop and fix your build configuration before shipping.
Explicitly ensure debug is disabled in release builds:
For extra safety, explicitly set the flag in your Gradle build configuration. Belt and suspenders approach:
2. Backup Configuration Leaks
Here's a subtle one that catches many developers off guard. By default, Android backs up your app data to Google Drive. Sounds helpful, right? The problem is that this backup might include sensitive information: authentication tokens, cached user data, and encryption keys stored in SharedPreferences.
The problem in action:
How attackers exploit this:
The following sequence shows how backup extraction works in practice:

An attacker with brief physical access to a device (think: borrowed phone, lost device, or malicious app with ADB access) can extract your entire app's data:
Output:
Nuance on flutter_secure_storage: If you configure it with AndroidOptions(encryptedSharedPreferences: true), the encryption key lives in the Android Keystore and is not included in backups. The extracted XML will contain ciphertext that is useless without the key.
However, when using the default configuration (or older package versions < 5.0), the key material may be stored alongside the data. That makes backup extraction a real threat.
Always enable encryptedSharedPreferences: true on Android.
The fix:
Explicitly disable backups. Or, if you need selective backup for user convenience, carefully exclude sensitive files. See the <application> element docs for the full attribute reference:
For Android 12+ (API 31+), create android/app/src/main/res/xml/data_extraction_rules.xml:
For older Android versions, create android/app/src/main/res/xml/backup_rules.xml:
And reference it:
3. Cleartext Traffic (HTTP)
In 2026, there's really no excuse for allowing unencrypted HTTP traffic. Yet I still see Flutter apps with this misconfiguration regularly, usually because developers enabled it "temporarily" for local testing and forgot to disable it.
The problematic setting:
When this flag is enabled, your app can communicate over unencrypted HTTP. Any attacker on the same network (coffee shop WiFi, compromised router, or hostile network) can see everything your app sends and receives: authentication tokens, personal data, everything.
The fix:
Create a proper network security configuration. This approach gives you fine-grained control and makes your intent explicit. Create android/app/src/main/res/xml/network_security_config.xml:
Reference it in your manifest:
4. Certificate Pinning
If your app communicates with sensitive backend services (especially for financial or health data), consider adding certificate pinning. This prevents attackers from intercepting traffic even if they manage to install a rogue CA (Certificate Authority) certificate on the device.
Here's how to add certificate pinning in your network security config:
Generate pin hash from your server's certificate:
Output:
Use this Base64 string as the <pin> value in your config.
Important: Always include at least two pins (a backup), and set an expiration date. If your certificate rotates and you only have one pin, your app will stop working.
5. Exported Components
This is one of the most frequently overlooked security issues in Android development. Components marked as exported="true" can be accessed by any other app on the device. This includes activities, services, broadcast receivers, and content providers.
Since Android 12 (API 31), the android:exported attribute is required for every component that declares an <intent-filter>. Builds targeting
API 31+ will fail without it.
The problem:
Why this matters for Flutter:
Flutter's main activity is often exported for deep linking. While this is necessary for handling deep links, you need to be extremely careful about validating any data that comes through:
The Fix:
Set
exported="false"for internal componentsAdd permission requirements for exported components
Validate all inputs from intent data
Validate deep link data in Flutter:
The following flow shows how every incoming deep link should be validated before the app acts on it:

Example output when processing deep links:
6. Excessive Permissions
This is one of those areas where I see a lot of "it works, ship it" mentality. Requesting unnecessary permissions increases your app's attack surface and violates the principle of least privilege. Every permission you request is a potential vector for abuse—by malicious code in your dependencies, by attackers who compromise your app, or by you accidentally leaking data you shouldn't have access to.
I've reviewed apps that request camera, contacts, location, SMS, microphone, and storage access... for a simple note-taking app. Each of those permissions opens a door. The more doors you open, the more you have to defend.
The problematic approach:
The better approach:
Only request what you actually need, and request it at runtime with proper explanation. Users have become increasingly permission-aware, they'll uninstall your app if you ask for things that don't make sense.
Request permissions properly in Flutter:
Console output (example flow):
iOS Security Misconfigurations in Flutter
Now let's shift our attention to iOS. While iOS has a reputation for being more secure than Android (and in some ways it is), it's not immune to misconfiguration issues. In fact, some iOS-specific security features are so easy to disable that developers do it without realizing the implications.
1. App Transport Security (ATS)
App Transport Security is Apple's way of enforcing secure network connections. It requires HTTPS by default, with modern TLS versions and cipher suites. It's a fantastic security feature that protects your users from man-in-the-middle attacks.
And yet, one of the most common Stack Overflow answers to "my app can't connect to my server" is "just disable ATS." This is terrible advice that has made its way into countless production apps.
The problematic configuration:
When you add this to your Info.plist, you're telling iOS "I don't care about network security, let me talk to any server over any protocol." Your app can then be tricked into communicating with malicious servers, or have its traffic intercepted on insecure networks.
The secure approach:
If you absolutely must connect to a server that doesn't support HTTPS (and please, pressure them to fix this), use targeted exceptions rather than disabling ATS entirely:
Best practice: Don't disable ATS at all. If you're hitting HTTP endpoints, the right fix is to update those servers to use HTTPS, not to weaken your app's security. Let's Encrypt makes free certificates available to everyone—there's no excuse for HTTP anymore.
2. Missing Privacy Usage Descriptions
iOS requires you to explain why your app needs access to sensitive features—camera, location, contacts, etc. This isn't just a formality; it's a privacy protection that helps users make informed decisions about what they're allowing.
Here's the fun part: if you request a permission without providing the corresponding usage description, your app doesn't show a generic dialog. It crashes. Immediately. And this will definitely get caught in App Store review if you somehow missed it in testing.
What happens without descriptions:
No graceful error handling, no fallback—just a crash. So make sure you add all necessary descriptions to ios/Runner/Info.plist:
3. Insecure Keychain Configuration
The iOS Keychain is one of the most secure places to store sensitive data on the device, but only if you configure it correctly. The Keychain has different accessibility levels that determine when your data can be accessed, and choosing the wrong one can leave your users' data exposed.
The problematic approach (in native code):
Using kSecAttrAccessibleAlways means the data can be read even when the device is locked. If someone steals the phone, they can potentially extract this data without knowing the passcode.
The secure approach in Flutter (using flutter_secure_storage):
The good news is that flutter_secure_storage gives you control over Keychain accessibility. Here's how to configure it properly:
Output:
Understanding Keychain Accessibility Options:
Choosing the right accessibility level requires balancing security with user experience. Here’s a quick breakdown:
passcode
Only when unlocked with passcode
No
unlocked
When device is unlocked
Yes
unlocked_this_device
When device is unlocked
No
first_unlock
After first unlock until restart
Yes
first_unlock_this_device
After first unlock until restart
No
always
Always (deprecated since iOS 12)
Yes
always_this_device
Always (deprecated since iOS 12)
No
kSecAttrAccessibleAlways and kSecAttrAccessibleAlwaysThisDeviceOnly have been deprecated since iOS 12. Any code still targeting these values will trigger App Store review warnings.
Migrate to
first_unlock_this_deviceorunlocked_this_device.
For most sensitive data like auth tokens, I recommendfirst_unlock_this_device. This provides strong protection while still allowing background operations (like push notification handling) to access the data after the user has unlocked their device at least once since reboot.
4. URL Scheme Hijacking
Custom URL schemes are a convenient way to open your app from links, but they come with a significant security risk: any app can register the same URL scheme.If a malicious app registers your scheme first (or the user installs a malicious app that does), it can intercept links meant for your app.
The problematic approach:
There's nothing stopping a bad actor from creating an app that also registers the myapp:// scheme. When a user taps a link, iOS might open their malicious app instead of yours, and they could capture OAuth callbacks, payment confirmations, or other sensitive deep link data.
The secure solution:Universal Links
Instead of custom URL schemes, use Universal Links. These are tied to a domain you own and cryptographically verified by Apple. No other app can intercept them.
You'll also need to host an apple-app-site-association file at your domain's root (served over HTTPS, no redirect):
This file must be served athttps://yourapp.com/.well-known/apple-app-site-association. Apple will fetch and verify this file when your app is installed, ensuring only your app can handle links to your domain.
Flutter/Dart Level Misconfigurations
Beyond Android and iOS platform configurations, there are security considerations in your Dart code as well. Let's look at some common issues.
1. Debug Mode Detection Bypass
Your app should behave differently in debug versus release mode. Sensitive endpoints, verbose logging, and development shortcuts should never be available in production builds. Here's a pattern I use to manage this:
Output (debug build):
Output (release build):
2. Insecure HTTP Client Configuration
One of the most dangerous patterns I see in Flutter codebases is disabling certificate validation "to make development easier." The problem is, this often gets left in production code, or worse, it's deliberately added to bypass certificate errors without understanding why those errors exist.
Output:
Why badCertificateCallback and not a plugin? Dart's HttpClient gives you direct access to the TLS handshake. For package:http or Dio users, you can pass a custom SecurityContext or HttpClient to get the same effect.The trade-off: native-level pinning (e.g., network_security_config.xml) fires before Dart code runs, so it blocks non-Dart traffic too.
If you're seeing certificate errors, the solution is to fix the server's certificate configuration, not to tell your app to ignore security errors.
3. Insecure SharedPreferences Usage
SharedPreferences is incredibly convenient for storing small bits of data, but it's not encrypted. On a rooted or jailbroken device, anyone can read its contents. I've seen apps store auth tokens, API keys, and even passwords inSharedPreferences. Don't be one of those apps.
Output:
The rule is simple: if you wouldn't want it on the front page of a newspaper,don't put it in SharedPreferences.
4. Logging Sensitive Information
Logging is essential for debugging, but it's also a common source of security leaks. I've seen production apps that logged full user credentials, API keys, and payment information—all visible to anyone with access to device logs.
Debug-mode output:
Even in debug mode, be thoughtful about what you log. Screenshots and screen recordings can capture log output, and you never know where those might end up.
Security Configuration Audit Checklist
Here's a checklist you can use to audit your Flutter app's security configuration. I recommend going through this checklist before every major release, it's saved me from shipping vulnerable code more than once.
Android Configuration
AndroidManifest.xml
android:debuggableis NOT set to true (or is absent)android:allowBackup="false"or properly configured backup rulesandroid:usesCleartextTraffic="false"android:networkSecurityConfigpoints to secure configNo unnecessary
exported="true"on componentsExported components have permission requirements
Only necessary permissions are declared
android:hasFragileUserData="false"for sensitive apps (prevents data retention on uninstall)
build.gradle
minifyEnabled truefor release buildsshrinkResources truefor release buildsRelease signing configured properly
debuggable falseexplicitly set for release
network_security_config.xml
cleartextTrafficPermitted="false"as defaultCertificate pinning for sensitive endpoints
No overly broad exceptions
debug-overridesonly for debugging
iOS Configuration
Info.plist
No
NSAllowsArbitraryLoadsset to trueSpecific domain exceptions only if necessary
All privacy usage descriptions present and accurate
URL schemes are documented and necessary
Entitlements
Only necessary entitlements enabled
Associated Domains configured for Universal Links
Keychain sharing groups are appropriate
Xcode Settings
Code signing configured properly
Debug builds don't use release certificates
Bitcode settings appropriate for your needs
Dart/Flutter Configuration
Code configuration
No hardcoded secrets or API keys
Proper environment configuration
Secure storage used for sensitive data
HTTP client validates certificates
Deep links are validated before processing
Logging doesn't include PII in release
Debug features disabled in release mode
Dependencies
All packages are from trusted sources
No known vulnerabilities in dependencies
Minimum necessary permissions for plugins
Automated Security Scanning
Manual checklists are great, but humans make mistakes, especially when we're rushing to meet deadlines. That's why I strongly recommend implementing automated security checks in your CI/CD pipeline. Here's a GitHub Actions workflow that catches common misconfigurations before they reach production:
Note: you may adjust the following config a bit to match your project setup!
This workflow won't catch everything, but it will catch the most common and dangerous misconfigurations. I've seen this simple check prevent several "oops, I shipped with debug mode enabled" incidents.
Runtime Configuration Validation
For an extra layer of protection, you can add runtime checks that validate your app's configuration at startup. This is particularly useful for catching issues that might slip through static analysis:
Output (debug, non-production package):
Output (release, production package accidentally in debug):
Conclusion
Security misconfiguration is one of the most common vulnerabilities in mobile apps, and honestly, one of the most frustrating because it's so preventable.Unlike complex vulnerabilities that require deep security expertise to understand, misconfigurations are simply settings that someone forgot to change or didn't realize were insecure.
As Flutter developers, we have the added responsibility of managing configurations across multiple platforms simultaneously. Every time you configure Android's AndroidManifest.xml, you need to remember to check iOS'sInfo.plist too. Every permission you add to Gradle needs a corresponding entry in Xcode. It's a lot to keep track of.
Here's what I want you to take away from this article:
Disable debug features in production builds. Verify before release.
Disable or restrict backups so attackers can’t extract user data.
Enforce HTTPS everywhere. Add pinning for high-security cases.
Minimize permissions to what you need. Users notice. Attackers too.
Protect exported components with permissions. Validate deep link inputs.
Automate security checks in CI/CD. Don’t rely on manual review.
Audit regularly. Configurations drift as features ship.
The key insight is that a single misconfiguration can undermine all your other security efforts. You can implement perfect encryption, bulletproof authentication, and thorough input validation—but if you leave android:debuggable="true" in your release build, attackers can bypass it all.
Make configuration review a standard part of your development process. Add it toyour pull request checklist. Run automated scans on every commit. It's boringwork, but it's the kind of boring work that prevents headlines.
In the next article, we'll explore M9: Insecure Data Storage, where we'll dive deep into how to properly store sensitive data on mobile devices. Spoiler alert: it's more nuanced than just "use encrypted storage."
Additional Resources
Stay secure, Flutter engineers!
Last updated
Was this helpful?

