焼売飯店

GoとかTS、JSとか

macOSのApple Seatbelt (sandbox-exec) について調べた

先週金曜、突如OpenAIのCodex CLIというコーディングエージェントがリリースされましたが、このリポジトリを見て特に気になったのは、ツールの機能そのものよりも、以下の記述*1でした。

  • macOS 12+ – commands are wrapped with Apple Seatbelt (sandbox-exec).

    • Everything is placed in a read‑only jail except for a small set of writable roots ($PWD, $TMPDIR, ~/.codex, etc.). Outbound network is fully blocked by default – even if a child process tries to curl somewhere it will fail.

この記述によると、Codex CLIがmacOS上で起動するコマンドはApple Seatbelt (sandbox-exec) という機構でWrapされてSandbox化されるそうです。

本記事では、このApple Seatbeltについて調べた内容と、それを簡単に使うために実装したsbxというコマンドについて紹介します。

github.com

Apple Seatbeltとは?

Codex CLIの説明に当たり前のように現れた「Apple Seatbelt」ですが、これが何なのか筆者は全く知りませんでした。

ざっくり言うと、macOS組み込みの sandbox-exec というコマンドがあって、このコマンドを経由して別コマンドを実行すると、OSレベルでシステムへのアクセスをブロックできるというものです。 ブロックできる操作には、ファイルの読み書き、ネットワークアクセスなどがあります。

Apple Seatbeltそのものについての概要は、monochromeganeさんが言及していた*2以下のyuzuharaさんの記事を読んでいただくとイメージが掴みやすいと思います。

yuzuhara.hatenablog.jp

Apple Seatbeltは既に非推奨化されている

自分以外にも、Apple Seatbeltについて聞いたことが無かったという方も多いと思います。

それもそのはず、Apple Seatbeltは既に非推奨となっている機能で、代わりにApp Sandboxを使うようアナウンスされています。

それに加えて、Apple Seatbeltを使うための sandbox-exec コマンドの権限ポリシーの指定に使うSBPL (Sandbox Profile Language、あるいはSandbox Policy Language?) は仕様が公開されていません

内部的に sandbox-exec を使用しているChromiumのWikiには、「Apple-Sandbox-Guide-v1.0」と言うSBPLの仕様らしきドキュメントへのリンクが貼られていますが、なんと、これも第三者がリバースエンジニアリングしたもののようです。

https://reverse.put.as/wp-content/uploads/2011/09/Apple-Sandbox-Guide-v1.0.pdf

ということで、Apple Seatbeltは、

  • 非推奨化されている
  • そもそも仕様が公開されていない

ということから、知らなくて当然のものとわかりました。

この辺りの話は、今回自分が作ったコマンドの着想元となったlittledivyさんのsh-denoというツールに関する以下のブログにまとめられていて、ここを起点に情報を集めることができました。とても助かりました…。

Seatbelt Deno for macOS

あとは以下の記事も詳しいです。

macOS: App sandboxing via sandbox-exec · Karl Tarvas

sandbox-exec は、非推奨とはいえ、Chromium (Chrome) やBazelなどの重要なツールで使われているので、そう簡単に消えることはないと思われます。

sandbox-execコマンドの使い方

man sandbox-exec の内容を抜粋します。

SYNOPSIS
     sandbox-exec [-f profile-file] [-n profile-name] [-p profile-string] [-D key=value ...] command [arguments ...]

DESCRIPTIONS

     The options are as follows:

     -f profile-file
             Read the profile from the file named profile-file.

     -n profile-name
             Use the pre-defined profile profile-name.

     -p profile-string
             Specify the profile to be used on the command line.

     -D key=value
             Set the profile parameter key to value.

このmanの内容から、

  • プロファイルのファイルパス
  • 事前定義されたプロファイル名
  • プロファイル文字列
  • key / value形式

でプロファイルを指定できるとわかります。

事前定義されたプロファイル名

事前定義されたプロファイル名としては、先ほどの資料 (Apple-Sandbox-Guide-v1.0) によると、以下が利用可能なようです。

  • no-internet
  • no-network
  • no-write-except-temporary
  • no-write
  • pure-computation

例えば、no-internetを使うと、lsはできてpingはできないといったプロファイル設定になります。

$ sandbox-exec -n no-internet ls
sandbox-exec: The no-internet profile is deprecated and may not be secure
# lsの結果が表示される

$ sandbox-exec -n no-internet ping syum.ai
sandbox-exec: The no-internet profile is deprecated and may not be secure
PING syum.ai (172.67.150.218): 56 data bytes
ping: sendto: Operation not permitted
^C
--- syum.ai ping statistics ---
1 packets transmitted, 0 packets received, 100.0% packet loss

このとき出力されるメッセージを見たところ、sandbox-exec コマンド自体だけではなく、個々のプロファイルも非推奨化されているようです。

プロファイルの書式

プロファイルの書式については、macOSに初めから同梱されているプロファイルの内容を読むとわかりやすいです。自分の使っているmacOS Sequoia 15.3.2では以下のディレクトリに配置されていました。

  • /System/Library/Sandbox/Profiles
  • /usr/share/sandbox

上記ディレクトリを見るとわかりますが、結構な量が格納されています。

macOSに同梱されているプロファイルを引用する代わりに、筆者の作成したsandbox-execのWrapperコマンドであるsbxが動的に生成するプロファイル内容を示します。

sbxでは、以下のように、明示的に許可 / 拒否したい権限をDenoのフラグに似た形で指定します。 デフォルトが全て拒否の設定のため、基本的に許可のフラグしか指定しません。

$ sbx --allow-network='*:443' --allow-file-read='/opt/local' curl https://syum.ai/ascii

curlの実行のために、 443 ポートへのリクエストと、証明書ファイルが格納されている /opt/local への読み取り処理を許可するフラグ指定となっています。 この時、動的に生成されるプロファイルは以下の内容です。

(version 1)
(import "system.sb")
(deny default)
(allow file-read*
        (subpath "/opt/local/lib")
        (subpath "/usr/lib")
        (subpath "/usr/local/lib")
        )
(allow network* (local unix-socket))
(allow network* (remote ip "*:443"))
(allow process-exec (literal "/opt/local/bin/curl"))

sbxでは、この内容のプロファイルを -p フラグで直接 sandbox-exec に渡して利用しています(この方法は、sh-denoに倣いました)。

プロファイル冒頭の記述を見るとわかりますが、SBPLにはバージョンがあります。macOS同梱のSBPLには version 3 と記載されているものもありましたが、Apple-Sandbox-Guide-v1.0では version 1 の解説しか行われていないので、sbxではそこまでしか使っていません。

また、別のSBPLファイルのインポートも可能です。 sbxでは、 /System/Library/Sandbox/Profiles 内にある "system.sb" を暗黙的に利用します。このプロファイルには、多くのコマンドを実行するのに必要な、無害と考えられるルールが含まれているようです。

"system.sb" には、例えば以下のようなものが含まれています。

(allow file-read* file-test-existence file-write-data
       (literal "/dev/null")
       (literal "/dev/zero"))

確かに、 /dev/null などへの読み取りや、存在確認、データ書き込みは問題ないといえるでしょう。

(deny default) は、あらゆる操作をデフォルトで拒否します。sbxでは、 --allow-all フラグを使うことで、あらゆる操作をデフォルトで許可することもできます。

既に deny が登場しましたが、これは、ある操作の前に指定することで、その対象の操作を拒否する設定となります。その逆に allow があります。基本的な使い方としては、まず (deny default) であらゆる操作をデフォルトで拒否し、特別に許可したい操作を (allow {operation}) で指定していく形となります。

各操作には、その対象を絞り込むフィルターが存在します。例えば、ここに書かれている (subpath "/opt/local/lib") は、指定したディレクトリのサブパスをすべて含むフィルターとなります。 ここで暗黙的に許可しているのは、動的ライブラリへのパスです。これをいちいち指定しないとコマンドが実行できないとなると、流石に不便かと思い、含めるようにしました。

権限のデバッグ

権限の指定が不足していた時、コマンドの実行に失敗してエラーメッセージが出力されます。 例えば、lsでは以下のようになります。

$ sbx ls .
ls: .: Operation not permitted

lsの処理は、ファイルの読み取りを許可すれば成功します。

$ sbx --allow-file-read . ls .

ここで浮かぶ疑問が、「特定のコマンドの実行に必要な権限はどのように検出すればよいか?」というものです。

Apple-Sandbox-Guide-v1.0 を読むと、trace という特別な操作を含んだプロファイルを使うことで、不足していた権限をSBPLファイルに書き出せるということでした。

(version 1)
(debug all)
(import "bsd.sb")
(trace "trace.sb") ; trace.sbに書き出す
(deny default)

しかしながら、残念ながらこの機能はどうやら2017年にリリースされたmacOS High Sierraから動作していないようです。

もっと前から動いていないかもしれませんが、自分が見つけられた限りではこの言及しかありませんでした。 このtrace機能を使うことができれば、コマンドの実行失敗時に、指定が必要なフラグを表示する機能をsbxに入れられたので、非常に残念です。

sbxの紹介

既に本文中で言及してしまいましたが、筆者の作成したsbxというコマンドを使うと、 sandbox-exec のプロファイルをフラグ形式で簡単に指定できます。

GitHub - syumai/sbx: an easy-to-use command-line tool for running commands with macOS sandbox-exec policies using flag-based interface

実行イメージは以下の通りです。実行対象のコマンドのフラグを使う場合は、 -- を間に挟んでください。 

$ sbx --allow-file-read . ls .
$ sbx --allow-file-read='.' ls .
$ sbx --allow-file-read='.' -- ls -l .
$ sbx --allow-network='localhost:8080' curl http://localhost:8080

sbxは、 go install github.com/syumai/sbx/cmd/sbx@latest でのインストール、またはReleasesページからのダウンロードで利用可能です。

直近のユースケースで言うと、「信頼できないMCPサーバーの実行」がこのコマンドにぴったりハマると思いますので、よかったら試してみてください(恐らくちゃんと動くと思いますが、問題があったらすみません)

もし足りないフラグがあれば、追加するPRをお待ちしています!