エンジニアリング記事

クラウドMacでXcode成果物の絶対パス漏えいを監査する

クラウドMacでXcode成果物の絶対パス漏えいを監査する

アーカイブパイプラインが数か月にわたって安定稼働していても、ビルドアカウントやリポジトリのディレクトリ、さらには一時ワークスペースの名前が成果物に残ることがあります。代表的な痕跡は、/Users/runner/DerivedData、マウントボリュームのパス、ソースファイルのフルパスです。通常、これらがAppの動作を直接変えることはありませんが、インストールパッケージやシンボルファイル、ログを入手した相手に内部構造を明かす可能性があります。クラウドMacは異なるジョブで繰り返し利用されるため、パス検査は一度きりの文字列検索ではなく、リリース前の定常工程に組み込むべきです。

検査対象となる成果物の境界を明確にする

1回のXcodeアーカイブでも、少なくとも3種類の対象を区別する必要があります。配布予定の.app、チーム内に保管する.dSYM、そしてパイプラインログです。これらのリスクを一括して判断してはいけません。

配布対象のAppに実在するユーザーディレクトリが含まれている場合は、優先度の高い問題として扱います。dSYMにソースパスが記録されるのはデバッグ情報の一部であるため、パスが見つかったという理由だけで削除すべきではありません。本当の問題は、dSYMが誤って公開されることや、外部へ伝えるべきでないジョブ識別子がパスに含まれていることです。ログの露出範囲は読み取り権限と保存期間によって異なりますが、一時的な認証情報ディレクトリや個人のユーザー名を長期間残すべきではありません。

対象 代表的なパスの発生源 推奨する対応
App実行ファイル アサーション、デバッグ文字列、生成コード 実在するローカルパスが見つかった場合はブロック
dSYM DWARFのコンパイルディレクトリとソースパス プレフィックスをマッピングしてシンボリケーションを再検証
ビルドログ コマンドのエコー、スクリプト、ツール出力 機密情報を除去し、保存期間を制限
テスト添付ファイル スクリーンショット、診断パッケージ、結果パッケージ 個別に検査してからアーカイブ

パス監査の目的は、すべてのデバッグ情報を消去することではありません。ビルドマシンの実際のディレクトリ構造を、必要のない配布境界へ持ち込まないことです。

クリーンなアーカイブからスキャンを始める

まずアーカイブ先を固定し、スキャンスクリプト自身がランダムなディレクトリを作らないようにします。Release構成はパイプラインから明示的に渡し、開発マシンで最後に選択されたSchemeの状態に依存させないでください。

set -euo pipefail

ARCHIVE="$PWD/output/App.xcarchive"

rm -rf "$ARCHIVE"
xcodebuild archive \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -archivePath "$ARCHIVE"

Appのメイン実行ファイルを特定したら、システム標準のstringsを使って重要度の高いパターンを検索します。/Users/に加えて、/Volumes/DerivedData.xcworkspace、チームが実際に使用している一時ディレクトリのプレフィックスも確認します。

APP="$ARCHIVE/Products/Applications/App.app"
EXEC=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$APP/Info.plist")
BIN="$APP/$EXEC"
REPORT="$PWD/output/app-paths.txt"

strings -a "$BIN" |
  grep -E '/Users/|/Volumes/|DerivedData|\.xcworkspace|\.xcodeproj' \
  > "$REPORT" || true

if [ -s "$REPORT" ]; then
  cat "$REPORT"
  exit 1
fi

メインプログラムが検査を通過しても、パッケージ全体がクリーンとは限りません。埋め込まれたframework、拡張機能、リソースファイルも走査する必要があります。スキャンレポートには「ファイルパス、検出内容、成果物の種類」を記録しますが、バイナリ全体や環境変数をログへ直接アップロードしてはいけません。

dSYMを個別に検査する

dSYMの検査では、一般的な文字列の件数ではなく、DW_AT_comp_dirとソースファイルの記録を重点的に確認します。まずDWARFファイルを特定し、実際のワークスペースを指している可能性がある項目を出力します。

DSYM="$ARCHIVE/dSYMs/App.app.dSYM"
DWARF="$DSYM/Contents/Resources/DWARF/App"

xcrun dwarfdump --debug-info "$DWARF" |
  grep -E 'DW_AT_(comp_dir|decl_file).*("/Users/|"/Volumes/)' \
  > "$PWD/output/dsym-paths.txt" || true

xcrun dwarfdump --uuid "$DWARF"

UUIDはアーカイブ内の実行ファイルと一致している必要があります。一致していなければ、パスを正しく処理できていても、その後クラッシュアドレスを確実に復元することはできません。

コンパイル時のプレフィックスマッピングで実ディレクトリを隠す

Swiftコンパイラでは-debug-prefix-mapを使い、ワークスペースのプレフィックスを安定した論理ディレクトリに置き換えられます。Clangでは-fdebug-prefix-mapを使用します。XcodeのRelease構成には、それぞれ次の設定を追加できます。

OTHER_SWIFT_FLAGS = $(inherited) -debug-prefix-map $(SRCROOT)=/src
OTHER_CFLAGS = $(inherited) -fdebug-prefix-map=$(SRCROOT)=/src
OTHER_CPLUSPLUSFLAGS = $(inherited) -fdebug-prefix-map=$(SRCROOT)=/src

/Users全体をそのままマッピングしないでください。範囲が広すぎるルールは外部依存関係の出所を隠す可能性があり、異なるディレクトリが意図せず同じ論理パスへ集約される原因にもなります。まず$(SRCROOT)をマッピングし、依存関係のチェックアウト先が固定されている場合は、そのディレクトリ用のルールを別途追加します。

マッピング後は必ずアーカイブを作り直してください。ビルド設定だけを変更し、古いDerivedDataを再利用してはいけません。また、一部のカスタムスクリプトはプロジェクト設定を経由せずコンパイラを直接呼び出すため、実際のコンパイルコマンドに引数が付与されていることも確認します。

誤検出を一括許可せず段階別に分類する

スキャンでは、リソース内のサンプルパス、テストフィクスチャ、サードパーティ製のデバッグテキストが頻繁に検出されます。対応方法は増え続けるグローバルな除外語リストではなく、検出位置と到達可能性に基づいて決めるべきです。

結果は次の3段階に分けることを推奨します。

  1. 配布対象のAppまたは埋め込みコンポーネントから現在のビルドマシンの実プレフィックスが見つかった場合は、直ちに失敗とします。
  2. dSYMからマッピングされていないソースプレフィックスが見つかった場合は失敗としますが、診断用にファイルを残します。
  3. 内部ログやテスト結果パッケージからパスが見つかった場合は警告を生成し、アクセス範囲を確認します。

許可リストは「ファイル + 正規表現 + 理由」の単位で厳密に指定します。たとえば、特定のテストリソースで実際に/Users/example/を表示する必要があるなら、そのリソースだけを例外にできます。/Users/全体を除外リストへ追加してはいけません。さらに、パイプラインでは現在のワークスペースパスを動的な禁止項目として扱うべきです。考えられるすべてのユーザー名を推測するよりも確実です。

スキャンスクリプトから追加情報を漏らさない

レポートでは実際のプレフィックスを$WORKSPACEに置き換え、相対パスと検出タイプだけを残せます。コマンドが失敗した場合も、環境全体を出力してはいけません。元のレポートを保存する必要がある場合は、全メンバーが読めるコンソールへ直接展開せず、アクセス制限付きのビルド添付ファイルとして保管します。

リリース前にシンボリケーションを再検証する

パスのマッピングがスキャンを通過しても、デバッグ処理系が完全に維持されているとは限りません。リリース前には少なくともApp、dSYM、UUID一覧、ビルドコミット識別子を保存し、実在する命令アドレスを1つ選んでシンボリケーションをテストします。結果には16進数のアドレスだけでなく、関数名と/src/...形式の論理ソースファイルパスが返される必要があります。

最終ゲートは4項目にまとめられます。App内に実ワークスペースのプレフィックスがないこと、dSYMのUUIDがバイナリと一致すること、マッピング後のソースパスを識別できること、ログに元のスキャン内容をそのまま展開しないことです。これにより、ディレクトリ情報の露出範囲を縮小しながら、本番障害の調査に必要な証拠を維持できます。

パス監査は、VMOrbitクラウドMacでアーカイブジョブが完了した直後に実行するのが適しています。固定のユーザー名に依存せず、ソースディレクトリの構造を変更する必要もありません。ワークスペースのルートパスをパイプラインから統一して渡せば、同じルールで一時ジョブと長期運用するビルドノードの両方を検査できます。

よくある質問

App内に/Users/があれば必ず脆弱性ですか?

必ずしも脆弱性ではありませんが、ビルド用ユーザー名やディレクトリ構成を推測される材料になります。検出場所と配布範囲を確認して判断します。

debug-prefix-mapを使えばdSYMは不要ですか?

必要です。プレフィックス変換はデバッグ情報内のパス表現を変えるだけです。dSYMを保存し、実アドレスでシンボリケーションを再確認します。

パス検出時は常にビルドを失敗させるべきですか?

配布Appの実ローカルパスは失敗対象にし、dSYMや内部ログは警告とレビューに分けると、正当なデバッグ情報による誤停止を減らせます。

専有物理ノード

ビルドタスクを専有クラウドMacで実行

VMOrbit M4またはVMOrbit M4 Proを選び、チームの所在地に合わせてノードを選択します。実際の利用可否と提供情報は、コンソールにリアルタイムで表示される内容をご確認ください。

クラウドMacを選ぶ