Xcodeで新規に作成したプロジェクトのビルド設定には、ENABLE_USER_SCRIPT_SANDBOXING = YESが設定されている。YESのときは、Run Scriptのビルドフェーズが、サンドボックスの中で実行される。サンドボックスは、ソースのフォルダやビルドの出力先にある、フェーズに宣言していないファイルの読み書きを拒否する。

error: Sandbox: bash(12345) deny(1) file-write-create [プロジェクトのパス]/generated.txt

スクリプトが読み書きするファイルは、フェーズの「Input Files」と「Output Files」に宣言する。宣言できない場合は、ターゲットのENABLE_USER_SCRIPT_SANDBOXINGをNOにする。

実行例はXcode 27.0、macOS 27.0.1のもの。

ENABLE_USER_SCRIPT_SANDBOXINGとは

Xcodeのビルド設定では、「Build Options」の「User Script Sandboxing」が、ENABLE_USER_SCRIPT_SANDBOXINGである。Xcodeの定義では、YESにすると、宣言されていない入出力の依存を禁止するために、ユーザースクリプトをサンドボックス化する。

設定を省略したときの既定値はNOである。Xcodeのプロジェクトテンプレートは、プロジェクトのビルド設定にYESを書く。そのため、Xcodeで新規に作成したプロジェクトは、YESになる。

ビルド設定の値は、xcodebuild -showBuildSettingsで確認できる。

$ xcodebuild -project [プロジェクト名].xcodeproj -scheme [スキーム名] -showBuildSettings 2>/dev/null | grep -E "^\s+ENABLE_USER_SCRIPT_SANDBOXING ="
    ENABLE_USER_SCRIPT_SANDBOXING = YES

参考: 【xcodebuild】MACOSX_DEPLOYMENT_TARGETの優先関係を-showBuildSettingsで確認する

宣言していない入出力は拒否される

Run Scriptのフェーズに入出力を宣言しないで、ENABLE_USER_SCRIPT_SANDBOXING = YESでビルドすると、スクリプトが失敗する。次のスクリプトで確認した。

cat "$SRCROOT/data.txt" > "$DERIVED_FILE_DIR/out.txt"

入出力の宣言の有無と、ビルドの結果は次のとおりである。

ENABLE_USER_SCRIPT_SANDBOXINGInput FilesOutput Filesビルドの結果
NOなしなし成功(警告あり)
YESなしなし失敗(out.txtの書き込みを拒否)
YES$(SRCROOT)/data.txtなし失敗(out.txtの書き込みを拒否)
YESなし$(DERIVED_FILE_DIR)/out.txt失敗(data.txtの読み取りを拒否)
YES$(SRCROOT)/data.txt$(DERIVED_FILE_DIR)/out.txt成功
NO$(SRCROOT)/data.txt$(DERIVED_FILE_DIR)/out.txt成功

読み取るファイルを「Input Files」に、書き込むファイルを「Output Files」に宣言すれば、YESでも成功する。

エラーメッセージ

拒否された操作は、ビルドログにerror: Sandbox:で始まる行として出力される。

error: Sandbox: [コマンド名]([PID]) deny(1) file-read-data [プロジェクトのパス]/data.txt (in target '[ターゲット名]' from project '[プロジェクト名]')
error: Sandbox: bash([PID]) deny(1) file-write-create [プロジェクトのパス]/generated.txt (in target '[ターゲット名]' from project '[プロジェクト名]')

file-read-dataは読み取りの拒否、file-write-createは新規ファイルの書き込みの拒否である。xcodebuildの終了コードは65になる。

宣言しない場合の警告

ENABLE_USER_SCRIPT_SANDBOXINGの値によらず、Output Filesを宣言していないフェーズには、次の警告が出る。

warning: Run script build phase '[フェーズ名]' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase.

入出力を宣言したフェーズは、入力が変わらなければ再実行されない。入力ファイルを変更すると、次のビルドで再実行される。入出力を宣言しないフェーズは、ビルドのたびに実行される。毎回実行したいフェーズは、「Based on dependency analysis」のチェックを外す。

何が許可され、何が拒否されるか

Xcodeは、ビルドごとにサンドボックスのプロファイル(.sbファイル)を生成し、sandbox-execでスクリプトを実行する。プロファイルは、ビルドの中間ファイルのフォルダに生成される。

$ find [DerivedDataのパス] -name "*.sb"
[DerivedDataのパス]/Build/Intermediates.noindex/[ターゲット名].build/Debug/[ターゲット名].build/[ハッシュ値].sb

プロファイルは、全体を許可した上で、次のようなフォルダの読み書きを拒否する。

  • SRCROOT(ソースのフォルダ)
  • PROJECT_DIR
  • OBJROOT、SYMROOT、DSTROOT
  • CONFIGURATION_BUILD_DIR、CONFIGURATION_TEMP_DIR
  • DERIVED_FILE_DIR

そのうえで、宣言した入力ファイルの読み取りと、宣言した出力ファイルの読み書きを許可する。

プロファイルに含まれないフォルダは、拒否の対象ではない。ネットワークアクセスも制限されない。ENABLE_USER_SCRIPT_SANDBOXING = YESのスクリプトから、curlで外部のサイトにアクセスすると、成功した。

フォルダは入力に宣言できない

フォルダを入力に宣言しても、フォルダ内のファイルは読めない。.gitフォルダを入力に宣言したスクリプトで、git rev-parseを実行すると、.git/HEADの読み取りが拒否された。

error: Sandbox: git([PID]) deny(1) file-read-data [プロジェクトのパス]/.git/HEAD (in target '[ターゲット名]' from project '[プロジェクト名]')

リビジョンやビルド番号をgitから取得するスクリプトは、サンドボックスの中では実行できない。このようなスクリプトを含むターゲットは、ENABLE_USER_SCRIPT_SANDBOXINGをNOにする。

ターゲットだけ無効にする

プロジェクトのENABLE_USER_SCRIPT_SANDBOXINGをYESのままにして、スクリプトを含むターゲットだけをNOにできる。ターゲットのビルド設定が、プロジェクトの設定より優先される。

ENABLE_USER_SCRIPT_SANDBOXING = NO

gitを使うスクリプトを、プロジェクトがYES、ターゲットがNOの状態でビルドすると、成功した。

$ xcodebuild -project [プロジェクト名].xcodeproj -scheme [スキーム名] -showBuildSettings 2>/dev/null | grep -E "^\s+ENABLE_USER_SCRIPT_SANDBOXING ="
    ENABLE_USER_SCRIPT_SANDBOXING = NO

ターゲットの設定を変えられない場合は、xcodebuildのコマンドラインで上書きできる。コマンドラインの指定は、プロジェクトとターゲットの設定より優先される。

$ xcodebuild -project [プロジェクト名].xcodeproj -scheme [スキーム名] build ENABLE_USER_SCRIPT_SANDBOXING=NO

サンドボックスのルールが効かない場合

プロジェクトを/tmp配下に置いてビルドすると、ENABLE_USER_SCRIPT_SANDBOXING = YESでも、宣言していないファイルの読み書きが拒否されなかった。/tmpは、/private/tmpへのシンボリックリンクである。同じプロジェクトをホームフォルダ配下に置くと、拒否された。

サンドボックスの動作を確認するときは、シンボリックリンクを含まないパスにプロジェクトを置く。

どちらの方法を選ぶか

YESにしておくと、スクリプトがソースやビルド出力のフォルダにある未宣言のファイルに依存していたときに、ビルドの失敗で気づける。入出力を宣言すると、ビルドの再実行の判断も正確になる。

ファイルを列挙できないスクリプトや、gitのようにフォルダ全体を読むコマンドを使うスクリプトは、ターゲットをNOにする。YESのプロジェクトで、必要なターゲットだけをNOにすれば、他のターゲットのスクリプトは、サンドボックスの中で実行される。