-
-
Notifications
You must be signed in to change notification settings - Fork 2
テレメトリを本番でオプトイン可能にする場合のリスク評価をドキュメント化 #6982
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
TinyKitten
merged 12 commits into
dev
from
claude/telemetry-production-optin-risk-a8oj1i
Sep 15, 2026
+186
−0
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
5d55e14
テレメトリを本番でオプトイン可能にする場合のリスク評価をドキュメント化
claude fa39749
テレメトリ本番オプトインのリスク評価にレビュー指摘を反映
claude be4b6c3
コンソールテレメトリの送信先が基盤に存在しない事実をリスク評価へ追記
claude 8e2df1e
リスク評価の未確定な記述を実装とポリシー原文から確定した内容へ置き換え
claude 3e7c3ae
リスク評価から裏付けの無い記述を外し、ビルド設定から確認できる事実に揃える
claude 4bde5de
コンソール経路の記述をルータ定義から言える範囲に限定
claude 182ab1e
公開物に書いてよい根拠を定めるPublishing gateを自動化エージェント向け手引きに追加
claude 282c9f8
スロットルが位置送信のみに効くことをリスク評価に明記
claude 77d1629
公開済みの記述の訂正は自己判断ではなく報告と指示を経る規則へ修正
claude 050fb21
公開済みの記述を訂正する際の承認対象をpush以外の公開操作にも広げる
claude 755e535
訂正の速さを一律に求める記述を削除
claude 10788b4
AGENTS.mdの変更を別PRへ分離するためこのブランチから取り除く
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| # テレメトリの本番オプトイン: リスク評価 | ||
|
|
||
| 試験的機能として canary 限定で動いているテレメトリを、本番 (App Store / Google Play | ||
| 配布ビルド) でもオプトイン可能にする場合に何が起きるかを整理したもの。実装前の | ||
| 意思決定用メモであり、この文書の時点でコードには一切手を付けていない。 | ||
|
|
||
| 結論として、**プライバシーポリシーの改定は必須**で、かつそれだけでは足りない。 | ||
| コード側の前提を 3 点変えないと本番投入は危険。 | ||
|
|
||
| ## 現状のゲート | ||
|
|
||
| ```text | ||
| isTelemetryEnabledByBuild = isDevApp && ENABLE_EXPERIMENTAL_TELEMETRY === 'true' | ||
| ``` | ||
|
|
||
| - `src/utils/telemetryConfig.ts`: ビルド時定数。canary (dev アプリ) でのみ true | ||
| - `src/screens/AppSettings.tsx`: 試験的機能の導線自体が `isDevApp` でガードされている | ||
| - `src/screens/ExperimentalSettings.tsx`: ユーザー側トグル。既定は false (オプトイン) | ||
| - `src/components/Permitted.tsx`: MMKV (`STORAGE_KEYS.TELEMETRY_ENABLED`) から復元 | ||
|
|
||
| つまり「本番でオプトイン」は、ビルド時ゲートを外すことと、試験的機能画面の | ||
| `isDevApp` ガードを外す (あるいはトグルを通常の設定画面へ出す) ことの両方を指す。 | ||
|
|
||
| これとは別に、送信先 URL (`EXPERIMENTAL_TELEMETRY_ENDPOINT_URL`) が空なら、下に挙げる | ||
| 送信経路はいずれも早期 return する。コンソールテレメトリの場合は `console.*` の差し替え自体が | ||
| 行われない。ビルド時ゲートを外しても、本番ビルドへ URL を焼き込まない限り送信は | ||
| 起きない。 | ||
|
|
||
| ## オプトインすると実際に何が送信されるか | ||
|
|
||
| | 経路 | 中身 | 頻度 | | ||
| | --- | --- | --- | | ||
| | `sendLocation` | 緯度・経度 (生の精度)、accuracy、速度、lineId / stationId、移動状態、バッテリー残量・充電状態、端末モデル名、アプリバージョン (ビルド番号込)、OS、channel、sessionId | 最短 1 秒に 1 回 (`TELEMETRY_THROTTLE_MS = 1000`) | | ||
| | `sendLogEvent` / `sendInteractionEvent` | アプリ内ログ、`app_launch` などの操作イベント | 随時 | | ||
| | `useConsoleTelemetry` | `console.log` / `debug` / `warn` / `error` の全出力 | 5 秒ごとにフラッシュ | | ||
|
|
||
| 送信先はいずれも `EXPERIMENTAL_TELEMETRY_ENDPOINT_URL`。GraphQL ミューテーション | ||
| (`/graphql`) と、コンソールテレメトリだけが使う REST (`/api/log`) の 2 系統がある。 | ||
|
|
||
| ただし後者には対応するルートが無い。THQ (`TrainLCD/THQ`) のルータに定義されて | ||
| いるのは `/`、`/ws`、`/healthz`、`/graphql` の 4 つで、`/api/log` は含まれない | ||
| (`src/server.rs`)。GraphQL 側には同じ役割の `sendLogEvent` があり、そちらは | ||
| `sessionId` / `appVersion` / `platform` / `channel` も受け取れる。クライアント側は | ||
| カスケードエラーを防ぐため送信結果を見ないので、この不一致はアプリ側からは | ||
| 分からない。どちらへ寄せるかの方針決めは TrainLCD/THQ#38。 | ||
|
|
||
| ### 同意文言と取得範囲のズレ | ||
|
|
||
| トグルの説明は `telemetryDescription` (`assets/translations/ja.json:351`, | ||
| `assets/translations/en.json:350`) の「お使いの端末の地理的座標を解析用サーバに | ||
| 送信します」だけ。バッテリー・端末モデル・ログ本文・操作イベントは説明されて | ||
| いない。同意を取る地点の文言が実際の取得範囲を下回っているので、ポリシーと | ||
| あわせてこの文言も直す必要がある。 | ||
|
|
||
| ## プライバシーポリシーとの矛盾 | ||
|
|
||
| 現行ポリシー (`https://trainlcd.app/privacy-policy` / 最終更新 令和 8 年 8 月 13 日) | ||
| には、本番テレメトリと正面から矛盾する記述が 2 つある。 | ||
|
|
||
| 1. **「取得した位置情報は、駅の判定のために一時的に TinyKitten の管理するサーバーへ | ||
| 送信されますが、サーバー上に保管されることはありません。」** | ||
| 本番テレメトリは位置ログを解析基盤に永続化して集計するのが目的なので、この | ||
| 一文はそのままでは事実に反する。「解析目的で送信された位置情報は、利用者が | ||
| 明示的に有効化した場合に限り N 日間保管される」という趣旨へ書き換える。 | ||
| 1. **「位置情報を、広告の配信や利用者様の行動の追跡 (トラッキング) のために | ||
| 利用することは一切ありません」** | ||
| 広告目的でないのは事実だが、1Hz の連続測位ログは移動軌跡そのもので、 | ||
| sessionId によりプロセス単位で連結できる。「やらないこと」だけを書くのでは | ||
| なく、「セッション単位の移動軌跡を解析目的で保管する。ただし個人の特定・ | ||
| 広告配信・アプリ横断の追跡には利用しない」と、やることを書いた上で否定する | ||
| 形にする。 | ||
|
|
||
| ### 新設が必要な項目 | ||
|
|
||
| 現行ポリシーに項目自体が無く、追記が要るもの。 | ||
|
|
||
| - 取得項目の追加: バッテリー残量・充電状態、端末モデル名、アプリバージョン、 | ||
| アプリ内ログ本文、操作イベント | ||
| - 保管期間の明示: 現状は「一定の期間」で数値が無い。位置ログは日数を切る | ||
| - オプトアウト導線: 「端末の設定から停止できる」だけでなく、アプリ内設定から | ||
| 個別に無効化できることを明記する | ||
| - 保管先: テレメトリ基盤 (THQ) の記載が無い。THQ は TinyKitten が管理する国内の | ||
| サーバで動き、Cloudflare 経由で公開している。保管も国内で完結し、第三者への | ||
| 提供も無いので、外国にある第三者への提供 (個人情報保護法 28 条) と、外国で | ||
| 取り扱う場合の「外的環境の把握」(安全管理措置) はどちらも論点にならない。 | ||
| 経路に入る Cloudflare は現行ポリシーが既に外部サービスとして記載している | ||
| - 開示・削除請求への応答方針: 後述のとおり、特定可能性の評価と手順の決めが要る | ||
|
|
||
| ## コード側で先に直すべき 3 点 | ||
|
|
||
| ### 1. コンソールテレメトリは本番に出さない | ||
|
|
||
| `useConsoleTelemetry` は `console.*` を横取りして送る。`babel.config.js` は | ||
| production 環境のビルドで `transform-remove-console` を有効にし、`warn` / `error` | ||
| 以外の `console.*` を除去するので、本番ビルドでこの経路に乗るのは警告とエラーに | ||
| なる。それでもサードパーティ製ライブラリのエラーや、ディープリンク URL・ユーザー | ||
| 入力を含む例外メッセージは `console.error` で出るため、何が入るかは制御できない。 | ||
| `sanitizeTelemetryMessage` の防御は正規表現 4 本 | ||
| (`src/utils/sanitizeTelemetryMessage.ts`) で、キー名が想定外の秘密情報や生の JWT は | ||
| 素通りする。 | ||
|
|
||
| 加えて `/api/log` への送信ペイロードには `channel` も `sessionId` も入っていない | ||
| ため、本番ログと canary ログが基盤側で区別できない。 | ||
|
|
||
| 前述のとおり、この経路の送信先 `/api/log` は THQ のルータに無い。受け口を用意すれば | ||
| 上記の内容がそのまま流れ込むので、本番へ出す前にコンソール経路を `sendLogEvent` へ | ||
| 寄せるか、経路ごと落とすかを決めておく。 | ||
|
|
||
| 対応は、本番では `FxConsoleTelemetry` (`src/screens/Main.tsx`) をマウントしない。 | ||
| 出すなら `warn` / `error` 限定かつ送信元を許可リストで絞る作りに変えてから。 | ||
|
|
||
| ### 2. 静的 Bearer トークンをやめる | ||
|
|
||
| `EXPERIMENTAL_TELEMETRY_TOKEN` は Babel プラグイン (`babel.config.js` の | ||
| `module:react-native-dotenv`) がビルド時に値へ置き換えるので、トークンはバンドルの | ||
| 中身そのものになる。canary は配布先が限られるため許容できていたが、本番は公開配布 | ||
| なので、アプリを入手した第三者は正規クライアントと同じ資格情報を持つことになる。 | ||
| 書き込み権限を端末ごとに絞れないため、汚染された位置ログを後から見分ける手段も | ||
| 無い。 | ||
|
|
||
| `src/lib/session.ts` に installId を短期セッショントークンへ交換する仕組み | ||
| (`/auth/token`) が既にあるので、これに乗せ替えるのが筋。 | ||
|
|
||
| ### 3. リモートキルスイッチを付ける | ||
|
|
||
| 現在のゲートはビルド時定数なので、事故ってもストア審査を通さないと止められない。 | ||
| `REMOTE_CONFIG_KEYS.AI_AGENT_ENABLED` に「障害・コスト超過時にサーバー側から機能を | ||
| 止められるようにするキルスイッチ」という先例があるので、同じ形で | ||
| `telemetry_enabled` を追加する。 | ||
|
|
||
| ## その他のリスク | ||
|
|
||
| ### ストア申告 | ||
|
|
||
| App Store のプライバシーラベルと Google Play のデータセーフティは、アプリが実際に | ||
| 収集する内容と一致している必要がある。位置情報を解析目的で収集する状態になるので、 | ||
| 申告内容を見直す。ATT が必要かどうかは「アプリ横断の追跡に使うか」「データブローカー | ||
| へ提供するか」で決まるため、そこを広げない設計を前提にする。 | ||
|
|
||
| ### 開示・削除請求への対応方針 | ||
|
|
||
| `sessionId` はプロセスごとの使い捨て UUID (`src/hooks/useTelemetrySender.ts` の | ||
| `getOrCreateSessionId`) で端末に残らない。ただし「端末に ID が残らない」ことだけを | ||
| 根拠に特定不能と決めつけない。送信されるのは連続した移動軌跡と時刻・端末モデル・ | ||
| アプリバージョンなので、保管側の他の情報と突き合わせれば特定できる場合がある。 | ||
| まず照合可能性の評価が要る。 | ||
|
|
||
| そのうえで方針を決める。GDPR 17 条の削除請求に対して 11 条 (特定を要しない取扱い) | ||
| を援用できるのは、特定できないことを管理者が示せる場合に限られ、請求者が特定に | ||
| 足る追加情報を出してきたときは同条を理由に一律で断れない。請求を受けたときの手順 | ||
| (申請内容との照合、対象データの特定、削除可否と理由の記録、特定できない場合の説明、 | ||
| 追加情報が出てきたときの再評価) を決めておく。削除に応じられる形にするなら | ||
| installId 紐づけへ変える選択肢もあるが、匿名性は下がる。基盤側の論点として | ||
| TrainLCD/THQ#37 に起票済み。 | ||
|
|
||
| ### コストと電池 | ||
|
|
||
| スロットル (`TELEMETRY_THROTTLE_MS`) が効くのは `sendLocation` だけで、最大 1 秒 1 | ||
| リクエストに制限されるものの、バッチングもリトライ制御も無い。毎秒の測位が続けば | ||
| 30 分乗車で 1 ユーザーあたり 1,800 リクエストになる。他の経路はこの制限の外にあり、 | ||
| `sendLogEvent` / `sendInteractionEvent` は呼ばれるたびに送信し、コンソール経路は | ||
| 5 秒ごとのフラッシュでキューに溜まったログ 1 件につき 1 リクエストを出す。送信は画面を見ている間に限らない。`index.js` の `TaskManager` | ||
| タスクが `handleTrackingLocation` 経由で `locationAtom` を更新し、 | ||
| `FxTelemetrySender` がそれを購読しているため、バックグラウンド測位が動いている | ||
| あいだは測位のたびに送信が続く。canary の母数では問題にならなかった数字が、本番の DAU では桁が | ||
| 変わる。本番はサンプリング (対象ユーザーの N 分の 1) か送信間隔の緩和 (5〜10 秒) | ||
| が現実的。省電力測位モードの効果を毎秒の通信で部分的に打ち消す点も検討が要る。 | ||
|
|
||
| ### 同意のスコープクリープ | ||
|
|
||
| MMKV に `'true'` が永続化されるだけなので、将来取得項目を増やしたときに古い同意が | ||
| そのまま流用される。同意バージョンを持たせ、範囲を広げるときは再同意を取る設計に | ||
| しておく。 | ||
|
|
||
| ## 推奨する出し方 | ||
|
|
||
| 段階を踏むなら、次の構成で本番へ出す。 | ||
|
|
||
| - 位置イベントのみ (コンソールテレメトリとインタラクションイベントは canary 据え置き) | ||
| - サンプリングまたは送信間隔の緩和 | ||
| - Remote Config によるキルスイッチ | ||
| - セッショントークン認証 | ||
|
|
||
| プライバシーポリシーはこの「位置イベントのみ」の範囲で書き換え、取得項目を | ||
| 増やすときに改めて改定する。 | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.