メインコンテンツまでスキップ

キャッシュの取り扱いに関する方針

Status: Accepted

要約

  • 利用するライブラリによっては、取り扱うデータがキャッシュディレクトリ内にキャッシュデータとして保存される場合があります
  • 機密情報を外部ライブラリ内で取り扱う場合には、そのライブラリでどのようにキャッシュデータが保存されるかを確認します
  • テスト時には内部ストレージのキャッシュディレクトリ内に生成されているキャッシュデータを確認し、万が一の流出を許容できない機密情報が含まれていないことを確認します
  • テスト時には外部ストレージのキャッシュディレクトリ内にキャッシュデータが保存されていないことを確認します

コンテキスト

モバイルアプリでは、以下に示すようなデータが内部ストレージのアプリ専用領域へキャッシュとして保存される場合があります。

  • 外部へのHTTPリクエストやレスポンスの内容
  • 画像選択の際の一時的な画像データ

内部ストレージのアプリ専用の領域に置かれたデータは、基本的には他のアプリから読み取ることはできません。 しかし端末のroot権限を奪取されているようなケースでは、悪意のある攻撃者にデータを読み取られる可能性が残ります。 機密性の高い情報を扱うアプリでは、意図せず機密情報がキャッシュとして保存されてしまわないように注意することが望ましいです。

また、一部のライブラリでは外部ストレージにキャッシュを保存するものもあります。 外部ストレージは他のアプリからも参照・更新が可能な領域であり、秘匿すべき情報を保存してはいけません。 外部ライブラリを採用する際には、外部ストレージへのアクセスを行っていないか注意深く確認する必要があります。

この記事では、React Nativeを用いたモバイルアプリの開発時に気を付けておくべきキャッシュについて整理します。

議論

各OSのキャッシュディレクトリ

Androidの場合、内部ストレージと外部ストレージの双方に、アプリ毎に永続ファイルを保存するためのディレクトリとキャッシュデータを保存するディレクトリが用意されています。 具体的には、内部ストレージの場合は/data/data/<アプリのパッケージ名>/cache/のようなPathとなります。 また外部ストレージの場合は<外部ストレージのPath>/Android/data/<アプリのパッケージ名>/cache/のようなPathとなります。 詳細については、データストレージとファイルストレージの概要を参照してください。

iOSの場合、キャッシュデータの保存場所は内部ストレージ内に用意されています。 具体的には、<Application_Home>/Library/Caches/[Bundle Identifier]/がキャッシュディレクトリとなります。

またiOSの場合、キャッシュディレクトリとは別にtmpディレクトリも用意されています。 キャッシュディレクトリはアプリの起動中に消えても再生成が可能なデータを保存する場所で、アプリの起動中でもOSがファイルを削除する可能性があります。 tmpディレクトリはアプリの起動中に利用する一時的なデータを保存する場所で、アプリの起動中はOSによってファイルが削除されることはありません。 いずれのディレクトリに置かれたファイルも、iCloudへのバックアップ対象にはなりません。 詳細については、iOSデータストレージガイドラインを参照してください。

キャッシュディレクトリに保存されているデータは必要に応じて随時OSによって自動的に削除されます。 ただし、不要になったことが明確なキャッシュはアプリ側で随時削除することが望ましいです。

各OSのキャッシュディレクトリは、React Nativeを用いたアプリの場合、 例えばexpo-file-systemなどのライブラリを通じてファイルの作成・更新・削除などを行うことができます。

注意すべきキャッシュ

HTTPリクエスト・レスポンスのキャッシュ

一般的なアプリでは、外部のサーバに対して様々なHTTPリクエストを送信し、様々なレスポンスを受け取っています。 これらのHTTPリクエスト・レスポンスの内容は、キャッシュディレクトリ内に保存される場合があります。

Androidの場合、React Nativeアプリでfetchやaxiosなどを用いてHTTPリクエストを送信すると、デフォルトでは通信内容のキャッシュが保存されます。

キャッシュは、キャッシュディレクトリ内のhttp-cacheディレクトリ内に、末尾に'.0'や'.1'がついたランダムなIDのファイルとして保存されています。 ファイル内には以下のような形式でリクエストURLやレスポンスヘッダ、レスポンスボディなどが記録されています。

https://httpbin.org/get
GET
0
HTTP/1.1 200
8
date: Fri, 14 Jan 2022 08:57:31 GMT
content-type: application/json
content-length: 266
server: (宛先サーバのソフトウェア)
access-control-allow-origin: *
access-control-allow-credentials: true
OkHttp-Sent-Millis: 1642150651488
OkHttp-Received-Millis: 1642150651667

(中略:TLS通信に関するログ)
TLSv1.2
{
"args": {},
"headers": {
"Accept-Encoding": "gzip",
"Host": "httpbin.org",
"User-Agent": "okhttp/3.12.12",
"X-Amzn-Trace-Id": "(TraceId)"
},
"origin": "(IP Address)",
"url": "https://httpbin.org/get"
}

iOSの場合も同様に、デフォルトでは通信内容のキャッシュが保存されます。

キャッシュは、キャッシュディレクトリ内にCache.db, Cache.db-shm, Cache.db-walのようなSQLite3 Database用のファイルとして保存されます。 このキャッシュのデータベース内には、以下のような形式でリクエストURLやレスポンスヘッダ、レスポンスボディなどが記録されています。

sqlite> select * from cfurl_cache_response;
1|0|-2528053911948506688|0|https://httpbin.org/get|2022-01-14 08:05:34|
sqlite> select * from cfurl_cache_receiver_data;
1|0|{
"args": {},
"headers": {
"Accept": "*/*"
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Host": "httpbin.org",
"User-Agent": "(アプリのUserAgent)",
"X-Amzn-Trace-Id": "(TraceId)",
},
"origin": "(IP Address)",
"url": "https://httpbin.org/get"
}

Android、iOSのいずれの場合も、デフォルトではリクエストURLのクエリパラメータやレスポンスヘッダ、レスポンスボディなどがキャッシュとして記録されます。 リクエストURLのクエリパラメータやレスポンスヘッダ、レスポンスボディなどに秘匿すべき情報が含まれている場合、記録されてしまうのはあまり望ましい状態ではありません。

AndroidとiOSどちらの場合も、React Nativeのfetchやaxiosのパラメータ設定の範囲では、これらのキャッシュファイルの作成は無効化できなさそうです。 今回のアプリでは、クエリパラメータやレスポンスヘッダ、レスポンスボディに秘匿性の高い情報が含まれていないことから、デフォルトの動作のままとします。

端末内からファイルを選択する際のキャッシュ

端末内のファイルを選択してアップロードするようなユースケースにおいて、端末内からファイルを選択するUIをライブラリを用いて実現する場合があります。 こうしたファイル選択系のライブラリでは、選択されたファイルが一時的にキャッシュ領域に保存される場合があります。

例えばexpo-document-pickerライブラリの場合、 copyToCacheDirectoryのオプションがデフォルトでtrueとなっています。 そのため選択したファイルはデフォルトではキャッシュディレクトリにコピーされます。 機密情報を含むファイルを扱う場合は、アップロードなどの処理が完了次第、キャッシュファイルを明示的に削除しておくことが望ましいです。

また画像ファイルを選択してリサイズを行うようなライブラリでも、選択した画像やそれを加工した画像がキャッシュディレクトリに保存される場合があります。 こちらも同様に、処理が完了次第、キャッシュファイルを明示的に削除しておくことが望ましいです。

今回のアプリでは、秘匿性の高いファイルを端末内から選択することはないため、特に対応は実施しません。 ただし、利用するライブラリによって外部ストレージ内にキャッシュが保存されていないことをテスト時に確認します。

外部URLから取得した表示用画像ファイルのキャッシュ

React NativeのImageコンポーネントなど、画像表示用のコンポーネントの多くでは、外部URLを指定して画像を表示できます。 表示時に外部URLから取得された画像ファイルは端末内のキャッシュ領域に保存される場合があります。

React NativeのImageコンポーネントを用いて外部URLから取得した画像を表示した場合、AndroidとiOSのいずれも画像のキャッシュが保存されていました。 Androidの場合はキャッシュディレクトリ内のimage_cacheディレクトリ内に複数のディレクトリに分かれて画像ファイルが保存されます。 またiOSの場合はキャッシュディレクトリ内のfsCachedDataディレクトリ内に画像ファイルが保存されます。

インターネット上に公開されている画像ファイルであれば特に問題はないですが、 機密情報を含む画像ファイルを外部から取得して表示している場合は、キャッシュの取り扱いにも注意を払う必要があります。

今回のアプリでは、秘匿性の高い画像ファイルを表示することはないため、特に対応は実施しません。 ただし、利用するライブラリによって外部ストレージ内にキャッシュが保存されていないことをテスト時に確認します。

テスト時におけるキャッシュディレクトリ内の確認

利用しているライブラリが意図せず外部ストレージに書き込みをしていないか、全てのライブラリの仕様を事前に把握するのは現実的には難しいです。 そのため、その代わりにテスト時には、キャッシュディレクトリにどのようなデータが生成されているか確認する必要があります。

特に外部ストレージのキャッシュディレクトリは原則利用すべきでないため、何かファイルが作成されている場合は注意が必要です。 ファイルが存在している場合は何が記録されているのかを確認します。

決定

  • 機密情報を外部ライブラリ内で取り扱う場合には、そのライブラリでどのようにキャッシュデータが保存されるかを確認します
  • テスト時には内部ストレージのキャッシュディレクトリ内に生成されているキャッシュデータを確認し、万が一の流出を許容できない機密情報が含まれていないことを確認します
  • テスト時には外部ストレージのキャッシュディレクトリ内にキャッシュデータが保存されていないことを確認します