エンジニアリング実践

拡張属性が原因のiOS署名エラーを調査する

拡張属性が原因のiOS署名エラーを調査する

ローカルではアーカイブを作成できるのに、クラウドMacへ移すと署名段階で resource fork, Finder information, or similar detritus not allowed エラーが発生することがあります。この種の問題は、証明書やプロビジョニングプロファイルの失効ではなく、画像、スクリプトの生成物、コピーされたディレクトリなどに付与されたmacOSの拡張属性が原因である場合がほとんどです。拡張属性はリソースとともにAppバンドルへ入り込み、署名ツールによる厳格な検査が実行されるまで表面化しません。

まず障害が発生しているレイヤーを特定する

「署名に失敗した」というだけで、すぐに証明書を作り直してはいけません。まず完全なビルドログから最初の codesign エラーを探し、失敗した対象がメインApp、Framework、拡張機能、リソースバンドルのどれかを記録します。ログに FinderInfoResourceFork、または detritus と明記されている場合は、ファイルのメタデータを重点的に調査します。

拡張属性は、次のような経路で付与されることがよくあります。

  • GUIファイルマネージャーからリポジトリへコピーした画像、フォント、圧縮ファイル;
  • 共有ディレクトリで展開し、そのままコミットしたリソース;
  • ビルドスクリプトが cp -R で移動した事前生成済みディレクトリ;
  • バージョン管理には含まれていないものの、作業ディレクトリに長期間残っているローカル生成物;
  • キャッシュの復元時に、ファイルのメタデータとともに書き戻された依存関係。

DerivedDataを削除して消せるのはビルド出力だけです。汚染がソースコードや外部入力に含まれている場合、次回のビルドでも同じ問題が確実に再現します。

クリーンアップする前に、失敗したコマンド、対象パス、ログを保存してください。そうしなければ、たまたまビルドが通るようになっても、本当の汚染源を特定できません。

読み取り専用スキャンの基準を作る

リポジトリの入力を検査する

xattr -lr は、指定したパスとその属性を再帰的に一覧表示します。スキャンしながら削除するのではなく、まず結果をログへ書き出します。

set -euo pipefail

ROOT="${SRCROOT:-$PWD}"
REPORT="${TMPDIR:-/tmp}/ios-xattr-source.log"

xattr -lr "$ROOT" > "$REPORT" 2>&1 || true

grep -E \
  'com\.apple\.(FinderInfo|ResourceFork)' \
  "$REPORT" || true

リポジトリが大きい場合は、.git、DerivedData、依存関係のキャッシュを除外し、実際にパッケージへ組み込まれるディレクトリを個別に検査できます。重要なのは検出件数ではなく、属性がどのファイルに付与されているか、そのファイルを何が生成したか、どのBuild Phaseを通ってバンドル内へ入ったかという3つの点を明らかにすることです。

検出された場所 優先して確認する項目 対処方針
Assetsまたはリソースディレクトリ ファイルのインポート方法、展開ツール 元ファイルを修正する
スクリプトの出力ディレクトリ cpditto の引数 ステージングディレクトリを再作成する
DerivedData キャッシュの復元、古い生成物 キャッシュを削除して再現を確認する
Framework内部 ダウンロードと展開の手順 再取得して検査する

アーカイブバンドルを検査する

アーカイブがすでに生成されている場合は、.app を直接スキャンすることで、汚染が成果物へ混入したかどうかを確認できます。

ARCHIVE_PATH="$PWD/build/App.xcarchive"
APP_PATH="$ARCHIVE_PATH/Products/Applications/App.app"
REPORT="$PWD/build/xattr-app.log"

test -d "$APP_PATH"
xattr -lr "$APP_PATH" > "$REPORT" 2>&1 || true

if grep -Eq 'com\.apple\.(FinderInfo|ResourceFork)' "$REPORT"; then
  echo "forbidden extended attributes found"
  exit 1
fi

ここでは固定のサンプルパスを使用しています。実際のパイプラインでは、アーカイブディレクトリから唯一の .app を特定し、候補が見つからない場合や複数見つかった場合は直ちに失敗させる必要があります。これにより、誤ったバンドルを検査する事態を防げます。

追跡可能性を維持してクリーンアップする

最初からワークスペース全体に xattr -cr を実行することは推奨しません。すべての拡張属性が削除されるため、変更範囲が広がるだけでなく、ダウンロード、展開、キャッシュの各工程にある問題を隠してしまう可能性があります。より安全なのは、署名を壊すことが確認されている2種類の属性だけを削除する方法です。

TARGET="$PWD/StagingPayload"

find "$TARGET" -print0 |
while IFS= read -r -d '' item; do
  xattr -d com.apple.FinderInfo "$item" 2>/dev/null || true
  xattr -d com.apple.ResourceFork "$item" 2>/dev/null || true
done

TARGET には、開発者の元のワークスペースではなく、使い捨てのステージングコピーを指定するのが理想です。クリーンアップ後に再スキャンし、まだ検出される場合はビルドを停止します。最終的な検収条件に || true を付けてはいけません。

属性が圧縮ファイルに由来する場合は、展開後の検収手順を修正します。ビルドスクリプトに由来する場合は、実行のたびに空のステージングディレクトリを作成し、明示したファイル一覧だけをコピーするようにします。処理の最後に再帰的なクリーンアップコマンドを1行追加するだけでは、汚染が残り続け、別のジョブで再発します。

検査をアーカイブ処理へ組み込む

ビルド前に入力を検査する

ビルド前のゲートでは、Appへ組み込まれるリソースディレクトリだけをスキャンすればよく、ホームディレクトリ全体を対象にする必要はありません。検出時には相対パスと属性名を出力し、ゼロ以外のステータスで終了します。これにより、時間のかかるアーカイブ処理を始める前に失敗させられ、直近の変更も特定しやすくなります。

検査スクリプトはバージョン管理に含め、実行環境を固定します。

  1. スクリプト自身の位置を基準にリポジトリのルートディレクトリを解決する;
  2. 空白を含むパスにはNULL文字区切りを使用する;
  3. レポートを今回のジョブ専用の成果物ディレクトリへ書き込む;
  4. スキャン中にファイルを変更しない;
  5. 対象ディレクトリが存在しない場合は直ちにエラーを返す。

アーカイブ後に署名を検証する

拡張属性のスキャンを通過したら、システムの署名ツールでアーカイブを検証します。

APP_PATH="$PWD/build/App.xcarchive/Products/Applications/App.app"

codesign --verify \
  --strict \
  --verbose=2 \
  "$APP_PATH"

App内に独立したFrameworkや拡張機能が含まれている場合は、それらのネストされたコードオブジェクトも列挙し、個別に検証する必要があります。メインバンドルだけを確認して、内部コンポーネントも安全だと判断してはいけません。検証ログ、アーカイブパス、ソースコードのコミットIDはまとめて保存し、「ソースコードは同じでも入力キャッシュが異なる」状況を区別できるようにします。

検収チェックリストで調査を完了する

信頼できる修正では、次の条件をすべて満たす必要があります。

  • ソースコードと外部入力のスキャン結果が保存されている;
  • 汚染されたファイルの生成経路またはコピー経路が特定されている;
  • クリーンアップの対象が、明確に指定した属性または使い捨てのステージングコピーに限定されている;
  • DerivedDataと関連キャッシュを削除した後も、アーカイブを再作成できる;
  • 最終的なAppバンドル内にFinderInfoとResourceForkが存在しない;
  • メインAppとネストされたコードオブジェクトの両方が厳格な署名検証を通過している;
  • 同じコミットを新しい作業ディレクトリで繰り返し検証しても通過する。

拡張属性の問題を調査しにくいのは、コマンドが複雑だからではありません。ソースコード、ファイルシステムのメタデータ、キャッシュ、署名という4つのレイヤーにまたがっているためです。「まず記録し、次に特定し、その後クリーンアップし、最後に検収する」という手順をゲートとして定着させれば、この種の問題を解決するために担当者がノードへログインして一時対応する必要はなくなり、作業ディレクトリを変更しても手掛かりを失わずに済みます。

よくある質問

DerivedDataを削除しても署名エラーが再発するのはなぜですか?

ソース素材、ダウンロードした入力、スクリプトでコピーするディレクトリにFinderInfoやResourceForkが残っていると、クリーンビルドでも再び取り込まれます。先に入力側を調べます。

リポジトリ全体にxattr -crを実行してもよいですか?

推奨しません。最初に該当パスを記録し、FinderInfoとResourceForkだけを削除します。全属性の再帰削除は破棄可能な作業コピー内に限定します。

クリーンアップ後のアーカイブはどう検証しますか?

Appバンドルを再走査して対象属性がないことを確認し、codesign --verify --strictを実行します。走査ログとアーカイブのチェックサムも保存します。

NowMini M4

独自の物理ノードで次のタスクを実行

M4、16GB RAM、256GB SSDを搭載したクラウドMacを、シンガポール、東京、ソウル、香港のノードからタスク期間に合わせて選択できます。

クラウドMacを今すぐレンタル