Claude CodeでPrivacy Manifestの宣言漏れを洗い出す手順
iOSアプリのPrivacyInfo.xcprivacyを、読み取り専用のサブエージェントで点検する手順です。Required Reason APIの使用箇所とSDK同梱manifestを走査し、宣言漏れを表にします。
iOSアプリの PrivacyInfo.xcprivacy の宣言漏れは、Claude Codeのサブエージェントに「コード側の使用箇所」と「SDK側のmanifest」を別々に走査させると洗い出せます。走査役にはファイルを書き換えられない権限だけを渡し、結果は表で返させます。この記事では、その走査役の定義、grepの網、報告の形式までを順に組みます。
PrivacyInfo.xcprivacyに書く内容と宣言漏れの起点
Privacy Manifestは、アプリやサードパーティSDKに同梱するプロパティリストです。ファイル名は PrivacyInfo.xcprivacy で、Xcodeでは「App Privacy File」テンプレートから作ります。記録する内容は2系統あります。
- 収集するデータの種類(全プラットフォームで記載が必要)
- 使っているRequired Reason API(iOS、iPadOS、tvOS、visionOS、watchOSで記載が必要)
洗い出しの対象になりやすいのは後者です。Required Reason APIは、端末やユーザーの識別(フィンガープリント)に使われる恐れのあるAPIの一群で、使うなら理由の宣言が要ります。宣言しないままApp Store Connectへアップロードすると、Appleから追記を促すメールが届きます。2024年5月1日以降は、理由を宣言していないアプリは受け付けられません。
Appleが理由の宣言を求めるAPIカテゴリは5つです。
| カテゴリのキー | 対象APIの例 | 理由コードの例 |
|---|---|---|
NSPrivacyAccessedAPICategoryFileTimestamp | 対象APIの例creationDate、fstat、getattrlist | 理由コードの例DDA9.1(画面表示)、C617.1(アプリのコンテナ内) |
NSPrivacyAccessedAPICategorySystemBootTime | 対象APIの例mach_absolute_time()、systemUptime | 理由コードの例35F9.1(経過時間の計測) |
NSPrivacyAccessedAPICategoryDiskSpace | 対象APIの例statfs、volumeAvailableCapacityKey | 理由コードの例85F4.1(画面表示)、E174.1(書き込み前の空き確認) |
NSPrivacyAccessedAPICategoryActiveKeyboards | 対象APIの例activeInputModes | 理由コードの例3EC4.1(カスタムキーボードアプリ) |
NSPrivacyAccessedAPICategoryUserDefaults | 対象APIの例UserDefaults | 理由コードの例CA92.1(アプリ自身の読み書き) |
getattrlist と fgetattrlist、getattrlistat は、ファイルのタイムスタンプとディスク容量の両方に載っています。1つの呼び出しが、2つのカテゴリの宣言を要求することがあります。
この一覧はAppleが随時見直す対象で、更新されることがあります。走査役のプロンプトに一覧を固定で書き込むと、いずれ古くなります。四半期ごとなど、日を決めて見直してください。
走査を3つの役割に分ける
1つの会話で「manifestを確認して」と頼むと、アプリ本体のコード、SDK、manifestファイルの出力が同じコンテキストに積み上がります。サブエージェントは自前のコンテキストで動き、メインの会話には要約だけが返ります。公式ドキュメントでも、冗長な出力が出る作業の切り出しに向くとされています。
そこで、走査を次の3役に分けます。
宣言漏れの洗い出しを3役に分ける
- 1
アプリ本体のコードを走査する
自分たちが書いたSwift、Objective-Cのソースから、5カテゴリのAPI使用箇所を拾います。結果は「ファイル、行、カテゴリ」の一覧です。
- 2
同梱manifestを棚卸しする
アプリ本体と各SDKの
PrivacyInfo.xcprivacyを探し、どこにあるか、どのカテゴリを宣言しているかを読み出します。 - 3
突き合わせて差分を出す
1と2を照らし、使っているのに宣言がないカテゴリを列挙します。宣言があるのに使用箇所が見当たらないカテゴリも、別枠で挙げます。
1と2は互いに依存しないので、並列に走らせられます。公式ドキュメントにも、独立した調査は複数のサブエージェントに同時に任せる書き方が例示されています。3だけは1と2の結果を要するため、メインの会話で行います。
読み取り専用の走査役サブエージェントを作る
走査役は読むだけでよく、ファイルを書き換える権限は要りません。tools に Read, Grep, Glob だけを書けば、その3つ以外は使えなくなります。公式ドキュメントのコードレビュー役の例と同じ構成です。
プロジェクトの .claude/agents/privacy-manifest-scanner.md に、次の内容で保存します。
---
name: privacy-manifest-scanner
description: iOSアプリのソースからRequired Reason APIの使用箇所を拾い、PrivacyInfo.xcprivacyの宣言と突き合わせる。Privacy Manifestの確認を頼まれたら使う
tools: Read, Grep, Glob
model: sonnet
---
あなたはiOSアプリのPrivacy Manifestを点検する担当です。
ファイルは読むだけで、編集しません。
## 拾う対象(Required Reason APIの5カテゴリ)
- FileTimestamp: creationDate, modificationDate, NSFileCreationDate,
NSFileModificationDate, NSURLContentModificationDateKey,
NSURLCreationDateKey, stat系, getattrlist系
- SystemBootTime: mach_absolute_time, systemUptime
- DiskSpace: statfs, statvfs, volumeAvailableCapacity系,
NSFileSystemFreeSize, NSFileSystemSize
- ActiveKeyboards: activeInputModes
- UserDefaults: UserDefaults, NSUserDefaults
## 手順
1. 指示されたディレクトリのソースをGrepで走査し、
該当箇所を「ファイル:行 / カテゴリ / 呼び出しの文脈」で並べる
2. PrivacyInfo.xcprivacyをGlobで探し、Readで
NSPrivacyAccessedAPITypesの中身を読む
3. 使用箇所があるのに宣言のないカテゴリを「宣言漏れ候補」と書く
## 守ること
- 理由コードは断定しない。使い方から候補を挙げ、根拠を1行添える
- 判断できない箇所は「要判断」と書く
- SDKの使用箇所は、そのSDKのmanifestの問題として分けて書く保存すると、Claude Codeは数秒以内に変更を検出し、次の委任から新しい定義を使います。再起動は要りません。ただし、.claude/agents/ ディレクトリを新しく作った最初の1回は、再起動が必要です。
呼び出しは自然な文でも、@-mentionでもできます。後者は、そのサブエージェントを確実に動かしたいときに使います。
@"privacy-manifest-scanner (agent)" Sources/ 以下を走査してサブエージェントが受け取るのは、この定義本文と作業ディレクトリなどの基本情報です。Claude Codeの既定のシステムプロンプトは渡りません。5カテゴリの一覧を本文に書いてあるのはそのためです。
同じ考え方は、読み取り専用の分析役をhookで縛るサブエージェントによるDB分析の記事でも使っています。権限を絞ると、走査を任せる心理的な負担も下がります。
Required Reason APIの使用箇所をgrepで拾う
サブエージェントに任せる前に、網の目をコマンドで確かめておくと、結果の読み方がぶれません。走査役はGrepツールで同じ検索をします。手元で同じ式を打てば、件数の見当を先につけられます。
# UserDefaults
grep -rnE 'UserDefaults' --include='*.swift' \
--include='*.m' --include='*.mm' Sources/
# ファイルのタイムスタンプ
grep -rnE 'creationDate|modificationDate|NSFileCreationDate|NSFileModificationDate|\b[fl]?stat\(|getattrlist' \
--include='*.swift' --include='*.m' --include='*.mm' Sources/
# 起動からの経過時間
grep -rnE 'mach_absolute_time|systemUptime' \
--include='*.swift' --include='*.m' --include='*.mm' Sources/
# ディスク容量
grep -rnE 'statfs|statvfs|volumeAvailableCapacity|NSFileSystemFreeSize|NSFileSystemSize' \
--include='*.swift' --include='*.m' --include='*.mm' Sources/
# アクティブなキーボード
grep -rn 'activeInputModes' --include='*.swift' --include='*.m' \
--include='*.mm' Sources/Sources/ は自分のプロジェクトのソースの置き場に読み替えてください。タイムスタンプの式の \b[fl]?stat\( は、stat に加えて fstat、lstat も拾います。getattrlist は部分一致なので、fgetattrlist と getattrlistat も掛かります。一方で mystat( のような名前は、単語境界で除外されます。名前が似た別の関数が混ざるときは、走査役に「呼び出しの文脈」を添えさせて、絞り込みを任せます。
落とし穴
使用箇所が見つかっても、理由コードは決まらない
grepで分かるのは「どのAPIを呼んでいるか」までです。どの理由コードを宣言できるかは、その値を何に使うかで決まります。例えば UserDefaults なら、アプリ自身のデータだけを読み書きするなら CA92.1、App Groupで共有するなら 1C8F.1 です。Appleは、宣言した理由以外の目的で、APIとそこから得た値を使うことを認めていません。走査役の報告は候補として受け取り、確定は人が行います。
SDKのmanifestは同梱状況を見る
SDKの使用箇所は、アプリ側のmanifestでは申告できません。Appleは、SDKが自分のコードでRequired Reason APIを使うなら、そのSDK自身のmanifestに書くよう定めています。リンク先のアプリや、ほかのSDKのmanifestに頼ることはできません。
SDKがmanifestを持つべき場面は、Appleが次のように整理しています。
- 「SDKs that require a privacy manifest and signature」の一覧に載っている
- 一覧外でも、Required Reason APIを使う
- ユーザーのデータを収集する、またはアプリに収集させる
- トラッキング用ドメインに接続する
2025年2月12日以降は、App Store Connectに提出するアプリに、よく使われる一部のSDKの有効なmanifestが含まれている必要があります。対象のSDK名は、Appleのその一覧で確認します。
同梱の場所は、配布形態ごとに決まっています。
| 配布形態 | manifestの位置 |
|---|---|
| iOS、iPadOS、tvOS、visionOS、watchOSのアプリ | manifestの位置バンドル直下 |
| macOS、Mac Catalystのアプリ | manifestの位置Contents/Resources/ |
| iOS系のフレームワーク | manifestの位置フレームワーク直下 |
| macOS、Mac Catalystのフレームワーク | manifestの位置Versions/A/Resources/ |
| XCFramework | manifestの位置対応する各プラットフォームの変種ごとに1つ |
Swiftパッケージは少し違います。Xcodeは PrivacyInfo.xcprivacy を既定ではリソースとして認識しません。パッケージ側で resources: [.process("PrivacyInfo.xcprivacy")] のように明示する必要があります。manifestがソースツリーにあるのに、ビルド成果物へ載っていないケースが、ここで起こります。
また、静的ライブラリ(.a)にはリソースを同梱できません。manifestを持たせるには、静的フレームワークのターゲットに作り直す必要があります。
走査役には、manifestの有無だけでなく、中身も読ませます。依頼文は次のような形です。
Pods/ とパッケージの取得先から PrivacyInfo.xcprivacy を全部探して。
SDKごとに、manifestの有無と、宣言しているカテゴリを一覧にして。
manifestが無いSDKは、ソースに5カテゴリのAPI使用箇所があるかも調べて。バイナリで配布されるSDKは、grepで使用箇所を追えません。その場合に確かめられるのは、manifestがあるか、NSPrivacyAccessedAPITypes に何が入っているか、までです。
宣言漏れの修正先をどちらにするか
候補が出たら、直す場所を決めます。コードを書いたのが誰かで、修正先が分かれます。
宣言漏れの修正先を決める
- Q使用箇所は自分たちのアプリのコードにある?答えがいいえ(SDKの中)答えがはい
SDK自身のmanifestに書かれるべき項目です。アプリ側のmanifestでは代替できないため、SDKの提供元に更新版を依頼します。更新版の有無は、SDKのリリースで確かめます。
- Qその呼び出しは、同じ値を使う目的が1つに絞れる?答えがいいえ(複数の目的がある)答えがはい
使う目的ごとに、宣言できる理由が変わります。宣言した理由と違う目的で値を使うことは認められません。目的を整理するか、複数の理由を並べて宣言します。
アプリのmanifestに追記する
NSPrivacyAccessedAPITypes の配列に、カテゴリのキーと理由コードの辞書を足します。
追記する形は次のとおりです。公式の構造に沿った、キー名だけの例です。
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>ここで書く理由コードは例です。自分のアプリの使い方に合うコードを、表や公式の一覧から選んでください。
結果を表にして理由コードを選ぶ
走査役の報告は、行の形を決めておくと突き合わせが楽になります。プロンプトに「次の列の表で返す」と書いておきます。
| 列 | 内容 |
|---|---|
| 場所 | 内容ファイル:行 またはSDK名 |
| カテゴリ | 内容5カテゴリのキー |
| 現在の宣言 | 内容manifestにある理由コード、なければ「なし」 |
| 理由の候補 | 内容使い方から見た候補と、根拠の1行 |
| 状態 | 内容漏れ候補 / 一致 / 要判断 |
「理由の候補」の列には、断定を書かせません。走査役が見られるのは呼び出しの周辺のコードだけで、アプリの機能全体は見えないからです。画面に表示する用途か、サーバーへ送る前の値かは、アプリ全体の仕様で決まります。
理由コードの選び方は、カテゴリごとに「値を何に使うか」と「端末の外に出すか」の2軸で絞れます。Appleの各理由には、端末外への送信を認めない旨が付いています。たとえばファイルのタイムスタンプを画面に表示する理由(DDA9.1)では、得た情報やそこから派生した情報を端末の外へ送れません。
最後の確認は、Xcodeでの検証に任せます。Xcodeはmanifestがターゲットのリソースに入っているときに、プライバシーレポートの生成でそれを使います。manifestの追加後は、レポートを作り直して内容を見ます。キーや値が不正なmanifestは、App Store Connectが受け付けません。外部SDK由来のものを含め、不正なファイルの名前とパスがメールで届きます。
つまずきやすい点
走査を始める前に、次の点を押さえておくと手戻りが減ります。
動的ライブラリや拡張にも、それぞれmanifestが要る
Required Reason APIを使う実行ファイルや動的ライブラリを含むバンドルは、そのバンドルにmanifestを持つ必要があります。アプリ本体のmanifestだけを確認して終えると、組み込みの動的フレームワークなどの分が残ります。走査役への依頼には、対象ディレクトリをアプリ本体とターゲットごとに分けて書いてください。
サブエージェントの数に上限がある
既定では20個のサブエージェントが同時に動いていると、新しい起動は失敗します。走査は、アプリ本体とSDKの2本に分ければ足ります。
走査役はビルドを回さない
tools に Bash を入れていないので、xcodebuild は走りません。ビルドとアーカイブ後のレポート確認は、メインの会話か手元で行います。ビルドまで任せたい場合は、Bashを足したうえで権限の許可範囲を決めます。Xcodeとの併用手順はClaude CodeとXcodeでSwiftUI iOSアプリを開発する手順にあります。
宣言漏れがゼロでも、安全とは言い切れない
grepの網に載らない書き方(別名のラッパー経由の呼び出し、動的な呼び出し)は拾えません。ゼロ件の報告は「網に掛かるものがなかった」という意味にとどまります。App Store Connectのメールと、Xcodeのプライバシーレポートで二重に確かめる運用が堅実です。
まとめ
アプリ本体のコードはgrepの走査で漏れの候補を出せます。一方、バイナリで届くSDKは、manifestの有無と中身までしか確かめられません。そのため最後に残る作業は、manifestを持たないSDKの提供元に更新版を問い合わせることです。理由コードの確定は、走査の出力ではなく、値を何に使うかを知っている人の判断になります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- code.claude.com/docs/en/sub-agents
- developer.apple.com/documentation/bundleresources/privacy-manifest-files
- developer.apple.com/documentation/bundleresources/describing-use-of-required-r…
- developer.apple.com/documentation/bundleresources/adding-a-privacy-manifest-to…
- developer.apple.com/documentation/bundleresources/app-privacy-configuration/ns…