Skip to content

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/symbols securely server‑side.
  • Never ship symbols or mapping files in the app bundle.

Logging hygiene

  • Guard logs behind kReleaseMode or 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:exported on 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 NSExceptionDomains only for necessary, time‑boxed exceptions.
  • Release build settings:
  • Strip symbols.
  • Enable dead code stripping.
  • Enable whole‑module optimization.
  • Ensure no DEBUG flags or test libraries are present in release builds.
  • Keychain defaults for native/plugin storage:
  • Prefer ThisDeviceOnly classes 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
  • plist files
  • Unencrypted SQLite
  • Plaintext files from Dart
  • Do not embed secrets in:
  • const values
  • 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_SECURE on 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 / ASWebAuthenticationSession on 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.

  • 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, R8 minifyEnabled 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.