SSentinel Board
GuidesPricingOpen dashboard ↗

FLUTTER SECURITY

How to test a Flutter app for security vulnerabilities

Obsidian Orb InnovationsUpdated 9 September 2026~8 min read

Flutter gives you one codebase for iOS and Android — and one set of security mistakes shipped to both. Most of what goes wrong in a Flutter release is not exotic: a token in the wrong storage bucket, an unobfuscated release build, a backend endpoint that never checks who is asking. This guide walks the checks in the order that finds the most problems fastest.

Everything below assumes you are testing your own app or one you have explicit written permission to test. Intercepting traffic or decompiling binaries you do not own is illegal in most jurisdictions.
WHAT THIS COVERS
  1. Where your app stores secrets
  2. Traffic interception and certificate pinning
  3. Release build hardening
  4. What a decompiler can read
  5. The part people skip: your backend API
  6. Platform-specific settings

1. Start where the secrets live

The single most common Flutter finding is sensitive data in SharedPreferences. It is convenient, it is the first thing most tutorials reach for, and it is unencrypted plain text on both platforms. Anyone with filesystem access to the device — including any app running as the same user on a rooted or jailbroken phone — can read it.

What to check

  • Auth tokens, refresh tokens, and session identifiers use flutter_secure_storage (which maps to the iOS Keychain and Android Keystore), not SharedPreferences.
  • No credentials or API keys are hard-coded as Dart string constants.
  • Logout actually clears stored tokens — not just the in-memory copy.
  • If you use a local SQLite database for anything personal, it is encrypted (sqflite_sqlcipher) rather than a plain .db file.

How to verify it rather than assume it

Read the app's own data directory on a debug build and look for your own token. On Android:

adb shell run-as com.example.yourapp \
  ls -R /data/data/com.example.yourapp

# Then dump anything promising and grep for a token you know:
adb shell run-as com.example.yourapp \
  cat /data/data/com.example.yourapp/shared_prefs/FlutterSharedPreferences.xml

If you can see your own session token in that output, so can an attacker with device access. That is the finding — write it down and move on, there will be others.

2. Put a proxy in the middle

Next, watch what the app actually sends. Route the device through an intercepting proxy (Burp Suite, mitmproxy, or Charles) with its CA certificate installed, then exercise every screen that talks to the network — login, password reset, payments, file upload, profile edit.

What you are looking for

  • Every request is HTTPS. A single http:// call is a finding, even to a "harmless" analytics or config endpoint.
  • No passwords, tokens, or personal data appear in URL query strings — those land in server logs, proxy logs, and browser history.
  • Error responses do not return stack traces, SQL fragments, or internal hostnames.
  • Tokens are sent in an Authorization header, not a custom header or a cookie without the Secure and HttpOnly flags.

Then test the opposite

Here is the check most teams get backwards. If your proxy can read the traffic with its CA installed, that is expected. The real question is whether certificate pinning stops it. Remove the proxy CA from the device's trust store and try again: a properly pinned app should fail to connect. If requests still succeed, you have no effective pinning, and anyone who can get a certificate onto the device can read everything.

Pinning is a trade-off, not a free win. Pinned certificates expire, and an app that pins a certificate you later rotate will break in the field for every user who has not updated. Pin to an intermediate CA or include a backup pin, and make sure you have a rollout plan.

3. Check what your release build is actually doing

Debug builds leak by design. The question is whether your release pipeline strips that back out. Build the way you ship, then verify:

flutter build apk --release \
  --obfuscate --split-debug-info=build/symbols

flutter build ipa --release \
  --obfuscate --split-debug-info=build/symbols

The --obfuscate flag is not on by default, and a surprising number of shipped Flutter apps do not use it. Keep the --split-debug-info output somewhere safe — you need those symbol files to read production crash reports.

  • --obfuscate is part of your actual release command, not just documentation.
  • debuggable is false in the production Android manifest.
  • Debug print() calls that log tokens, request bodies, or PII are gone.
  • No test or staging URLs remain reachable in the production binary.

4. Read your own binary

Unzip the APK and run strings over the Flutter library. You are not doing a deep reverse-engineering exercise — you are checking whether your secrets are sitting in plain sight:

unzip -o app-release.apk -d apk-out
strings apk-out/lib/arm64-v8a/libapp.so | \
  grep -iE 'api[_-]?key|secret|password|bearer'

Any API key you find here is compromised the moment you publish. This matters more in Flutter than people expect: a key compiled into Dart feels "inside the app", but an APK is just a zip file that anyone can download from the store and open.

If you find one, the fix is architectural, not cosmetic — obfuscation will not save a key that has to be sent to a server anyway. Move the privileged call behind your own backend and let the server hold the credential.

5. Test the backend, because that is where the real risk is

This is the step that gets skipped, and it is the one that matters most. Your Flutter app is a client. An attacker does not have to use it — they can read your API from the proxy logs in step 2 and then call it directly with curl, with no app, no UI, and none of your client-side validation.

So every rule you enforce in Dart has to be enforced again on the server. Two checks find most of the damage:

Broken access control

Log in as user A, capture a request that fetches your own data, then change the identifier to user B's and replay it with user A's token:

# Your own record — expected 200
curl -H "Authorization: Bearer $TOKEN_A" \
  https://api.example.com/v1/users/1001/profile

# Someone else's record — this MUST be 403, not 200
curl -H "Authorization: Bearer $TOKEN_A" \
  https://api.example.com/v1/users/1002/profile

A 200 on the second request is broken object-level authorization, and it is the most commonly exploited API flaw there is. Try the same trick on admin-only routes with a regular user's token.

Missing authentication

Replay the same requests with the Authorization header removed entirely. Endpoints that return data to an unauthenticated caller are usually an oversight in routing or middleware, and they are trivial to find — which means someone else will find them too.

6. Close out the platform settings

  • Android: the manifest requests only permissions you actually use, allowBackup is false if you store anything sensitive, and no components are exported unintentionally.
  • iOS: App Transport Security is not disabled with a blanket NSAllowsArbitraryLoads exception, and credentials go to the Keychain.
  • Both: run flutter pub outdated and update packages with known advisories. Your dependency tree is part of your attack surface.

A realistic order of operations

If you only have an afternoon: read your own storage (step 1), proxy your traffic (step 2), and test your backend for broken access control (step 5). Those three find the large majority of real, exploitable problems. Build hardening and binary inspection matter, but they raise the cost of an attack rather than closing an open door.

The harder part is not running the checks once — it is re-running them every release and keeping track of what you already fixed. That is the gap the toolkit below is built to fill.

SENTINEL BOARD

Automate the API half of this checklist

Sentinel Board runs the OWASP-aligned backend checks from step 5 as a single command — authentication, access control, injection, security headers, CORS, TLS, and information disclosure — then imports the results into a local dashboard so you can see severity, posture, and control coverage in one view. It ships with the full Flutter security checklist this guide is drawn from, plus reporting worksheets. One-time $49, no subscription, nothing sent to the cloud.

See what's included →
← All guides API security testing checklist Security headers explained
SSentinel Board
GuidesTermsPrivacyRefunds
© 2026 Obsidian Orb Innovations