Claude Media
Claude CodeでPrivacy Manifestの宣言漏れを洗い出す手順

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. 1

    アプリ本体のコードを走査する

    自分たちが書いたSwift、Objective-Cのソースから、5カテゴリのAPI使用箇所を拾います。結果は「ファイル、行、カテゴリ」の一覧です。

  2. 2

    同梱manifestを棚卸しする

    アプリ本体と各SDKの PrivacyInfo.xcprivacy を探し、どこにあるか、どのカテゴリを宣言しているかを読み出します。

  3. 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/
XCFrameworkmanifestの位置対応する各プラットフォームの変種ごとに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 に何が入っているか、までです。

宣言漏れの修正先をどちらにするか

候補が出たら、直す場所を決めます。コードを書いたのが誰かで、修正先が分かれます。

切り分け

宣言漏れの修正先を決める

  1. Q使用箇所は自分たちのアプリのコードにある?
    答えがいいえ(SDKの中)

    SDK自身のmanifestに書かれるべき項目です。アプリ側のmanifestでは代替できないため、SDKの提供元に更新版を依頼します。更新版の有無は、SDKのリリースで確かめます。

    答えがはい
  2. 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の提供元に更新版を問い合わせることです。理由コードの確定は、走査の出力ではなく、値を何に使うかを知っている人の判断になります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/sub-agents
  2. developer.apple.com/documentation/bundleresources/privacy-manifest-files
  3. developer.apple.com/documentation/bundleresources/describing-use-of-required-r…
  4. developer.apple.com/documentation/bundleresources/adding-a-privacy-manifest-to…
  5. developer.apple.com/documentation/bundleresources/app-privacy-configuration/ns…
この記事を共有:XはてブLinkedIn