OWASP Top 10 For Flutter - M5: Insecure Communication for Flutter and Dart
Last updated
Was this helpful?
Last updated
Was this helpful?
In this fifth installment of our OWASP Top 10 series for Flutter developers, we shift focus to M5: Insecure Communication.In earlier parts, we tackled , , , and , each playing a crucial role in app security. But M5 strikes at the core of how mobile apps operate: how data moves in and out of your Flutter app.
Picture this, you’re in a cozy café, laptop open, integrating a new feature. You join the free Wi‑Fi, hit Run, and data starts to flow. What you don’t see is someone on that same network quietly capturing every byte, login tokens, profile calls, even payment info, because it’s traveling like postcards through a crowded street. That’s the heart of insecure communication.
This article isn't just about fixing bugs, it's about understanding how data is exposed, how attackers think, and how you can prevent silent breaches before they start.Then, let's get started
It’s every moment when your data can be watched, intercepted, or altered while in transit.Insecure communication shows up any time the bytes leaving your Flutter app—or your Dart backend—can be read, replayed, or rewritten by someone who wasn’t supposed to see them. It’s not just about typing https://
in your URLs. It’s about every transport your app relies on and every trust decision your code makes along the way.
Remember that café scene? If your request flies in the clear, the stranger at the next table can read or even modify it. And even when we think we’re being careful, we sometimes stub out certificate checks during testing, accept sketchy proxies during debugging, or log sensitive payloads in places we shouldn’t. These shortcuts pull us right back into danger—even if we’re technically using HTTPS.
Now that we’ve seen what insecure communication looks like in practice, let’s break down the channels where your data flows. We’ll get to code shortly, but first, here’s where risks tend to hide.These are the lifelines of your app. If any of them are exposed, everything else downstream is vulnerable.REST / GraphQLAlways use https://
. Never put credentials or tokens in query strings—send them in headers or the body.
WebSocketsUse wss://
, never ws://
. Apply the same certificate validation and pinning logic as your REST layer.
If you’re using GraphQL subscriptions with
graphql_flutter
, confirm the client connects overwss://
and inherits your validation logic. No exceptions.
Raw TCP & gRPCEncryption is not optional. For raw sockets, wrap in SecureSocket
. For gRPC, use ChannelCredentials.secure()
and pin the server cert like you would for any HTTPS call.
SMS One-Time CodesAvoid them where possible, they’re vulnerable to SIM swaps and silent interception. If you must use them, expire them quickly and add detection for suspicious activity.Bluetooth & NFCPairing doesn’t mean encryption. Use BLE Secure Connections for Bluetooth, and encrypt NFC payloads end-to-end.
No matter which transport you choose, if your app sends data in the clear—or trusts the wrong certificate—you’re vulnerable. Here are the common actors waiting to take advantage:
Passive listeners on public or compromised networks
Active man-in-the-middle attackers who intercept or inject traffic
Rogue access points (evil-twin Wi-Fi, hijacked routers)
On-device malware or tools running on rooted/jailbroken phones
Enterprise or MDM-pushed root CAs that override your app’s trust settings
Even one misconfigured transport can expose your entire session.Next, we’ll dive into the most common and preventable mistake: letting http://
endpoints sneak into production.
The fastest way to lose user trust, and data, is to let a single http://
endpoint slip into production. Before we talk about certificate pinning or advanced validation, we need to eliminate the most basic mistake: allowing unencrypted traffic in the first place.
Using http://
is like taping your house key to the front gate—anyone walking by can grab it. Traffic moves in cleartext: tokens, cookies, form data, even search queries. All of it is readable to anyone on the same network, or logged by any proxy in the chain.And worse? A man-in-the-middle doesn’t even have to “break” encryption—because there isn’t any.The fix isn’t glamorous, but it’s non-negotiable:Encrypt every byte in transit and refuse to speak plaintext, ever.
If you’re running a Dart backend—whether with shelf
, HttpServer
, or another server framework—you need a TLS certificate to serve HTTPS. This allows your Flutter app to connect securely, validate the server’s identity, and lay the foundation for things like certificate pinning.Let’s look at two common paths: one for production deployments, and one for local development. You can also get certificates from a cloud provider or third-party CA—more on that below.
Option 1: Trusted Certificate (Let’s Encrypt or Third-Party)
For production, use a publicly trusted certificate from:
Your cloud provider (e.g., AWS ACM, Google Managed Certs, Azure App Service)
A third-party certificate authority like DigiCert, GlobalSign, or ZeroSSL
Here’s how to set one up using Let’s Encrypt and Certbot on Ubuntu:
To test auto-renewal:
Certificates will be saved to:
You’ll reference these in Dart using SecurityContext()
when starting your server.
💡 If you’re using a cloud platform (e.g., AWS, GCP, Azure), they may handle TLS termination for you. In that case, your Dart backend only sees HTTPS traffic forwarded as HTTP from the load balancer.
Option 2: Self-Signed Certificate (for Local Development)
For local testing and emulator development, a self-signed cert works fine. It’s not trusted by browsers or real devices—but that’s okay in dev. You’ll still benefit from full HTTPS support in your backend and Flutter app.To generate one using OpenSSL:
This creates two files:
localhost.crt
— the certificate
localhost.key
— the private key
Save these in your project directory or anywhere accessible by your Dart server.
Serve HTTPS in Dart with Shelf
Here’s how to use either cert type in a Dart backend with the shelf
package:
⚠️ If you're using a self-signed cert, your Flutter app may reject the connection unless you override validation during development (more on that in section 4).
Here is now how we run our application with https supported.
While this seems to be more backend related, but we can also make sure our Flutter is adhering to best practices.
Ensuring that your app refuses to connect to any HTTP endpoints is crucial. This prevents your app from accidentally leaking sensitive data over unencrypted channels. Let’s configure both Android and iOS to enforce HTTPS-only communication and block any insecure traffic.
Android gives you granular control over cleartext (non-HTTPS) traffic through Network Security Config. This is where you tell the app to only use HTTPS for production traffic, while allowing cleartext only for certain cases (like local development).
Create or modify the network_security_config.xml
file in your project:
Path: android/app/src/main/res/xml/network_security_config.xml
.
cleartextTrafficPermitted="false"
: This ensures that cleartext traffic (i.e., http://
) is blocked for all domains.
The domain
field specifies that only secure traffic (https://
) is allowed for api.yourdomain.com
and its subdomains.
Reference the network_security_config.xml
file in your AndroidManifest.xml
:
Path: android/app/src/main/AndroidManifest.xml
.
Inside the <application>
tag, add:
This step ensures that your app adheres to the defined network security rules.
Need to Allow http://10.0.2.2
for Local Development (Emulator)?For local development on Android, the emulator uses http://10.0.2.2
as the host for local services. To allow cleartext traffic for this during debugging, add a second domain-config
block under the <network-security-config>
:
let me give you a quick tip, wrap this configuration in a debug-only resource to ensure that it doesn't end up in production builds.
iOS: App Transport Security (ATS)
iOS enforces secure connections by default through App Transport Security (ATS), blocking any cleartext (HTTP) traffic. However, sometimes you might need to allow insecure connections during development, especially for local testing or non-production services.
Modify your Info.plist
file (located at ios/Runner/Info.plist
):
Add or modify the following ATS settings:
This configuration:
Disables arbitrary loads (HTTP connections) for production builds.
Allows insecure connections (HTTP) only for localhost
, useful when you're testing locally during development.
Test your setup:
Build a release version of your app (via flutter build ios
), then try hitting an http://
URL. You should see it fail as expected—indicating that your app is correctly enforcing HTTPS.
You don’t need complex setups to ensure secure HTTP requests. Dart’s http
package uses default TLS validation, so you can trust it right out of the box. Here’s how you make a secure request with it:
This simple line of code ensures that:
The request will only succeed if the connection is encrypted (via HTTPS).
If the server presents an invalid or expired certificate, Dart will throw a handshake exception, preventing the connection from being established.
Your app will fail closed (securely) rather than silently accepting a downgraded insecure connection.
If you need additional layers of security, like certificate pinning or advanced validation, you can wrap this client using IOClient
from the http/io_client.dart
package, giving you finer control over certificate handling.
Before diving into certificate pinning or enforcing strict policies, it’s essential to understand how TLS (Transport Layer Security) works in Dart and Flutter by default. TLS is the foundation of secure communication, and it’s crucial to know how your app handles it when performing HTTPS requests.
HttpClient
Validates Certificates by DefaultWhen you call http.get(...)
or use Dart’s HttpClient
, Dart automatically performs a standard TLS handshake to ensure the connection is secure:
ClientHello: Your app initiates the handshake by suggesting which protocol versions (e.g., TLS 1.2, TLS 1.3) and cipher suites it supports.
ServerHello & Certificate: The server responds with its chosen protocol and cipher suite, along with its certificate chain.
Validation: Dart’s HttpClient performs the following checks on the server’s certificate:
Certificate Chain: Dart ensures that the certificate is linked to a trusted root authority in your app’s trust store.
Expiry Check: Dart checks if the certificate is expired.
Hostname Matching: Dart ensures that the hostname you requested matches the certificate’s Subject Alternative Name (SAN) or Common Name (CN).
Key Exchange & Encryption: If the certificate passes all checks, Dart and the server exchange session keys and establish an encrypted connection.
The Simple, Secure Call
Under the hood, Flutter’s http
package uses IOClient
, which delegates to the same HttpClient
logic. The simplest, most secure call looks like this:
With this setup, you don’t need any additional code to validate certificates. Just use https://
for secure communication and ensure you don’t disable certificate validation callbacks.
SecurityContext
in DartStore certificates to validate servers (e.g., SSL/TLS certificates).
Store a private key for your server when acting as a server.
Control which TLS protocols to use (like enforcing TLS 1.3).
Manage certificate pinning to ensure only specific certificates or public keys are trusted.
You can create and configure a SecurityContext
in Dart to use it with an HttpClient
or HttpServer
when making or accepting HTTPS requests.For example, here’s how you load a certificate chain and a private key into a SecurityContext
:
useCertificateChain
: Loads the certificate chain from a file (in PEM format). The certificate chain can include the server's certificate and any intermediate certificates up to a trusted root certificate.
usePrivateKey
: Loads the private key used for the server's certificate. This key is crucial for secure communication, as it enables the server to prove its identity.
💡 Tip: For local development, you may use self-signed certificates for testing. Just ensure the server trusts them by adding
client.badCertificateCallback
or using anassert()
for dev-mode certificates.
Why SecurityContext
Matters
In production, you should never disable certificate verification. Doing so opens the door to severe security risks, such as man-in-the-middle (MITM) attacks, where an attacker could intercept and modify your traffic.SecurityContext
provides a secure, flexible, and powerful way to manage SSL/TLS connections in Dart. By configuring it properly, you ensure your app can securely connect to remote servers while avoiding common pitfalls.
Enforcing TLS Versions
You can enforce the use of specific TLS versions (e.g., TLS 1.3) by configuring the SecurityContext
. This is useful to make sure your app only uses the most secure and up-to-date protocols.
You can also define ALPN (Application-Layer Protocol Negotiation) to ensure certain protocols are used, like HTTP/2:
This ensures that your app negotiates the best available protocol for secure communication.
Safe Dev-Mode Overrides for Self-Signed Certificates
During development, you may need to connect to a local server with a self-signed certificate (common for testing). Instead of disabling validation globally (which is dangerous), you can apply a scoped override that only activates for your local development server and only in debug builds.Here’s how you can safely trust your local dev server during testing:
The assert()
ensures that this override only happens in debug mode (i.e., during development). The override will be stripped out in release builds, preventing any accidental trust issues.
client.badCertificateCallback
allows your app to trust the server, even if the certificate is self-signed, but only if the host is 10.0.2.2
(the default for local development in Android emulators).
Why “Trust-All” Is a Recipe for Disaster
It might seem tempting to fix the CERTIFICATE_VERIFY_FAILED
error by blindly accepting all certificates, like so:
However, this is extremely dangerous. What you’ve just done is disable all certificate validation. This effectively turns HTTPS into plain HTTP, leaving your app wide open to man-in-the-middle (MITM) attacks. Any attacker could present a fake certificate, and your app would blindly trust it.It’s like leaving your front door wide open and assuming no one will walk in. You won’t see the attacker coming, and they can capture everything you send or receive.
Certificate pinning adds an extra layer of security to your app by hard-wiring trust. Instead of relying on the OS trust store to validate certificates, pinning ensures that your app only trusts the specific certificate or public key it was shipped with.
This makes it much harder for attackers to intercept or manipulate traffic, even if they manage to install a rogue certificate authority (CA) on the device.
Why and When to Use Certificate Pinning
Extra Security Layer: Pinning ensures that your app will only trust a specific certificate or public key for a particular domain.
When to Use: Pinning is essential for apps that handle sensitive data, like those in finance, healthcare, or any domain that makes your app a target.
Downsides: Pinning requires operational overhead. You need to rotate pins before the certificate changes, or users will lose connectivity. Always ship a backup pin to survive certificate renewals.
When Not to Use: Pinning isn’t required for every app, especially if the server’s certificate is expected to remain stable and not change often. But it’s a strong defensive measure if you need to minimize risk.
Export the Server Certificate and SPKI Fingerprint
The first step in pinning is obtaining the certificate or public key you’ll pin to. You can extract the server’s certificate and its SPKI fingerprint using OpenSSL.
Dump the server's certificate (replace api.yourdomain.com
with your target domain):
Generate the SPKI hash (the public key’s SHA-256 hash):
Save the Base64 hash: This is the SPKI fingerprint you’ll pin.
Manual Pinning with SecurityContext
Once you have the certificate or SPKI hash, you can manually configure your app to only trust this certificate.
Add server_cert.pem
to your assets and declare it in pubspec.yaml
:
Create a pinned HttpClient
that uses your certificate:
setTrustedCertificatesBytes()
ensures only the certificates you’ve added are trusted.
badCertificateCallback
is used to reject any certificate not in the pinned certificate list.
Pinning by Fingerprint in a Callback
If you prefer to pin using the SPKI fingerprint instead of the full certificate, you can use the certificate’s hash directly in the badCertificateCallback
.
This approach avoids storing the full certificate and directly compares the certificate's hash against the expected value.
Wrap the HttpClient
in IOClient
and use it with the http
package just like before.
Using http_certificate_pinning
for Less Boilerplate
This package abstracts away much of the manual setup and makes pinning easier to implement.
Rotation Strategy and Gotchas
Ship at least two pins: Always have both the current and next certificate pinned, so you can smoothly rotate certificates.
Schedule renewals: Coordinate with your backend dev-ops to ensure the new certificate is live before the old one expires. Test failure scenarios by pointing your app to a server with an unpinned certificate to make sure it refuses to connect.
Obfuscate your pins if you’re worried about reverse engineering, but remember that an attacker with full device control can still bypass pinning. Pinning raises the bar, but it’s not a bulletproof shield.
Proactively rotate pins: Set up calendar reminders or CI/CD hooks to rotate pins before they expire.
With certificate pinning in place, your app will refuse to connect to impostor servers, even if they present a seemingly valid certificate. Pinning ensures that only the expected certificate or public key is trusted, adding defense in depth to your app’s security.
When your app needs instant updates—whether it's for chat, live dashboards, payments, or presence data—you’ll likely use WebSockets. They keep the connection alive and feel magical for real-time interactions. But don’t forget: ws://
is unencrypted and sends everything in plain text.Short take: Treat WebSockets exactly like HTTPS requests—TLS is mandatory, pinning and validation carry over, and never silently fall back to an insecure URL.
ws:// vs wss://
ws://
: WebSockets over plain HTTP—insecure, unencrypted, and prone to MITM (Man-In-The-Middle) attacks. Anyone on the same network can read or inject data.
wss://
: WebSockets over TLS—secure, encrypted, and ensures server identity and data integrity, just like HTTPS.
When working with real-time traffic, remember: it’s as sensitive as REST traffic. Always use wss://
, especially when transmitting sensitive data (chat, payments, user info). Never send sensitive data via ws://
.
Secure WebSocket Client in Flutter (No Extra Packages)
The web_socket_channel
package, part of the Flutter standard toolbox, supports WebSocket connections and allows you to pass a custom HttpClient
—so you can reuse the pinning logic from Section 5.Here’s how you can create a secure WebSocket connection with certificate pinning:
Key Rules for WebSocket Security
Never deploy ws://
—always use wss:// for production. Strip out any ws://
references in your release configurations.
If the server uses a self-signed certificate in staging, ensure it’s only trusted during debug builds. Production must fail closed if the certificate is invalid.
Treat handshake failures as fatal—don’t auto-retry insecure WebSocket connections. Never fall back to ws://.
Apply the same timeout and retry backoff logic you use for REST API calls. Just because the WebSocket is open doesn’t mean it’s healthy.
Backend Setup with Dart (dart_frog or shelf)
If you’ve followed Section 3 and your backend already serves HTTPS with a valid certificate, WebSocket connections inherit the same secure setup. Here’s how to handle WebSocket upgrade requests in a Dart server (using shelf
or dart_frog
):
As long as the listener runs on port 443 and is using the proper SSL/TLS certificate, the WebSocket will automatically use wss://
.
Testing the Failure Mode
To ensure everything works securely, test the failure mode:
Install mitmproxy on a test device and install its root certificate.
Modify your app’s config to point at https://realtime.yourdomain.com.
If your WebSocket pinning is set up correctly, the connection should fail with a handshake error when the invalid certificate is presented.
Your app should show a user-friendly error like “Secure connection failed”. Never allow it to silently downgrade to an insecure connection.
With secure WebSocket connections (wss://
), your app can safely transmit real-time data just as it does with HTTPS. Pinning certificates and ensuring strong validation reduces the risk of MITM attacks and ensures data integrity.
While TLS ensures that your data is protected in transit, you still need to be mindful of where you store sensitive information in your app, and how it is handled on the device. Storing secrets in the wrong place or accidentally logging sensitive data can undermine your security. Let’s break down best practices for safe payload design, secure storage, and leak-free logging.
Keep Secrets out of URLs
Never put sensitive data (like tokens) in URLs. Query strings are easily logged in proxies, crash reports, or even screenshots. The following is a bad example:
Instead, use headers or JSON bodies to transmit sensitive data:
Tokens or credentials in URLs can easily be captured by intermediate services, proxies, or even browser history.
Explicit Headers and JSON Bodies
Always:
Set Content-Type: application/json
when sending JSON data.
Convert maps to JSON using jsonEncode
(never concatenate strings).
Capture response.statusCode
and handle errors like 401
/403
properly by failing closed instead of silently retrying.
This ensures your app handles errors in a predictable and secure manner, rather than accidentally exposing sensitive data.
Secure Storage on Device
When to wipe tokens: If the device is rooted, the token could still be extracted from memory. Wipe the token on every "app background" event and rely on refresh tokens to quickly obtain a new one.
Don’t store tokens in memory or static variables between sessions. Always reload tokens securely from encrypted storage when needed.
Log and Analytics Hygiene
Sensitive data can easily leak through logs. A stray print(response.body)
or verbose logging left in production code is an open invitation for a data leak. Here’s how to keep your logging secure:
Remove verbose logging in production using flags like --dart-define=FLUTTER_WEB_LOGS=false
or wrap logs inside assert(() { … }())
for development-only logging.
Redact sensitive data in crash reporting services (e.g., Crashlytics, Sentry). Configure hooks to automatically redact tokens, emails, GPS coordinates, or anything personally identifying.
Application-Level Encryption (When TLS Isn’t Enough)
In certain scenarios, especially when regulations or threat models demand that data remain unreadable even on a compromised transport layer, you may want to encrypt the payload itself, in addition to relying on TLS.Here’s an example using the encrypt
package for AES encryption:
Share the encryption key securely with your backend. You can use asymmetric encryption to securely exchange keys (public/private key pairs) or share the key out of band.
Use different encryption keys for each user/session, and ensure key rotation occurs regularly.
Avoid hardcoding encryption keys directly in the app binary; store them securely.
Even flawless code can be sabotaged by a stray manifest flag or a forgotten server redirect. Lock the doors at the OS and server layers so your app physically cannot talk over cleartext.
Recent Android versions disable HTTP by default, but one debug tweak can switch it back on. Add a network‑security config that allows only the domains you own, and only over TLS.res/xml/network_security_config.xml
Point your <application>
tag at this file:android:networkSecurityConfig="@xml/network_security_config"
Ship a release build, hit any http://
URL, and watch it fail fast—exactly what you want.
iOS’s ATS already blocks cleartext. Make sure no earlier testing flag sneaks into production.ios/Runner/Info.plist
snippet
Preloading HSTS ensures even first-time users never hit your API over HTTP. Add the HTTP Strict Transport Security header so any compliant client refuses to downgrade.Nginx example
max-age
: one year in seconds
Building defenses is only half the game; now we prove they work and keep working.
Spin up OWASP ZAP or Burp Scanner in Docker each pull‑request.
Fail the build on any medium‑ or high‑risk finding.
If you own Burp Enterprise, call its REST API the same way; block merges when new TLS or mixed‑content issues appear.
Mitmproxy: start a local proxy, install its root cert on emulator, route traffic through 127.0.0.1:8080
.Expected results:
Any http://
request should fail instantly (platform block).
A pinned https://
request should fail handshake because the proxy cert isn’t pinned.
Debug‑only overrides (localhost, self‑signed) should still work.
Frida script (advanced): hook dart:io
’s _HttpClient
and attempt to replace badCertificateCallback
. Your pinning logic should continue to reject the injected cert, proving an on‑device attacker can’t bypass it without heavy lifting.
Never loop forever on a broken TLS handshake; instead:
If you still can’t connect, surface an error like “Secure connection failed—check Wi‑Fi or VPN” and refuse to downgrade.
Ingest mobile TLS failures into your backend logs (status code 0 or handshake exception). A sudden spike may mean your cert expired or a captive portal is blocking traffic.
Monitor pin‑mismatch events separately; if they rise, someone might be MITM‑ing users or your cert rotation went sideways.
Set up an uptime robot that curls your API over HTTP every hour; it must receive a 301/308 redirect or a 403 block. Alert if it ever gets a 200 OK—that means someone accidentally re‑enabled plaintext.
Once per quarter flip staging to an invalid certificate and run the app end‑to‑end:
Does the UI show a clear, user‑friendly error?
Does it refrain from retrying in the clear?
Can you ship a hotfix pin update quickly if needed?
For bonus points, automate pin mismatch simulation in CI using mocked certs or a TLS-intercepting proxy.
When these drills are boring, you’ve done it right: your pipeline, runtime checks, and human processes all treat insecure communication as an outage, not a warning.Next we’ll add final hardening for compromised devices with runtime detection.
Even with TLS, pinning, and platform blocks in place, everything collapses if the app runs on a rooted phone, inside an emulator, or under a live debugger. At that point an attacker can dump memory, switch off pinning at runtime, or extract tokens directly from disk. To close that last gap I add one more guardrail: on‑device tamper detection that shuts down sensitive flows the moment something looks wrong.
Root / jailbreak removes the OS sandbox; malware can read secure storage or inject hooks into Dart’s TLS stack.
Emulators invite dynamic instrumentation—think Frida scripts that patch badCertificateCallback
to always return true
.
Repackaged APKs can disable pinning, add spyware, then re‑sign the bundle and trick users into installing it.
Debuggers allow step‑through inspection of memory, exposing encryption keys and personal data.
One detection library will not beat every attacker, but it raises the cost so high that most move on to softer targets.
Add the dependency to pubspec.yaml
Bootstrap as early as possible—before you fetch tokens or open sockets.
High‑risk flows (payments, health‑records)
Immediately erase secrets, close sockets, and exit or force re‑authentication.
Medium‑risk flows (chat, productivity)
Switch to read‑only mode, warn the user, and log the incident.
Audit trail
Post a lightweight event (device hash, event type, UTC timestamp) so your SOC can spot trends and decide if a user or region is under attack.
Rooted emulator: launch the app; _react
should fire and secrets must be wiped.
For extra protection, send anonymized metrics to your SOC or backend security dashboard for analysis.
Frida attach: run frida -U -f com.example.app
; debugger detection should trigger.
Repackaged APK: decompile with Apktool, rebuild, reinstall; app should refuse to run.
False‑positive check: release build on clean hardware—no callbacks should trigger.
We started this journey in a bustling café, watching an attacker try to intercept our traffic as we sent our "postcard." Ten sections later, our once-vulnerable postcard has transformed into an armored van: HTTPS everywhere, strict certificate validation, pinning for sensitive use cases, secure WebSockets, airtight payload handling, platform policies that block cleartext, automated scans, hands-on MITM drills, and FreeRASP standing guard on compromised devices.
Key Takeaways:
Encrypt every byte in transit: No http://
, no ws://
. Always use https://
and wss://
for security.
Trust, but verify: Let Dart’s HttpClient
handle certificate validation; only implement pinning when you’re ready to manage the rotation playbook.
Keep secrets secure: Never store sensitive information in URLs, logs, or plain preferences. Use flutter_secure_storage
for safe storage.
Test your app like it's in production: Break the build on TLS regressions, and fail loudly on handshake errors.
Assume hostile devices: Implement runtime checks like FreeRASP to halt sensitive flows at the first sign of tampering.
With these layers of defense in place, if one layer slips, the others catch it. This defense-in-depth approach is at the heart of OWASP M5 for Flutter and Dart developers.
In Part Six, we move from securing the wire to securing the binary: Reverse Engineering & Code Protection (M6). We'll crack open a Flutter APK/IPA, show how attackers decompile Dart, inject method swizzles, and siphon hard-coded keys. Then, we’ll teach you how to harden your build so they leave empty-handed.See you there!
(free, automated)
Android:
In Dart, is used to configure SSL/TLS settings when establishing a secure connection, such as for HTTPS requests. It’s an essential part of managing certificates and enforcing security protocols.Let’s break it down step-by-step:A object stores and manages the certificates and keys used for secure communication (like HTTPS or gRPC) in your Dart server or client.It can:
If you want to simplify the pinning process, you can use the package, which reduces the amount of boilerplate code needed.
I have written a lot about this in But let's have a quick review here too.Avoid using insecure storage solutions like SharedPreferences for sensitive data. SharedPreferences stores data in plaintext, making it vulnerable to extraction. Instead, use the OS key-store via flutter_secure_storage
for secure data storage.
Build a release IPA and run curl
inside the app’s WebView or via http.get
; it should error instantly.
includeSubDomains
: catch everything, even
preload
: after seven clean days submit to so all browsers hard‑code HTTPS for you.
Majid Hajian - Azure & AI advocate, Dart & Flutter community leader, Organizer, author