Flutterアプリのセキュリティチェックリスト
フェーズ0: ガバナンス、環境、CI/CD
ツールとバージョン
- Flutter、Dart SDK、Android Studio/Xcode、およびすべてのプラグイン/依存関係を最新の状態に維持する。
- 各リリース前に、FlutterパッケージおよびネイティブSDKのセキュリティアドバイザリを確認する。
CIゲートの強制
- すべてのPRで以下を実行する:
-
dart analyze -
flutter test --coverage - Android/Gradle lint
- iOSビルドチェック
- 依存関係の監査(Dependency audits)
- アナライザーのエラーおよびエラーとして設定されたすべての警告でビルドを失敗させる。
- リリースブランチにおいてlintエラーゼロを強制する。
セキュリティゲートの確立
- すべてのPRおよびリリース前に、モバイル向けSAST/SCA/DASTスキャンを実行する。
- High/Criticalの検出事項でリリースをブロックする。
- High/Criticalの検出事項が発生した場合にパイプラインが失敗するよう、スキャンをCI/CDに直接統合する。
シークレットの取り扱い
- 以下にシークレットが含まれていないことを確認する:
- Dartソースコード
- ネイティブコード
-
AndroidManifest.xml -
Info.plist - アセット
- ログ
- 環境変数、CIシークレットストア、またはシークレットマネージャーを使用する。
- アナリティクスおよびロギングにおいてシークレットをマスキング(秘匿化)する。
- APIキー/トークンを定期的にローテーションし、漏洩が疑われる場合は直ちにローテーションを実施する。
エビデンスとトレーサビリティ
- すべてのアーティファクトについて以下を生成・アーカイブする:
- スキャンレポート
- SBOM
- ビルドログ
- テストレポート
- リリースノート
フェーズ1: 脅威モデリング、データ分類、およびインベントリ
- データタイプ(PII、認証情報、トークン、決済データ、ヘルスケアデータ)を分類する。
- 各データタイプが以下のどこで収集、処理、保存、送信されるかを文書化する:
- Flutterアプリコード
- ネイティブレイヤー
- 信頼境界(Trust Boundaries)を特定する:
- Flutterアプリコード
- ネイティブプラットフォームチャネル(Android/iOS)
- デバイスストレージ
- WebView/ブラウザ
- バックエンドAPI
- サードパーティサービス
- すべてのDartパッケージ、プラグイン、およびネイティブSDKのインベントリを作成する。
- 適切に保守され、署名されており、最小権限の原則に従った依存関係を優先する。
- 依存関係の総数およびスコープを最小限に抑える。
- リスククラスごとにセキュリティ合否基準を定義する(例:「保存時に機密データを平文で保持しない」、「すべての認証トラフィックをピン留めする」、「リリースビルドにデバッグログを含めない」など)。
フェーズ2: ビルドおよび設定の堅牢化(Flutter + Android + iOS)
Flutterリリースビルド
- Android向けに難読化およびデバッグシンボルの分割を有効にしてビルドする:
-
flutter build appbundle --release --obfuscate --split-debug-info=build/symbols - iOS向けに難読化およびデバッグシンボルの分割を有効にしてビルドする:
-
flutter build ipa --release --obfuscate --split-debug-info=build/symbols -
build/symbolsをサーバー側で安全に保管する。 - アプリバンドル内にシンボルファイルやマッピングファイルを絶対に同梱して配布しない。
ログ出力の適正化
- ログ出力を
kReleaseModeまたは同等の条件分岐で保護する。 - 本番コードパスから
print/debugPrint/ 詳細ログを削除する。 - リリースブランチの非テストコードにデバッグログが含まれている場合にビルドを失敗させるCIチェックを追加する。
Android(Gradle + マニフェスト)
- リリース向けにR8/ProGuardおよびリソース縮小(Resource Shrinking)を有効にする:
-
minifyEnabled true -
shrinkResources true - 最適化されたProGuardルールを使用する。
-
AndroidManifest.xmlを堅牢化する: - リリースビルドで
android:debuggable="false"に設定する。 - 安全なバックアップ設計が確立されていない限り
android:allowBackup="false"に設定する。 -
android:usesCleartextTraffic="false"に設定し、本番環境でHTTPへのフォールバックを禁止する。 - コンポーネントに明示的な
android:exportedを設定し、強力なパーミッション保護なしでの公開を避ける。 - Network Security Config を設定する:
- グローバルで平文トラフィックを無効化する。
- ドメイン設定を定義する。
- ピンニングを使用する場合は、バックアップピンを含むピンセットを定義する。
- 署名と整合性:
- 該当する場合は Play App Signing を使用する。
- キーストアを保護する。
- Play Integrity API を有効化し、そのシグナルをバックエンドのリスク判定に統合する。
iOS(Flutterプロジェクト内のXcode + Info.plist)
- App Transport Security(ATS):
-
NSAllowsArbitraryLoads = false。 - 厳格な
NSExceptionDomainsは、必要な期限付きの例外にのみ限定して使用する。 - リリースビルド設定:
- シンボルをストリップ(削除)する。
- デッドコードストリッピングを有効にする。
- Whole-Module Optimization(モジュール全体の最適化)を有効にする。
- リリースビルドに
DEBUGフラグやテストライブラリが含まれていないことを確認する。 - ネイティブ/プラグインストレージ向けのKeychainデフォルト設定:
- 可能な限り
ThisDeviceOnlyクラスを優先する。 - バックグラウンド移行時にスナップショットをマスキング(ネイティブまたはFlutterプラグイン経由)し、アプリスイッチャーに機密データが表示されないようにする。
フェーズ3: ネットワークセキュリティとTLS/ピンニング
HTTPS/TLSの強制
- すべてのAPIトラフィックでHTTPS/TLS 1.2+の使用を強制する。
- 無効な証明書、自己署名証明書(明示的に信頼されていない場合)、およびホスト名が一致しないエンドポイントを拒否する。
- 適切なタイムアウト値を設定する。
- 必要に応じてジッター(揺らぎ)を伴うリトライを実装する。
- TLSエラー発生時は常に安全側に倒して接続を遮断する(Fail Closed:HTTPへのダウングレードを行わない)。
証明書ピンニング
- クリティカルなドメインに対して、以下において証明書またはSPKI SHA-256ピンニングを実装する:
- Flutterが使用するHTTPクライアント
- プラグインが使用するネイティブネットワーキング
- ホストごとに少なくとも1つのバックアップピンを維持する。
- ピンのローテーションシナリオを設計・テストする。
- 以下の条件下でのピンニング動作を検証する:
- MITM(中間者攻撃)の試行
- 期限切れの証明書
- 信頼されていない認証局(CA)
- ピン検証の失敗をログに記録し、テレメトリとして可視化する。
サーバー側のセキュリティ
- バックエンドエンドポイントでHSTSを強制する。
- 強力な暗号スイートおよび最新のプロトコルバージョンを使用する。
- WebViewまたはアプリ内ブラウザからアクセスされるWebエンドポイントには、Secure、HttpOnly、SameSite属性を持つCookieを使用する。
- クライアント上で長寿命のベアラートークンを保持しない。
フェーズ4: ローカルストレージとシークレットの取り扱い
シークレットとトークン
- 以下にシークレット/トークンを保存しない:
-
SharedPreferences -
plistファイル - 暗号化されていないSQLite
- Dartからの平文ファイル
- 以下にシークレットを埋め込まない:
-
const値 - アセット
- 自動生成されたコード
セキュアストレージ
- トークンおよび暗号鍵には、プラットフォームのキーストアにバインドされたセキュアストレージを使用する(例:Flutterプラグイン経由のAndroid Keystore、iOS Keychain)。
- UXが許容する場合、生体認証またはOSパスコードによって高リスクな鍵の復号/使用をゲート制御する。
- 高価値なトークンには、デバイスバインドされた鍵(
ThisDeviceOnly/ ハードウェア保護)を優先する。
暗号化ストレージ(ファイル/DB)
- 機密レコードの保存には、暗号化SQLiteまたは同等のファイル暗号化を使用する。
- キャッシュレイヤー(HTTPキャッシュ、ローカル永続化など)がPIIやシークレットを平文で保存しないようにする。
データのライフサイクル
- ログアウト時およびアカウント削除時に以下を消去する:
- キャッシュ
- セキュアストレージのエントリ
- メモリ内の機密オブジェクト
- 認証フローまたは高リスクセッションの終了後、WebViewまたはアプリ内ブラウザのデータを消去する。
- 暗号化されていない形式での機密アプリデータのバックアップを防止するよう、Android/iOSのバックアップルールを設定する。
フェーズ5: プライバシー保護(UI、クリップボード、スクリーンショット)
- アプリがバックグラウンドに移行した際、またはタスクスイッチャーに表示された際に、機密性の高いFlutter画面をマスキングする:
- Androidでの
FLAG_SECURE - iOSでのスナップショットマスキング
- 極めて機密性の高いビュー(クレジットカード番号全体、シークレットなど)には、以下を備えた専用の「セキュアスクリーン」パターンを使用する:
- 追加のマスキング
- スクリーンショット/画面録画の防止
- アプリ起動時にクリップボードの内容を読み取ることを避ける。
- パスワード、トークン、またはパスワードリセット用リンクをクリップボードに絶対にコピーしない。
- ユーザーに明確に通知され明示的に要求された場合を除き、アプリ内のスクリーンショットやエクスポートからシークレット/PIIを除外する。
フェーズ6: 認証、認可、およびセッション管理
認証フロー
- パブリックなFlutterクライアントにはPKCEを伴うOAuth2/OIDCを使用する。
- アプリ内にクライアントシークレットを埋め込まない。
- システムブラウザ/プラットフォーム提供の認証フローを優先する:
- Android上のChrome Custom Tabs
- プラグイン経由のiOS上の
SFSafariViewController/ASWebAuthenticationSession - 可能な限りWebView内でのログインを避ける。
トークン
- 短寿命のアクセストークンを使用する。
- リフレッシュトークンはプラットフォームのキーストアにバインドされたセキュアストレージにのみ保存する。
- トークンを定期的にローテーションする。
- 以下の場合にトークンを無効化する:
- ログアウト時
- デバイス変更時
- 不審なアクセスパターンの検知時
- 極めて機密性の高い操作については、AndroidおよびiOSからのデバイスシグナルを活用し、デバイスバインディングおよびリスクベースの検証を検討する。
認可(Authorization)
- サーバー側でアクセス制御を強制する。
- リソースの保護をDart/UI側のロジックに依存しない。
- Flutterアプリが使用するすべてのAPIエンドポイントにおいて、以下に対するテストを実施する:
- IDOR/BOLA
- マスアサインメント(Mass Assignment)
- 垂直および水平方向の権限昇格
フェーズ7: WebViewの堅牢化とブラウザベースのフロー
推奨パターン
- 可能な限り、認証および決済にはシステムブラウザまたはプラットフォーム提供のセキュアなWebビューを使用する:
- Custom Tabs
-
SFSafariViewController
WebViewが必要な場合(Flutter WebViewまたはネイティブ)
- デフォルトでJavaScriptを無効化する。
- 明示的に必要な場合にのみJavaScriptを有効化し、その機能を制限する。
- ナビゲーションを信頼できるホストおよびスキームの許可リストに制限する。
-
file://、javascript:、およびその他の危険なURLをブロックする。 - AndroidおよびiOSのリリースビルドでWebコンテンツのデバッグ機能を無効化する。
- 機密セッション(認証、決済、PII編集)の終了後、WebViewのCookie、キャッシュ、およびストレージを消去する。
- ファーストパーティのWebコンテンツにおいて以下を確認する:
- CSP(コンテンツセキュリティポリシー)を適用していること
- HTTPSのみを使用していること
- 混在コンテンツ(Mixed Content)が存在しないこと
フェーズ8: アプリ間通信、ディープリンク、およびプラットフォームチャネル
ディープリンクとアプリ間フロー
- ディープリンクにはAndroid App LinksおよびiOS Universal Linksを優先して使用する。
- 処理を実行する前にリンクの送信元を検証する。
- 処理を実行する前にリンクパラメータを検証およびサニタイズする。
- 安全でないカスタムURLスキームの使用を避ける。
- カスタムスキームを使用する場合:
- 一意のスキームを選択する。
- すべてのパラメータおよびリダイレクトを堅牢に検証する。
プラットフォームチャネル(MethodChannel / EventChannel)
- Flutterからネイティブへ、およびその逆方向に渡されるすべてのデータは、検証されるまで信頼できないものとして扱う。
- ネイティブ側でメソッド名およびパラメータを検証する。
- 強力なネイティブAPIをチャネル経由で直接公開することを避ける。
- 検証およびサニタイズを行わずに、Flutterからの未検証の生データを特権的なネイティブ呼び出し(ファイルシステム、システム設定、通信機能など)に直接渡さない。
- テスト時にプラットフォームチャネルの入力に対してファジングを実施する。
- 予期しないメソッド呼び出しや不正な形式のデータをログに記録し、安全に処理する。
フェーズ9: リバースエンジニアリングおよび耐改ざん性
静的解析による情報漏洩の低減
- Dartコードを難読化する。
- Android向けにR8/ProGuard/リソース縮小を有効化する。
- 両プラットフォームにおいて、配布用バイナリからシンボルおよびデバッグ情報をストリップ(削除)する。
- リリースビルドから詳細ログ、デバッグメニュー、テストエンドポイント、非表示の機能フラグ、およびバックドアを削除する。
- 容易に抽出可能な形式(アセット内の平文文字列など)でシークレットや機密ビジネスロジックを埋め込むことを避ける。
環境チェック
- AndroidおよびiOSの両方で、ルート化/脱獄(Jailbreak)/フッキング/デバッガの検出機能を導入する(プラグインまたはネイティブコード経由)。
- セキュリティ監視のために検出されたシグナルを記録する。
- 何も検知しなかったかのように通常通り実行を継続するのではなく、状況に応じて適切に対応する:
- 機能を制限する
- 追加の検証を要求する
- 高リスクな操作をブロックする
改ざんテストと整合性
- 再パッケージ化または再署名されたFlutterアプリが、整合性チェックまたはサーバー側のアテストによって検出されることを検証する:
- Play Integrity(Android)
- iOS上のDeviceCheck / App Attest
- クライアント側の改ざんチェックはバックエンドへのシグナルとして扱う。
- クライアント側の改ざんチェックのみを唯一のセキュリティ制御として使用しない。
フェーズ10: 暗号技術と乱数生成
- 実績がありプラットフォームで承認された暗号ライブラリを使用する(例:Android Keystore/iOS Keychainをラップするプラグイン、または十分に検証された暗号ライブラリ)。
- Dartで独自の暗号アルゴリズムを実装しない。
- 暗号化には、以下の最新のAEADモードを使用する:
- AES-GCM
- ChaCha20-Poly1305
- 鍵導出関数(KDF)には、適切なパラメータと値ごとのソルトを指定した以下を使用する:
- PBKDF2
- HKDF
- 暗号論的に安全な乱数生成(プラグイン経由のSecureRandomまたはプラットフォームRNG)を使用して鍵、IV、およびノンスを生成する。
- IV/ノンスを絶対に再利用しない。
- 暗号鍵はKeystore/Keychainで保護されたストレージに保持するか保存時に暗号化し、メモリや設定ファイル内に必要以上に長期間保持しない。
フェーズ11: 権限、通知、および識別子
権限(パーミッション)
- 必要最小限の権限のみを要求する。
- 実際に使用するタイミング(Point-of-Use)でのみ権限を要求する。
- 各権限が必要な理由を説明する明確なUXを提供する。
- 拒否および権限取り消し時の適切なフォールバック処理を実装する(クラッシュや無限プロンプトを避け、制限された機能で動作を継続する)。
プライバシーと識別子
- 広告/アナリティクス識別子(GAID/IDFAなど)は、プラットフォームのポリシーに従い適切な同意フローを経た上でのみ使用する。
- 以下を使用したクロスアプリでの永続的なフィンガープリンティングの構築を回避する:
- ハードウェアID
- MACアドレス
- その他の禁止されている識別子
通知
- プッシュ通知/ローカル通知に含まれる機密コンテンツを最小限に抑える。
- ロック画面に表示される通知には一般的なタイトル/説明を使用する。
- ユーザーが通知カテゴリおよび機密度を設定できるようにする。
- 通知ペイロードにシークレットや完全なPIIを絶対に含めない。
フェーズ12: ロギング、アナリティクス、およびクラッシュレポート
- 構造化ロギングに標準化する。
- リリースビルドでは詳細ログ/デバッグログを無効化する。
- 以下からPIIおよびシークレットをマスキング(秘匿化)する:
- ログ
- アナリティクスイベント
- クラッシュレポート
- アナリティクスには集約およびサンプリングを優先する。
- クラッシュ時の難読化解除のために、シンボル/マッピングファイル(Flutterの分割デバッグ情報、R8マッピング、dSYM)をサーバー側で安全に保管する。
- アプリバンドル内にシンボル/マッピングファイルを絶対に同梱して配布しない。
- アナリティクスおよびログを定期的にレビューし、予期しないデータ(機密フィールド、クエリパラメータ、ヘッダー、ペイロード)が含まれていないか確認する。
フェーズ13: テスト、DAST、およびランタイム検証
トラフィック傍受テスト
- TLS検証が以下を確実に拒否することを検証する:
- 無効な証明書
- 期限切れの証明書
- 自己署名証明書(明示的に信頼されている場合を除く)
- ホスト名の不一致
- 証明書ピンニング(有効化されている場合)がMITM傍受ツールをブロックすることを確認する。
ランタイム検査
- テスト中に以下を検査し、シークレット/PIIが含まれていないか確認する:
- アプリストレージ(
SharedPreferences、ファイル、DB) - WebViewストレージ
- ログ
- セキュアストレージおよび暗号化が正しく適用されていることを確認する。
- ログアウト時/アカウント削除時のデータクリーンアップが有効に機能していることを確認する。
API悪用テスト
- Flutterアプリが使用するすべてのAPIに対して以下を検証する:
- レート制限(Rate Limits)
- リプレイ攻撃防止
- ページネーション境界
- 入力バリデーション
- サーバーのエラーメッセージが過度に詳細でなく、実装の詳細を漏洩していないことを確認する。
動的解析およびDAST
- Flutterの主要なフロー(認証、決済、プロフィール、設定)を実行しながら、モバイルDASTツールを実行する。
- DASTを活用して以下を浮き彫りにする:
- トランスポート層の問題
- ストレージの問題
- WebViewの問題
- トリアージ用にエビデンスをエクスポートする。
- 指摘事項を修正し、ビルドを昇格(プロモート)する前に再テストを実施する。
フェーズ14: SBOM、およびエビデンス
SBOMと依存関係
- 以下についてSBOMを生成・アーカイブする:
- Flutter(
pubspec.lock) - ネイティブモジュール(Gradle/CocoaPods)
- 使用しているパッケージセットのCVEおよびセキュリティアドバイザリを経時的に追跡する。
- リリース準備の一環として依存関係の更新スケジュールを組む。
エビデンスバンドル
- すべてのリリースレコードに以下を添付する:
- SAST/SCA/DASTレポート
- SBOM
- ビルド設定(難読化、ピンニング、ATS/networkSecurityConfig)
- テストレポート(自動および手動)
フェーズ15: レポーティングと標準規格への準拠
レポーティング
- Flutterアプリ向けに以下を作成する:
- エグゼクティブサマリー
- スコープと方法論
- PoCおよびスクリーンショットを含む詳細な検出結果
- リスクレーティング
- 改善計画(Remediation Plans)
- 再テスト結果
OWASP MASVS マッピング
- PLATFORM: フェーズ 2, 7–8, 11。
- STORAGE: フェーズ 3–4。
- CRYPTO: フェーズ 10。
- AUTH: フェーズ 6。
- NETWORK: フェーズ 5。
- CODE: フェーズ 2, 8, 12。
- RESILIENCE: フェーズ 9。
実務担当者向けクイックリファレンス
- ビルド:
--obfuscate --split-debug-info、R8minifyEnabled true、shrinkResources true。出荷アーティファクトにデバッグシンボルを含めないこと。 - ネットワーク:HTTPSのみ。重要なAPIには証明書/SPKIピンニング(バックアップあり)を適用。MITMが失敗することを検証すること。
- ストレージ:トークンにはセキュアストレージを使用。機密データには暗号化DB/ファイルを適用。ログアウト時に消去。機密バックアップを防止すること。
- 認証:OIDC + PKCE。短寿命トークン。リフレッシュトークンはセキュアストレージに保存。サーバー側認可。IDOR/BOLAをテストすること。
- WebView:認証にはシステムブラウザを優先。WebViewを使用する場合はJSをデフォルト無効、ナビゲーション許可リストを適用、機密フロー後にデータを消去すること。
- プライバシー:プラットフォームフラグでバックグラウンドスナップショットをマスキング。クリップボードへのシークレット配置を回避。明確なUXを伴う最小権限を要求すること。
- CIゲート:High/CriticalのSAST/SCAでブロック。DASTで「Highなし」を要求。リリース向けにアナライザー/lintエラーゼロを強制すること。