Security
Irbisa · cheatsheetSeptember 13, 2026

Security

Secure storage, certificate pinning, obfuscation, OWASP mobile top 10

Senior Developer20 itemscompressed for a skim
  1. 01

    What are the key security best practices for Flutter mobile apps?

    Hard

    Mobile security splits into five layers, and an interviewer usually wants one concrete control named per layer.

    // ── Secure Storage ────────────────────────────────
    const _storage = FlutterSecureStorage(
      aOptions: AndroidOptions(encryptedSharedPreferences: true),
      iOptions: IOSOptions(
        accessibility: KeychainAccessibility.first_unlock_this_device,
      ),
    …
  2. 02

    What is SSL/certificate pinning and how do you implement it in Flutter?

    Hard

    Certificate pinning prevents Man-in-the-Middle (MITM) attacks by hardcoding the expected server certificate or public key in the app.

    // ── Approach 1: trust ONLY your own certificate (strongest) ──
    import 'dart:io';
    import 'package:dio/io.dart';
    
    Future<Dio> createPinnedDio() async {
      final pem = await rootBundle.load('assets/certs/api_example_com.pem');
    …
  3. 03

    How does OAuth 2.0 with PKCE work for mobile apps and why is it required?

    Hard

    Public clients (mobile apps, SPAs) cannot keep a client_secret confidential — the secret would be embedded in the app binary, trivially extracted.

    // pubspec: flutter_appauth
    import 'package:flutter_appauth/flutter_appauth.dart';
    
    final _appAuth = const FlutterAppAuth();
    const _clientId = 'mobile-client-id';
    const _redirect = 'com.example.app:/oauthredirect';
    …
  4. 04

    How does code obfuscation work in Flutter, and how do you symbolicate crashes after enabling it?

    Medium

    Dart compiles to AOT machine code on release builds, which is harder to read than JS/Java but still leaks identifier names in the snapshot.

    # 1) Build a release with obfuscation + split debug info
    flutter build apk --release \
      --obfuscate \
      --split-debug-info=build/symbols/v1_2_3
    
    flutter build ipa --release \
    …
  5. 05

    What are App Transport Security (iOS) and Network Security Config (Android), and when do you have to touch them?

    Medium

    Both platforms ship a system-level network policy that runs BEFORE your code.

    <!-- iOS: ios/Runner/Info.plist — narrow ATS exception per domain -->
    <key>NSAppTransportSecurity</key>
    <dict>
      <key>NSAllowsArbitraryLoads</key>
      <false/>
      <key>NSExceptionDomains</key>
    …
  6. 06

    How do you avoid leaking sensitive data through logs, screenshots, and clipboard?

    Medium

    Most production leaks are not fancy attacks — they are tokens in crash logs, PII in analytics events, and payment screens captured in the app switcher.

    // ── Redacting logs ──────────────────────────────────
    class SafeLogger {
      static const _redactKeys = {'password', 'token', 'access_token', 'refresh_token', 'authorization', 'cookie'};
      static String redact(Map<String, dynamic> body) =>
          jsonEncode({
            for (final e in body.entries)
    …
  7. 07

    How do you implement a robust access-token + refresh-token rotation strategy?

    Hard

    Long-lived bearer tokens are the most common credential-leak vector in mobile apps.

    import 'dart:async';
    import 'package:dio/dio.dart';
    import 'package:flutter_secure_storage/flutter_secure_storage.dart';
    
    class AuthInterceptor extends Interceptor {
      AuthInterceptor(this._dio, this._storage);
    …
  8. 08

    What are the security pitfalls of using WebView in a Flutter app?

    Hard

    WebViews host arbitrary web content with the trust of your app process.

    import 'package:webview_flutter/webview_flutter.dart';
    
    // ✅ Constrained WebView — explicit allowlist, no JS bridge unless needed
    class HelpCenterPage extends StatefulWidget {
      const HelpCenterPage({super.key});
      @override State<HelpCenterPage> createState() => _HelpCenterPageState();
    …
  9. 09

    Why is a deep link untrusted input, and what stops another app from stealing yours?

    Hard

    A deep link arrives from outside your process, unauthenticated, with attacker-controlled parameters, and a plain custom scheme has no owner — any app on the device can register myapp:// too.

    // go_router: one guarded entry point for every incoming link
    final router = GoRouter(
      redirect: (context, state) {
        final loggedIn = ref.read(authProvider).valueOrNull != null;
        // ✅ session is re-checked on arrival, whatever the link claimed
        if (!loggedIn && _protected.hasMatch(state.matchedLocation)) {
    …
  10. 10

    A teammate wants to add a pub package with 30 likes to handle payments. How do you decide, and how do you keep the dependency tree safe over time?

    Medium

    Every package you add runs inside your app with your app's permissions, and so does everything it depends on — which is why OWASP lists supply chain as its own top-ten category.

    #!/usr/bin/env bash
    set -euo pipefail
    
    # What is actually being installed, including the transitive tree
    flutter pub deps --style=compact
    flutter pub outdated --show-all
    …
  11. 11

    local_auth returns true after a Face ID prompt. What has that actually proven, and how do you turn it into real protection?

    Medium

    It proves only that the operating system was willing to say yes on this device, in your process, right now — it is a UI gate, not a credential.

    import 'package:local_auth/local_auth.dart';
    import 'package:flutter_secure_storage/flutter_secure_storage.dart';
    
    final _auth = LocalAuthentication();
    const _storage = FlutterSecureStorage(
      iOptions: IOSOptions(
    …
  12. 12

    A third-party service hands you an API key and says to put it in the app. What do you do with it?

    Medium

    Start by classifying it, because anything the app can read at runtime an attacker with the binary can read too.

    // ✅ Publishable key — compiled in, restricted server-side, safe to ship
    class Config {
      static const mapsKey = String.fromEnvironment('MAPS_KEY');
      static const apiBase = String.fromEnvironment(
        'API_BASE', defaultValue: 'https://api.example.com',
      );
    …
  13. 13

    Users are signed out after moving to a new phone, and a few Android launches throw a decryption error reading the token — what happened?

    Medium

    The ciphertext was restored and the key was not: flutter_secure_storage writes an encrypted blob into a normal file, while the key that opens it lives in the Keystore or Secure Enclave and is not exportable.

    import 'package:flutter/services.dart';
    import 'package:flutter_secure_storage/flutter_secure_storage.dart';
    import 'package:shared_preferences/shared_preferences.dart';
    
    const _storage = FlutterSecureStorage(
      iOptions: IOSOptions(
    …
  14. 14

    Your app decodes the JWT, reads a role claim of admin and shows the admin tab — why is that a finding in a pentest report?

    Medium

    Because hiding a button is not access control: the endpoint behind it is still there, and the attacker calls the endpoint, not your widget tree.

    // ❌ The tab is hidden; the endpoint is wide open
    final claims = JwtDecoder.decode(accessToken);
    if (claims['role'] == 'admin') {
      tabs.add(const AdminTab()); // repackaged build: force this branch, call the API
    }
    …
  15. 15

    A search box feeds its text straight into a sqflite rawQuery, and a download saves under the filename the API returned; what do you fix first?

    Medium

    Both lines are the same bug: input from outside the app is being spliced into a language — SQL in one case, a filesystem path in the other — instead of being handed over as a value.

    import 'package:path/path.dart' as p;
    import 'package:sqflite/sqflite.dart';
    
    // ❌ One apostrophe changes the statement; a crafted string changes the app
    Future<List<Map<String, Object?>>> searchBad(Database db, String q, String sort) =>
        db.rawQuery("SELECT * FROM notes WHERE title LIKE '%$q%' ORDER BY $sort");
    …
  16. 16

    App Store Connect emails you about ITMS-91053 right after you add two plugins — what is Apple asking for, and what should you audit at the same time?

    Medium

    It wants a PrivacyInfo.xcprivacy that declares every required-reason API your binary touches, with a reason code from Apple's fixed list — and the missing one is almost certainly inside a plugin, not in your code.

    <!-- ios/Runner/PrivacyInfo.xcprivacy — add to Copy Bundle Resources -->
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
      "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    …
  17. 17

    Your root-detection check returns false on an obviously rooted phone and the tester says they simply attached Frida — what should have been protecting you?

    Hard

    Any answer computed inside your process can be rewritten inside your process; the only durable control is a hardware-backed attestation that your server verifies.

    import 'package:dio/dio.dart';
    import 'package:flutter/services.dart';
    
    /// Thin channel over Play Integrity / App Attest — the native side returns an
    /// opaque token; Dart deliberately cannot read or judge it.
    class Attestation {
    …
  18. 18

    A teammate encrypts the offline document cache with AES, using a key derived from the user's email and a constant IV copied from a tutorial — what breaks?

    Hard

    A key anyone can compute is not a key, and a fixed IV leaks the relationships between messages — together they turn encryption into obfuscation with extra steps.

    import 'dart:convert';
    import 'dart:math';
    import 'dart:typed_data';
    import 'package:cryptography/cryptography.dart';
    import 'package:flutter_secure_storage/flutter_secure_storage.dart';
    …
  19. 19

    The token store is encrypted, yet a forensics report pulled chat attachments and cached images off a locked test phone — how did they get out?

    Hard

    Platform disk encryption stops helping after the first unlock since boot, and everything outside flutter_secure_storage sits under exactly that default.

    import 'dart:io';
    import 'package:flutter_cache_manager/flutter_cache_manager.dart';
    import 'package:path_provider/path_provider.dart';
    import 'package:webview_flutter/webview_flutter.dart';
    
    /// Derived data belongs in Caches/tmp: not backed up, and the OS may reclaim it.
    …
  20. 20

    A modified build of your app unlocks the premium tier without paying, and the in-app purchase code looks correct — where is the trust boundary wrong?

    Hard

    The entitlement is being granted by the device, and the device is running the attacker's code — the grant belongs on your server, decided from a receipt the store signed.

    import 'package:dio/dio.dart';
    import 'package:in_app_purchase/in_app_purchase.dart';
    
    class Purchases {
      Purchases(this._iap, this._api, this._entitlements);
      final InAppPurchase _iap;
    …