ハイライト
Apple のプライバシーエンジニアである Joey Tyson が、app 開発フローを planning、design、development、testing、deployment の 5 段階に分解し、それぞれに対応するプライバシー設計上のアクションとシステム API を順に解説します。
主要内容
リリース直前の 1 週間になってからプライバシーを気にし始める開発者は少なくありません。「権限ダイアログを何回出す必要があるか」「Privacy Nutrition Label をどう書くか」「サードパーティ SDK にコンプライアンス上の問題はないか」といった具合です。しかし、こうした「後付け」の姿勢では妥協を強いられます。アーキテクチャがすでに固まり、データフローが複数のコンテキストをまたいだ状態で、エンドツーエンド暗号化やオンデバイス処理を追加しようとしても、もはや大きく手を入れることはできません。本セッションで Joey Tyson が一貫して主張しているのは、プライバシーとセキュリティはローカライズと同様、後からの retrofit が効かない領域であり、コードを 1 行も書く前から設計に組み込むべきだという点です。
彼は app 開発を planning、design、development、testing、deployment の 5 つのフェーズに分け、それぞれに具体的なプライバシー上のアクションを示しています。planning フェーズでは「privacy assurance(プライバシー保証)」を成果物として書き出し、app がユーザーデータをどう扱うかを平易な言葉で明文化します。design フェーズではこの保証を、ユーザーが体感できる UI(初回起動時のサマリー、状態変化の通知、文脈に沿った選択肢)に翻訳します。development フェーズでは PhotosPicker、Location Button、CloudKit encryptedValues、PIR、AdAttributionKit といったシステム機能を活用し、保証をコードレベルに落とし込みます。testing フェーズでは unit / integration / UI test を書いて保証が壊れていないことを検証し、deployment フェーズでは Privacy Nutrition Label を記入し、archive 時に Xcode の “Generate Privacy Report” を使ってすべての privacy manifest を集約します。このフローのメリットは、すべてのエンジニアリング上の意思決定を最初に書いたプライバシー保証まで遡れる点にあります。「なぜ位置情報をサーバーへ送る必要があるのか」がコードレビューの段階で初めて問われる、といった事態を避けられます。
スピーカーが繰り返し強調しているのは、直感に反する次のポイントです。権限ダイアログを減らすこと自体がプライバシー設計だ、ということです。PhotosPicker や ContactPicker、Location Button のようなシステム標準の picker は system process が描画するため、ユーザーの選択行為そのものが暗黙的な同意を含意し、ダイアログを別途出す必要がありません。「先にアルバム全体の権限を要求してからユーザーに選ばせる」よりも、UX もプライバシーも明らかに優れています。
詳細
Planning: 4 つの柱からプライバシー保証を書き出す(03:33)
Apple 社内では、4 つの pillar を指針にプライバシー保証を記述します。data minimization、on-device processing、transparency and control、security protections の 4 本柱です。スピーカーは “Pal About” という架空の app を例に、それぞれを次のように示しています。
- データ最小化 → “We only retain aggregate usage data.”
- 強いデフォルト → “When searching for nearby places, your current location is not stored by default.”
- オンデバイス処理 → “Suggested meetup locations are only generated locally using on-device data.”
- 透明性とコントロール → “Photos you upload are only used to train generative models if you opt in to improve intelligence features.”
- セキュリティ保護 → “We cannot read messages to your friends in transit between devices.”(end-to-end encryption によりアーキテクチャ的に担保)
これらの宣言はマーケティング的な表現をエンジニアリング要件にまで落とし込むものであり、それぞれが具体的な技術選定に対応します。
Development: 権限ダイアログをシステム picker で置き換える(10:29)
PhotosPicker は system process 内で描画され、app は実際にユーザーが選択した写真だけを受け取ります。フォトライブラリ全体への権限を要求する必要はありません。
// Define the app's Photos picker
PhotosPicker(
selection: $viewModel.selection,
matching: .images,
preferredItemEncoding: .current,
photoLibrary: .shared()
) {
Text("Select Photos")
}
// Configure a half-height Photos picker
.photosPickerStyle(.inline)
.ignoresSafeArea()
.frame(height: 340)
ポイント:
selection: $viewModel.selectionでステートにバインドし、ユーザーが明示的にチェックした写真だけが app 側に渡されます。matching: .imagesでメディアタイプを限定し、picker に動画は表示されません。photoLibrary: .shared()はシステム共有のフォトライブラリハンドルを使うため、選択処理全体がシステムプロセス内で完結し、app は直接フォトライブラリにアクセスしません。.photosPickerStyle(.inline)で picker をフルスクリーンではなく現在の UI に埋め込み、.frame(height: 340)と組み合わせてハーフハイトで表示します。- フォトライブラリ権限 API を呼び出さないため、App Tracking 以外で余計な権限ダイアログが出ることはありません。
Development: Location Button による一回限りの位置情報共有(11:33)
LocationButton(LocationButton.Title.currentLocation) {
// Start updating location when user taps the button.
// Location button doesn't require the additional
// step of calling 'requestWhenInUseAuthorization()'.
manager.startUpdatingLocation()
}.foregroundColor(Color.white)
.cornerRadius(27)
.frame(width: 210, height: 54)
.padding(.bottom, 30)
ポイント:
LocationButton(LocationButton.Title.currentLocation)はシステム提供のタイトル文言を使用し、ボタンが本当にユーザーのタップで起動されたかをシステム側が検証します。これにより app が偽のタップを偽装することは防がれます。- クロージャ内で直接
manager.startUpdatingLocation()を呼び出せばよく、事前にrequestWhenInUseAuthorization()を呼ぶ必要はありません。権限ダイアログを 1 回省略できます。 - 初回タップ時にはシステムがボタンの役割を説明する確認ダイアログを 1 度だけ出し、それ以降のタップではダイアログなしに現在地が共有されます。
- 画面上部には引き続き位置情報インジケーターが表示されるため、ユーザーは位置情報が利用されていることを常に把握できます。
- スタイル(色、角丸、サイズ)はカスタマイズ可能で、app のビジュアルに合わせられます。
Development: CloudKit のフィールドレベル暗号化(13:48)
myRecord.encryptedValues["encryptedStringField"] = "Sensitive value"
let decryptedString = myRecord.encryptedValues["encryptedStringField"] as? String
ポイント:
encryptedValuesはCKRecordの辞書型プロパティで、代入時に自動で暗号化、読み出し時に自動で復号されます。鍵管理を意識する必要はありません。- 併せて、CloudKit schema 側でフィールド型を
EncryptedString、EncryptedBytesなどの暗号化バリアントに設定する必要があります。CKAssetは標準で暗号化されています。 - ユーザーが Advanced Data Protection を有効にしている場合、これらのフィールドは自動的に end-to-end encryption に格上げされ、サーバー側からは平文を読めなくなります。
- 注意点として、暗号化フィールドはインデックスをサポートしないため、
CKQueryのpredicateやsortDescriptorsに含めるとクエリが失敗します。
Development: PIR でサーバーから検索内容を隠す(14:22)
準同型暗号(homomorphic encryption)は、暗号文のまま加算・乗算を行える性質を持ちます。Private Information Retrieval(PIR)はこれをベースにしており、デバイスがクエリを暗号化したままサーバーへ送り、サーバーは準同型暗号で暗号文上に結果を計算してデバイスへ返し、デバイス側で復号します。プロセス全体を通してサーバーはクエリの内容も、ヒットしたレコードも知ることができません。Apple 自身のプロダクトでは PIR がすでに使われており、OSS 実装は apple/swift-homomorphic-encryption で公開されています。
Development: 識別性の高いデータ収集を減らす(16:05)
- Private Access Tokens は CAPTCHA の代替として使えます。デバイスが検証を通過すると匿名の token を取得でき、サーバーは token を検証するだけで身元情報を必要としません。
- DeviceCheck はデバイスに最大 2 bit のステート(例: 「新規ユーザー特典を受け取り済みかどうか」)を紐づけられます。ステート自体は Apple が保持するため、再インストールや機種変更後も保持されますが、app 側はデバイスの識別子を取得しません。
- AdAttributionKit はデバイスが署名した postback でアトリビューションを行うため、App Tracking Transparency のダイアログを出す必要がありません。
Testing: プライバシー保証をテストケースとして書く(20:50)
- Unit test でプライバシー制御に関わる個別の関数を検証します。
- Integration test ではサブシステム間のデータフロー(例: CloudKit の暗号化フィールドの読み書きが正しく動作するか)を検証します。
- UI test ではユーザーが opt-in を切り替えた際に、実際にデータフローが追従して変化することを検証します。
iOS 15.2 以降は「設定」から App Privacy Report を有効にでき、app のデータアクセス、センサー利用、ネットワーク通信を自分でレビューし、ユーザーへの説明と整合しているかを確認できます。
Deployment: Privacy Nutrition Label(22:28)
App Store Connect の App Privacy セクションで Privacy Nutrition Label を記入し、app がデバイス外に送信するデータと、その用途を申告します。Xcode で archive する際に archive のコンテキストメニューから Generate Privacy Report を選ぶと、サードパーティ SDK のものも含めたすべての privacy manifest が集約され、レポートとして出力されます。これを使えば Nutrition Label が漏れなくカバーされているかを確認できます。Nutrition Label には「あり得る」用途をすべて記載する必要があり、ユーザーが個別の収集をオフにできる場合でも記載が必要です。なお Nutrition Label は app をリリースし直すことなく随時更新できます。
重要ポイント
1. キックオフドキュメントに「プライバシー保証宣言」のセクションを追加する
なぜやる価値があるか: PRD が機能要件しか書いておらず、データの契約事項が抜けているチームは多くあります。その結果、開発の途中で「保存すべきでないデータを保存していた」と気づくことになります。最初に「ユーザーの位置情報はデフォルトでアップロードしない」と一文書いておけば、その後のアーキテクチャ判断にすべてアンカーが生まれます。
始め方: Apple の 4 つの pillar をチェックリストとして使います。データ最小化、オンデバイス処理、透明性とコントロール、セキュリティ保護の各柱について、ユーザーが読んで理解できる具体的な約束を 1〜2 件ずつ書き出します。
2. 広範な権限 API をシステム picker に置き換える
なぜやる価値があるか: 権限ダイアログ 1 つひとつが「ユーザーに拒否される可能性」というリスクポイントです。一方で PhotosPicker、ContactPicker、Location Button、UIPasteControl のようなシステムコンポーネントはダイアログ自体が不要で、UX がスムーズになり、プライバシーも向上し、App Privacy Report 上のアクセス記録も減らせます。
始め方: コードベースで PHPhotoLibrary.requestAuthorization、requestWhenInUseAuthorization、requestRecordPermission のような呼び出しを grep し、picker に置き換え可能な箇所を洗い出します。新規コードでは picker を第一選択にし、要件を満たせない場合のみ権限 API にフォールバックします。
3. プライバシー保証のための UI test を 1 本書く
なぜやる価値があるか: プライバシーの問題はレビュー段階で発覚するほどコストが跳ね上がります(最悪の場合アーキテクチャの作り直しになります)。「ユーザーが opt-in をオフにした後、特定の API が呼び出されないこと」を CI で検証する UI test を 1 本書いておけば、リグレッションを即座に検知できます。
始め方: もっとも重要なプライバシー保証を 1 つ選び(例: 「opt-in されていない場合、写真をモデル学習用にアップロードしない」)、XCUITest でユーザーがスイッチをオフにする操作をシミュレートしたうえでアップロードフローを発火させ、対応するネットワークリクエストや解析イベントが発生していないことをアサートします。
4. 自前の暗号化を CloudKit encryptedValues で置き換える
なぜやる価値があるか: 鍵管理、ローテーション、バックアップを自分で運用するのは深い落とし穴の宝庫です。CloudKit の暗号化フィールドと Advanced Data Protection を組み合わせれば、ほぼゼロコストで end-to-end encryption が手に入ります。
始め方: schema 上の機微なフィールド型を EncryptedString / EncryptedBytes に変更し、コード上では record.encryptedValues["field"] で読み書きします。CKQuery の predicate や sort で当該フィールドを使っている箇所は忘れずに削除してください。
5. archive 前に Generate Privacy Report を走らせる
なぜやる価値があるか: サードパーティ SDK の privacy manifest はしばしば更新されます。手作業でつじつまを合わせていると見落としが起きやすい一方、Xcode の Generate Privacy Report はワンクリックで全 manifest を集約し、Nutrition Label と突き合わせやすくなります。
始め方: 「archive 後に Privacy Report を確認し、Nutrition Label と照合する」をリリースチェックリストに追加します。privacy manifest を提供していないサードパーティ SDK があれば、まずはライブラリの issue で対応を促し、対応されない場合は代替の検討に入ります。
関連セッション
- Supercharge device connectivity with Wi-Fi Aware — Wi-Fi Aware を使った peer-to-peer 接続。ユーザーのネットワーク環境を露出させずに通信できる
- What’s new in UIKit — UIKit の新 API 概要。権限関連の UI コンポーネント更新を含む
- What’s new in passkeys — iOS / iPadOS / macOS / visionOS 26 における passkeys の強化、パスワードレス認証のプライバシー基盤
コメント
GitHub Issues · utterances