HexVMのエンジニアリング実践

クラウドMacにiOSプライバシーマニフェスト監査ゲートを構築する

クラウドMacにiOSプライバシーマニフェスト監査ゲートを構築する

iOSプロジェクトをクラウドMacへ移行しても、ビルドが成功しただけではプライバシー申告の完全性は保証されません。依存関係の更新によって PrivacyInfo.xcprivacy が追加されることもあれば、リソースのコピー設定に問題があり、メインプロジェクトのマニフェストが最終的なAppに含まれないこともあります。より確実なのは、プライバシーマニフェストをビルド成果物の一部として扱うことです。最初にソースを検査し、次に生成物を確認し、最後にバージョン管理された基準ファイルを使って未確認の変更をブロックします。

監査ゲートの対象範囲を定義する

実用的な監査ゲートでは、少なくとも4つの層を確認します。マニフェストをplistとして解析できること、トップレベルフィールドの型が正しいこと、すべての依存関係のマニフェストが検査対象に含まれていること、そして最終的なAppに想定どおりのマニフェストが存在することです。リポジトリのルートにある1ファイルだけを検索してはいけません。ソース形式の依存関係、ビルド済みフレームワーク、パッケージマネージャーが取得したコンポーネントは、それぞれ独自の申告を含んでいる可能性があります。

まず、クリーンなワークスペースでマニフェストのインデックスを作成します。

find . \
  -path './.git' -prune -o \
  -path './DerivedData' -prune -o \
  -name PrivacyInfo.xcprivacy -print \
  | LC_ALL=C sort > privacy-manifests.current

プロジェクトが明示的に管理するパスは、privacy-manifests.baseline に記録しておくことを推奨します。パイプラインで2つのファイルを比較し、追加、削除、パス変更があれば、まず処理を失敗させます。コードオーナーによる確認後に基準ファイルを更新してください。これにより、依存関係の更新に伴う申告の変更が気付かれないまま取り込まれることを防げます。

基準ファイルはコンプライアンスの証明ではありません。現在の変更が明示的にレビューされたことを示すだけであり、コードの実際の動作に対する判断の代わりにはなりません。

plistとトップレベル構造を検証する

plutil -lint はXMLまたはバイナリ形式のplistの破損を検出できますが、フィールドの型までは判定しません。Pythonの標準ライブラリを使えば、追加の依存関係をインストールせずに軽量な構造検査を追加できます。

import pathlib
import plistlib
import sys

allowed = {
    "NSPrivacyTracking": bool,
    "NSPrivacyTrackingDomains": list,
    "NSPrivacyCollectedDataTypes": list,
    "NSPrivacyAccessedAPITypes": list,
}

failed = False
files = sorted(pathlib.Path(".").rglob("PrivacyInfo.xcprivacy"))

if not files:
    print("No privacy manifest found")
    sys.exit(1)

for path in files:
    try:
        with path.open("rb") as stream:
            data = plistlib.load(stream)
        if not isinstance(data, dict):
            raise TypeError("Root must be a dictionary")
        for key, value in data.items():
            expected = allowed.get(key)
            if expected is None:
                raise KeyError(f"Unknown top-level key: {key}")
            if not isinstance(value, expected):
                raise TypeError(f"{key} must be {expected.__name__}")
        print(path)
    except Exception as error:
        failed = True
        print(f"{path}: {error}")

sys.exit(1 if failed else 0)

このスクリプトを Scripts/validate_privacy_manifests.py として保存し、ビルド前に実行します。組織内で定義した拡張フィールドを使用する場合でも、すべての未知のキーを一括で許可してはいけません。許可リストへ個別に追加し、それぞれの用途を記録してください。

Required Reason APIのレビュー表を作成する

構造が正しくても、申告理由まで正しいとは限りません。ファイルのタイムスタンプ、システムの起動時間、ディスク容量、環境設定などに関する呼び出しは、すべてコードレビューの対象に含める必要があります。自動スキャンは手掛かりになりますが、マクロ、ラッパー層、バイナリ依存関係によって検出漏れや誤検出が生じます。そのため、1回のテキスト検索だけで理由コードを決定してはいけません。

申告ごとに、監査可能な記録を1行ずつ管理します。

確認項目 記録する内容 失敗条件
APIカテゴリ マニフェスト内のカテゴリ識別子 カテゴリに対応するコードパスがない
使用箇所 モジュール、ファイル、担当者 呼び出し元を特定できない
使用目的 ユーザー向けの実際の機能 理由と動作が一致しない
依存関係の出所 自社コードまたは具体的なコンポーネント バイナリの出所が不明
再レビューの契機 コードまたは依存関係のバージョン変更 変更後に再確認されていない

クローズドソースのバイナリコンポーネントについては、少なくともコンポーネントのバージョン、マニフェストのハッシュ、そのコンポーネントを導入した業務機能を記録します。出所を説明できない申告を推測で補完してはいけません。依存関係の担当者へ確認してください。

最終的なAppを再検査する

ソース内に存在するファイルでも、target membershipやリソースフェーズの設定に問題があるとコピーされない場合があります。パイプラインで署名なしのReleaseビルドを実行し、実際の成果物を検査します。

rm -rf .build/privacy

xcodebuild \
  -scheme "$SCHEME" \
  -configuration Release \
  -sdk iphoneos \
  -derivedDataPath .build/privacy \
  CODE_SIGNING_ALLOWED=NO \
  build

APP_PATH="$(find .build/privacy/Build/Products -type d -name '*.app' -print -quit)"
test -n "$APP_PATH"
find "$APP_PATH" -name PrivacyInfo.xcprivacy -print | LC_ALL=C sort

メインApp、埋め込みフレームワーク、拡張機能には、プロジェクトの想定に従ってそれぞれマニフェストが存在する必要があります。DerivedDataのランダムなディレクトリをハードコードしてはいけません。また、ソース内のファイル数だけを確認するのも不十分です。複数のソースマニフェストが、ビルド中に統合、置換、または除外される可能性があります。

成果物のインベントリを保存する

成果物内にある各マニフェストの相対パスとSHA-256を、パイプラインの添付ファイルとして保存します。

find "$APP_PATH" -name PrivacyInfo.xcprivacy -print0 |
  while IFS= read -r -d '' file; do
    relative="${file#"$APP_PATH"/}"
    digest="$(shasum -a 256 "$file" | awk '{print $1}')"
    printf '%s  %s
' "$digest" "$relative"
  done | LC_ALL=C sort > privacy-artifact.sha256

審査で問題が発生した際、この記録があれば「そのビルドに実際に何が含まれていたか」を確認できます。現在のブランチだけを調べるよりも確実です。

失敗条件を日常のワークフローへ組み込む

検査は高速チェックと完全チェックの2段階に分けることを推奨します。マージリクエストでは、マニフェストの検出、plistの解析、フィールド型の検証、基準ファイルとの差分確認を先に実行します。メインブランチでは、さらにReleaseビルドとApp成果物の検査を行います。高速チェックが失敗した場合は通常、数十秒以内に問題のあるパスを特定できます。完全チェックでは、リソースフェーズや埋め込みフレームワークに関する問題を検出します。

リリース前には、以下の確認項目を維持してください。

  • マニフェストのインデックスが承認済みの基準ファイルと一致している。
  • すべてのファイルが plutil -lint と構造検証スクリプトの両方に合格している。
  • 新しいAPIカテゴリが、コード上の位置、担当者、実際の用途に関連付けられている。
  • 依存関係の更新後に生じたマニフェストの差分が、人手でレビューされている。
  • 最終的なApp、拡張機能、埋め込みフレームワーク内のマニフェストが想定と一致している。
  • 成果物のパスとハッシュが、そのビルドとともにアーカイブされている。

この監査ゲートの目的は、チームに代わってコンプライアンス判断を自動化することではありません。見落としを明確なビルド失敗として表面化させ、申告の変更をコード、依存関係、レビュー記録まで追跡できるようにすることです。

よくある質問

リポジトリにPrivacyInfo.xcprivacyがあれば十分ですか?

十分ではありません。正常に解析でき、各フィールドの型が正しく、最終的なアプリまたはフレームワークの生成物に含まれることを確認します。

Required Reason APIの理由をスクリプトだけで判定できますか?

完全には判定できません。欠落、構造エラー、未確認の変更は検出できますが、実際の呼び出し目的との一致はコード所有者が確認します。

依存関係の更新後に基準を作り直すのはなぜですか?

依存関係が独自のマニフェストを追加または変更する場合があるためです。差分を確認してから基準を更新します。

専用Apple Silicon

次のビルドには専用クラウドMacを選択

ワークロードに合わせてMac miniの機種、利用期間、リージョンを選択できます。各注文には専用の物理ノードが割り当てられ、実際の利用可否はコンソールからリアルタイムで確認できます。

クラウドMacプランを選択