セキュアモバイル Telemetry プロキシ
モバイル アプリケーションは、ユーザーが制御していないデバイスに配布されます。 React Native バンドル、Swift アプリ、Kotlin アプリ、または Flutter バイナリにコンパイルされたものはすべて、最終的には検査できます。アプリ内の Telemetry API キー、アプリに配信されるリモート構成値、またはクライアントが読み取り可能な環境ファイルを配布しないでください。
代わりに、アプリケーションが所有する認証されたエンドポイントに小さなイベントを送信します。そのエンドポイントは、固定コントラクトを検証し、信頼できるサーバー コンテキストを追加し、サーバー側のキーを使用してイベントを転送します。
モバイルプロキシの操作フロー図。アプリが自社APIへイベントを送り、APIが認証、許可イベントの検証、レート制限を行ってからTelemetryへ転送します。 モバイルアプリにはTelemetryの取り込みキーを渡しません。
このパターンが当てはまる場合
製品マイルストーン、制限されたパフォーマンス測定、同期結果、管理されたエラー カテゴリ、およびリリース品質の信号にはモバイル プロキシを使用します。例としては次のものが挙げられます。
mobile_sync_completedローカルからサーバーへの同期が最終結果に達した後。mobile_screen_ready名前付き画面の小さなセットと測定された期間に対して。mobile_purchase_flow_completedサーバーが永続的な結果を確認した後。mobile_api_request_completedサンプリングされ、分類された依存関係の結果。
これは、クラッシュのシンボル化、デバイスログ、分散トレース、厳密な監査台帳の代わりにはなりません.モバイル配信は、接続、バックグラウンド実行制限、アプリの終了、同意、クライアントのクロック品質の影響を受けます。 専門的な診断情報は専用システムに保持し、SQL分析に必要な結果だけを送信してください.
クライアント契約を定義する
クライアントは、イベント名とフィールドのバージョン付きリストから選択する必要があります。 Telemetry テーブル名を選択したり、任意のプロパティ バッグを送信したり、例外メッセージを転送したりしてはなりません。
{
"event_name": "mobile_sync_completed",
"event_version": 1,
"event_id": "0196f37e-6e83-7b75-9d04-6b8283b35f74",
"occurred_at": "2026-07-30T18:42:11.150Z",
"status": "success",
"duration_ms": 842,
"item_count": 12,
"network_type": "wifi"
}
寸法を制限してください。管理されたものを好む screen_name 生のルート上で、 error_type 例外文字列に対する列挙型と粗い network_type ネットワーク識別子を介して。アクセス トークン、デバイス広告識別子、連絡先データ、メッセージ コンテンツ、ファイル パス、クリップボード データ、自由形式の検索テキスト、または生の URL は含めないでください。
event_id 再試行重複排除をサポートします。 occurred_at はクライアントの観察を記録しますが、プロキシはサーバーが受信したタイムスタンプも追加する必要があります。デバイスの時計が間違っている可能性があるため、鮮度および取り込みの監視にはサーバーのタイムスタンプを使用してください。
アプリケーション所有のエンドポイントを実装する
次の TypeScript スケッチは境界を示しています。認証とレート制限を、API ですでに使用されているフレームワークに適応させます。
const allowedEvents = {
mobile_sync_completed: {
statuses: new Set(["success", "failed", "cancelled"]),
maximumDurationMs: 300_000,
maximumItemCount: 10_000,
},
} as const;
export async function postMobileTelemetry(request: Request) {
const actor = await authenticateApplicationRequest(request);
if (!actor) return new Response("unauthorized", { status: 401 });
await enforceRateLimit({
accountId: actor.accountId,
deviceSessionId: actor.deviceSessionId,
});
const body = await readBoundedJson(request, { maximumBytes: 4096 });
const policy = allowedEvents[body.event_name as keyof typeof allowedEvents];
if (
!policy ||
body.event_version !== 1 ||
!isUuid(body.event_id) ||
!policy.statuses.has(body.status) ||
!isIntegerInRange(body.duration_ms, 0, policy.maximumDurationMs) ||
!isIntegerInRange(body.item_count, 0, policy.maximumItemCount)
) {
return new Response("invalid event", { status: 422 });
}
await telemetry.log("mobile_sync_completed", {
event_id: body.event_id,
event_version: 1,
occurred_at: parseBoundedClientTimestamp(body.occurred_at),
received_at: new Date().toISOString(),
status: body.status,
duration_ms: body.duration_ms,
item_count: body.item_count,
network_type: normalizeNetworkType(body.network_type),
account_id: actor.accountId,
app_platform: actor.platform,
app_version: actor.appVersion,
environment: process.env.APP_ENV ?? "development",
});
return new Response(null, { status: 202 });
}
API は、Telemetry イベント名を決定します。 ID、プラットフォーム、環境、および認証コンテキストをクライアントから受け取るのではなく、信頼できるサーバーの状態から取得します。エンドポイントが複数のイベントをサポートする場合は、すべてのイベントに独立したスキーマとテスト フィクスチャを与えます。
4 つのサーバー側コントロールを適用する
認証する
API の残りの部分で使用されるものと同じ署名済みのアプリケーション セッションまたはインストール資格情報が必要です。 CORS はモバイル セキュリティ境界ではないため、カスタム ヘッダーだけでは誰がリクエストを送信したかを証明できません。
ホワイトリスト
不明なイベント名、フィールド、列挙値、大きすぎる文字列、無効な数値、将来のタイムスタンプ、および小さなサイズ制限を超える本文を拒否します。ホワイトリストはプライバシー制御とカーディナリティ制御の両方です。
レート制限
認証されたアカウントと適切なインストールまたはセッション ID によって制限されます。また、グローバルな上限を課します。クライアントが制限を超えた場合、無期限に再試行せずに通常のアプリケーション エラーを返します。
信頼できるコンテキストを追加する
プロキシは、アカウント識別子、サーバー受信時間、環境、および検証済みのアプリケーションのバージョンが利用可能な場合は、それらの値を追加する必要があります。アカウント識別子がコンテキスト内で機密性の高いものである場合は、収集する前に一貫して仮名化し、マッピングを元に戻すことができる人を文書化します。
モバイル配信動作を選択する
React Native と Flutter の場合は、認証された API リクエストをすでに担当しているプラットフォーム HTTP クライアントを使用します。ネイティブ iOS と Android の場合は、同じものを使用します URLSession またはアプリで使用される HTTP スタック。エンドポイントとイベントのコントラクトは、プラットフォーム間で同一である必要があります。
オフライン配信が重要な場合は、境界のある小さなキューを保持してください。アイテム数と経過時間の両方に上限を設け、文書化されたウィンドウを超えたイベントを破棄し、ジッターを伴う指数バックオフを使用します。分析の再試行によって製品のアクションが遅れないようにしてください。同じものを保存する event_id サーバーが重複を排除できるように、複数の試行を繰り返します。
一部の結果はサーバーから出力された方が適切です。購入、サブスクリプションの変更、アクセス ポリシーの決定、または完了したインポートは、バックエンドがコミットした後にのみ正式なものになります。クライアント アサーションを信頼する代わりに、サーバーがこれらの結果を直接発行できるようにします。
結果をクエリして検証する
1 つの合成成功、失敗、キャンセル、無効なペイロード、未認証リクエスト、レート制限バースト、オフライン再試行、および重複を送信します event_id。次に、境界のあるサンプルを検査します。
SELECT
received_at,
event_id,
app_platform,
app_version,
status,
duration_ms,
item_count
FROM mobile_sync_completed
ORDER BY received_at DESC
LIMIT 50;
ロールアウトする前に、次のことを確認してください。
- 出荷されたアプリケーション バイナリと JavaScript バンドルには、Telemetry API キーは含まれません。
- 未知のイベントとフィールドは、サイレントに転送されるのではなく拒否されます。
- 生の例外テキスト、URL、ユーザー入力、資格情報、およびデバイス識別子がありません。
- 再試行しても 1 つ維持されます
event_id、重複配信によって結果数が膨らむことはありません。 - ダッシュボードには、レートとパーセンタイルのほかにサンプル数が表示されます。
- 同意、保持、削除、およびアカウント削除の動作は、アプリケーション ポリシーに一致します。
計画すべき障害モード
イベントが個別に設計された耐久性のあるビジネス ワークフローの一部でない限り、プロキシをベストエフォート型の可観測性として扱います。 Telemetry タイムアウトは通常、モバイル製品アクションを失敗させることなくログに記録され、制限される必要があります。プロキシの拒否率、転送失敗、キューの経過時間、およびイベントの鮮度を監視して、サイレント インストルメンテーションの中断が製品使用量の低下のように見えないようにします。
「デバッグを容易にする」ために、任意のクライアント イベントを自動的に受け入れないでください。これにより、エンドポイントが監査されていないデータ収集面に変わります。新しいバージョン管理されたフィールドまたはイベントは、その目的、タイプ、基数、プライバシー分類、および削除要件を確認した後でのみ追加してください。