Flutter App Security Checklist
Phase 0: Governance, environment, and CI/CD
Tooling and versions
- Keep Flutter, Dart SDK, Android Studio/Xcode, and all plugins/dependencies up to date.
- Review security advisories for Flutter packages and native SDKs before each release.
Enforce CI gates
- On every PR, run:
-
dart analyze -
flutter test --coverage - Android/Gradle lint
- iOS build checks
- Dependency audits
- Fail builds on analyzer errors and any warnings configured as errors.
- Enforce zero lint errors on release branches.
Establish security gates
- Run mobile SAST/SCA/DAST scans on every PR and before releases.
- Block on High/Critical findings.
- Integrate scans directly in CI/CD so that High/Critical findings fail the pipeline.
Secrets handling
- Ensure no secrets in:
- Dart source
- Native code
-
AndroidManifest.xml -
Info.plist - Assets
- Logs
- Use environment variables, CI secret stores, and/or secret managers.
- Redact secrets in analytics and logging.
- Rotate API keys/tokens regularly and whenever leakage is suspected.
Evidence and traceability
- Produce and archive for every artifact:
- Scan reports
- SBOMs
- Build logs
- Test reports
- Release notes
Phase 1: Threat modeling, data classification, and inventory
- Classify data types (PII, credentials, tokens, payment data, health data).
- Document where each type is collected, processed, stored, and transmitted in:
- Flutter app code
- Native layers
- Identify trust boundaries:
- Flutter app code
- Native platform channels (Android/iOS)
- Device storage
- WebView/browser
- Backend APIs
- Third‑party services
- Inventory all Dart packages, plugins, and native SDKs.
- Prefer well‑maintained, signed, least‑privilege dependencies.
- Minimize total dependency count and scope.
- Define security acceptance criteria per risk class (e.g., “No sensitive data at rest in plaintext”, “All auth traffic pinned”, “No debug logs in release”).
Phase 2: Build and configuration hardening (Flutter + Android + iOS)
Flutter release builds
- Build with obfuscation and split debug symbols for Android:
-
flutter build appbundle --release --obfuscate --split-debug-info=build/symbols - Build with obfuscation and split debug symbols for iOS:
-
flutter build ipa --release --obfuscate --split-debug-info=build/symbols - Store
build/symbolssecurely server‑side. - Never ship symbols or mapping files in the app bundle.
Logging hygiene
- Guard logs behind
kReleaseModeor equivalent. - Remove
print/debugPrint/ verbose logging from production code paths. - Add CI checks to fail builds when debug logging appears in non‑test code on release branches.
Android (Gradle + Manifest)
- Enable R8/ProGuard and resource shrinking for release:
-
minifyEnabled true -
shrinkResources true - Use optimized ProGuard rules.
- Harden
AndroidManifest.xml: -
android:debuggable="false"in release. -
android:allowBackup="false"unless you have a secure backup design. -
android:usesCleartextTraffic="false"; no HTTP fallback in production. - Set explicit
android:exportedon components; avoid exporting without strong permission guards. - Configure Network Security Config:
- Disable cleartext globally.
- Define domain configs.
- If pinning is used, define pin sets with backup pins.
- Signing and integrity:
- Use Play App Signing where applicable.
- Protect keystores.
- Enable Play Integrity API and integrate signals with backend risk decisions.
iOS (Xcode + Info.plist within Flutter project)
- App Transport Security (ATS):
-
NSAllowsArbitraryLoads = false. - Use strict
NSExceptionDomainsonly for necessary, time‑boxed exceptions. - Release build settings:
- Strip symbols.
- Enable dead code stripping.
- Enable whole‑module optimization.
- Ensure no
DEBUGflags or test libraries are present in release builds. - Keychain defaults for native/plugin storage:
- Prefer
ThisDeviceOnlyclasses where possible. - Mask snapshots on background (via native or Flutter plugins) so app switcher never shows sensitive data.
Phase 3: Network security and TLS/pinning
HTTPS/TLS enforcement
- Ensure all API traffic uses HTTPS/TLS 1.2+.
- Reject invalid certificates, self‑signed certificates (if not explicitly trusted), and hostname‑mismatched endpoints.
- Configure reasonable timeouts.
- Implement retry with jitter where appropriate.
- Always fail closed on TLS errors (no downgrade to HTTP).
Certificate pinning
- For critical domains, implement certificate or SPKI SHA‑256 pinning in:
- The HTTP client used by Flutter.
- Any native networking used by plugins.
- Maintain at least one backup pin per host.
- Design and test pin rotation scenarios.
- Validate pinning behavior under:
- MITM attempts.
- Expired certificates.
- Untrusted CAs.
- Log pin failures and surface telemetry.
Server‑side security
- Enforce HSTS on backend endpoints.
- Use strong cipher suites and modern protocol versions.
- Use secure, HttpOnly, SameSite cookies for web endpoints accessed via WebView or in‑app browsers.
- Avoid long‑lived bearer tokens on the client.
Phase 4: Local storage and secret handling
Secrets and tokens
- Never store secrets/tokens in:
-
SharedPreferences -
plistfiles - Unencrypted SQLite
- Plaintext files from Dart
- Do not embed secrets in:
-
constvalues - Assets
- Generated code
Secure storage
- Use secure storage bound to platform keystores for tokens and keys (e.g., Android Keystore, iOS Keychain via Flutter plugins).
- Where UX allows, gate decryption/usage of high‑risk keys with biometrics or OS passcode.
- Prefer device‑bound keys (
ThisDeviceOnly/ hardware‑backed) for high‑value tokens.
Encrypted storage (files/DB)
- Use encrypted SQLite or equivalent file encryption for sensitive records.
- Ensure caching layers (e.g., HTTP cache, local persistence) do not store PII or secrets in plaintext.
Data lifecycle
- Clear:
- Caches
- Secure storage entries
- In‑memory sensitive objects
on logout and account removal. - Wipe WebView or in‑app browser data after auth flows or high‑risk sessions.
- Configure Android/iOS backup rules to prevent backups of sensitive app data in unencrypted form.
Phase 5: Privacy safeguards (UI, clipboard, screenshots)
- Mask sensitive Flutter screens when the app is backgrounded or in task switcher:
-
FLAG_SECUREon Android. - Snapshot masking on iOS.
- For extremely sensitive views (e.g., full card numbers, secrets), use a dedicated “secure screen” pattern with:
- Extra masking.
- No screenshots/screen recording support.
- Avoid reading from the clipboard on app launch.
- Never place passwords, tokens, or reset links onto the clipboard.
- Ensure in‑app screenshots or exports omit secrets/PII unless explicitly required and clearly communicated to the user.
Phase 6: Authentication, authorization, and session
Auth flows
- Use OAuth2/OIDC with PKCE for public Flutter clients.
- Do not embed client secrets in the app.
- Prefer system browser / platform auth flows:
- Chrome Custom Tabs on Android.
-
SFSafariViewController/ASWebAuthenticationSessionon iOS via plugins. - Avoid in‑WebView login where possible.
Tokens
- Use short‑lived access tokens.
- Store refresh tokens only in secure storage bound to platform keystores.
- Rotate tokens regularly.
- Invalidate tokens on:
- Logout.
- Device change.
- Suspicious patterns.
- Consider device binding and risk‑based checks for highly sensitive actions, using device signals from Android and iOS.
Authorization
- Enforce access control on the server.
- Do not rely on Dart/UI logic to protect resources.
- Test for:
- IDOR/BOLA.
- Mass assignment.
- Vertical and horizontal privilege escalation
across all API endpoints used by the Flutter app.
Phase 7: WebView hardening and browser‑based flows
Preferred pattern
- Use system browser / platform‑provided secure web views for authentication and payments where possible:
- Custom Tabs.
-
SFSafariViewController.
If WebView is required (Flutter WebView or native)
- Disable JavaScript by default.
- Enable JavaScript only if explicitly needed and then limit capabilities.
- Restrict navigation to an allowlist of trusted hosts and schemes.
- Block
file://,javascript:, and other dangerous URLs. - Disable web content debugging in release builds on Android and iOS.
- Clear WebView cookies, cache, and storage after sensitive sessions (auth, payments, PII editing).
- Ensure first‑party web content:
- Enforces CSP.
- Uses HTTPS only.
- Has no mixed content.
Phase 8: Inter‑app communication, deep links, and platform channels
Deep links and inter‑app flows
- Prefer Android App Links and iOS Universal Links for deep linking.
- Validate link origins before acting.
- Validate and sanitize link parameters before acting.
- Avoid unsafe custom URL schemes.
- If custom schemes are used:
- Choose unique schemes.
- Robustly validate all parameters and redirects.
Platform channels (MethodChannel / EventChannel)
- Treat all data coming from Flutter into native and back as untrusted until validated.
- Validate method names and parameters on the native side.
- Avoid exposing powerful native APIs directly over channels.
- Never pass raw untrusted data from Flutter directly into privileged native calls (file system, system settings, telecom, etc.) without validation and sanitization.
- Fuzz platform channel inputs during testing.
- Log and safely handle unexpected method calls or malformed data.
Phase 9: Reverse engineering and tamper resilience
Reduce static leakage
- Obfuscate Dart code.
- Enable R8/ProGuard/resource shrinking for Android.
- Strip symbols and debug info from shipped binaries on both platforms.
- Remove verbose logs, debug menus, test endpoints, hidden feature flags, and backdoors in release builds.
- Avoid embedding secrets or sensitive business logic in easily extractable form (e.g., plain strings in assets).
Environment checks
- Add root/jailbreak/hooking/debugger detection on both Android and iOS (via plugins or native code).
- Log detected signals for security monitoring.
- Respond proportionally:
- Degrade features.
- Add extra verification.
- Block high‑risk operations
instead of silently continuing as normal.
Tamper testing and integrity
- Validate that repackaged or re‑signed Flutter apps are detected via integrity checks or server‑side attestation:
- Play Integrity (Android).
- Device checks on iOS.
- Treat client‑side tamper checks as signals to the backend.
- Never use client‑side tamper checks as the only security control.
Phase 10: Cryptography and randomness
- Use established, platform‑approved crypto libraries (e.g., plugins wrapping Android Keystore/iOS Keychain or well‑vetted crypto libraries).
- Do not implement custom cryptographic algorithms in Dart.
- For encryption, use modern AEAD modes:
- AES‑GCM.
- ChaCha20‑Poly1305.
- For KDFs, use:
- PBKDF2.
- HKDF
with appropriate parameters and per‑value salts. - Generate keys, IVs, and nonces using secure randomness (SecureRandom or platform RNGs via plugins).
- Never reuse IVs/nonces.
- Keep cryptographic keys in Keystore/Keychain‑protected storage or encrypted at rest, not in memory or config for longer than needed.
Phase 11: Permissions, notifications, and identifiers
Permissions
- Request the minimum necessary permissions.
- Request permissions only at point‑of‑use.
- Provide clear UX explaining why each permission is needed.
- Implement graceful denial and revocation paths (limited functionality instead of crashes or infinite prompts).
Privacy and identifiers
- Use advertising/analytics identifiers only according to platform policies (e.g., GAID/IDFA) with appropriate consent flows.
- Avoid building cross‑app persistent fingerprinting with:
- Hardware IDs.
- MAC addresses.
- Other prohibited identifiers.
Notifications
- Minimize sensitive content in push/local notifications.
- Use generic titles/descriptions for notifications shown on lock screen.
- Allow users to configure notification categories and sensitivity.
- Never include secrets or full PII in notification payloads.
Phase 12: Logging, analytics, and crash reporting
- Standardize on structured logging.
- Disable verbose/debug logging in release builds.
- Redact PII and secrets from:
- Logs
- Analytics events
- Crash reports
- Prefer aggregation and sampling for analytics.
- Store symbol/mapping files (Flutter split debug info, R8 mappings, dSYMs) securely server‑side for crash de‑obfuscation.
- Never ship symbol/mapping files in the app bundle.
- Periodically review analytics and logs for unexpected data (sensitive fields, query parameters, headers, payloads).
Phase 13: Testing, DAST, and runtime verification
Traffic interception tests
- Verify TLS validation rejects:
- Invalid certificates.
- Expired certificates.
- Self‑signed certificates (unless explicitly trusted).
- Hostname mismatches.
- Confirm that certificate pinning (where enabled) blocks MITM interception tools.
Runtime inspection
- During testing, inspect:
- App storage (
SharedPreferences, files, DBs). - WebView storage.
- Logs
for secrets/PII. - Confirm secure storage and encryption are correctly applied.
- Confirm cleanup on logout/account removal is effective.
API abuse tests
- Validate:
- Rate limits.
- Replay protections.
- Pagination boundaries.
- Input validation
for all APIs used by the Flutter app. - Check that server error messages are not overly verbose and do not leak implementation details.
Dynamic analysis and DAST
- Run mobile DAST tools while exercising core Flutter flows (auth, payments, profile, settings).
- Use DAST to surface:
- Transport issues.
- Storage issues.
- WebView issues.
- Export evidence for triage.
- Fix findings and re‑test before promoting builds.
Phase 14: SBOM, and evidence
SBOM and dependencies
- Generate and archive SBOMs for:
- Flutter (
pubspec.lock). - Native modules (Gradle/CocoaPods).
- Track CVEs and advisories for your package set over time.
- Schedule dependency refreshes as part of release readiness.
Evidence bundle
- Attach to every release record:
- SAST/SCA/DAST reports.
- SBOMs.
- Build configs (obfuscation, pinning, ATS/networkSecurityConfig).
- Test reports (automated and manual).
Phase 15: Reporting and standards alignment
Reporting
- Produce for Flutter apps:
- Executive summary.
- Scope and methodology.
- Detailed findings with PoCs and screenshots.
- Risk ratings.
- Remediation plans.
- Re‑test outcomes.
OWASP MASVS mapping
- PLATFORM: Phases 2, 7–8, 11.
- STORAGE: Phases 3–4.
- CRYPTO: Phase 10.
- AUTH: Phase 6.
- NETWORK: Phase 5.
- CODE: Phases 2, 8, 12.
- RESILIENCE: Phase 9.
Practitioner quick‑reference
- Builds:
--obfuscate --split-debug-info, R8minifyEnabled true,shrinkResources true; no debug symbols in shipped artifacts. - Network: HTTPS only; certificate/SPKI pinning (with backups) for critical APIs; verify MITM fails.
- Storage: Secure storage for tokens; encrypted DB/files for sensitive data; wipe on logout; prevent sensitive backups.
- Auth: OIDC + PKCE; short‑lived tokens; refresh tokens in secure storage; server‑side authorization; test IDOR/BOLA.
- WebView: Prefer system browser for auth; if WebView is used, JS off by default, navigation allowlist, clear data after sensitive flows.
- Privacy: Mask background snapshots via platform flags; avoid clipboard secrets; minimal permissions with clear UX.
- CI gates: Block on High/Critical SAST/SCA; require “no High” DAST; zero analyzer/lint errors for release.