sandbox-exec
Apple Seatbeltなどと呼ばれることも。
サポート
ただし詳細なドキュメントなし。かつ、公式にはdeprecatedな機能。
いま自分が使っているmacOS Sequoia 15.5では、man 1 sandbox-execにはdeprecatedと書かれていて、man 7 sandboxには書かれていなかった。
一方でいくつかのソフトウェアで依然として利用されているという状況。
ただし、自分の理解では、これは/Applicationsに置かれるようなアプリに対する仕組みであり、CLIツールについてユーザー側がちょっとずつ権限を変えながら使えるようなものではない……はず。
ただ、App Sandboxも内部実装ではsandbox-execと似たようなことをしているようで、少なくとも設定言語については同じものを使っていそうという観察がある。
という訳で、sanbox-execも個人が開発用に利用するくらいならすぐには困らないだろう……という感じになっている。
設定方法
manに書いてある使い方は以下の通り:
sandbox-exec [-f profile-file] [-n profile-name] [-p profile-string] [-D key=value ...] command [arguments ...]
権限を設定するためのファイルを作る必要があり、この詳細が分からない。S式のような構文で書いていることは分かる。
また、以下のディレクトリに公式の設定ファイルが格納されている。
/System/Library/Sandbox/Profiles
/usr/share/sandbox
/Library/Sandbox/Profiles ← 自分の環境ではこのディレクトリは存在するが中身が無かった。
リバースエンジニアリングでは(version 1)なものについて情報がまとまっているが、実際のMacに付属しているファイルを読むと(version 3)なものもあった。詳細不明。
たとえば以下のような設定ファイルを書いて:
code:example.sb
(version 1)
(deny default)
(allow process-exec)
(allow process-fork)
(allow sysctl-read)
(allow file-read*)
(allow file-write* (subpath "/Users/nekketsuuu/.volta"))
以下のように実行する:
sandbox-exec -f example.sb node -v
実例:Claude Codeをsandbox内で実行する
rm -rf ~みたいなことをしてほしくないじゃないですか。
findの実行を無条件で許したいけど、findには-execとか-deleteとかあって怖いじゃないですか。
元々これがしたくて調べていたらsandbox-execに辿り着いた。
Claude Codeのドキュメントには「Claude Codeは開始されたフォルダとそのサブフォルダのみにアクセスでき、親ディレクトリに上がることはできません」と書かれているが、少なくとも読み取りについては出来ている上に出来ないと駄目だし、書き込みについてもやろうと思えばできてしまう。 というわけで試しに作ってみたら、できた:
どこまでリスクを許すかが悩ましい。
ファイルの読み取りについては雑に許しちゃっても大丈夫にしたいが、一部に例外がある。たとえば~/.sshに置いてある秘密鍵。秘密鍵の内容がClaudeのインプットに渡ってしまうとその時点で駄目。
ホームディレクトリの隠しディレクトリ下や~/.config内のディレクトリ下、その他個別のディレクトリ下を読み書きして動くものが多くあり、個別に設定が必要。
Node.jsのために~/.npm、~/.yarn、Rubyのために~/.bundle、~/.rbenv、Rustのために~/.cargo……など。
Tmpdir下の読み書きは許したいが、どうも$TMPDIRがプロセスごとに時々異なりえるっぽいので緩めに許している。
カレントディレクトリ下の操作は全部許したいが、一応.gitディレクトリをふっとばすことはできるので、普段の作業ディレクトリとは別の場所を使った方が良いかもしれない。
git worktreeが使えるかもしれないと思ったが、新しく作ったworktreeの中でgit commitするには.git/worktrees/ほにゃらら下のファイルだけでなくいくつか.git下のファイルにも書き込み権限が必要らしく、メンテナンスが難しそう。
.git本体がカレントディレクトリより上にあるというだけでも事故は起きづらくなっているだろうけれど。
LLMモデルを叩くAPIへの通信は許したいのでnetwork-outboundは許したく、ただnetwork-outboundで指定できる追加のフィルターは弱いので全許可にしてしまっている。
localhost:*みたいな許可の仕方か*:443みたいな許可の仕方しかできないっぽい。
不審なエンドポイントへデータを送信してしまう不安は拭えないが、許容。
/System/Library/Sandbox/Profiles下のファイルを見るともう少し賢いフィルタリングができるように見えるが、まだ試していない。
同様にnetwork-bindも全許可にしてしまっている。
Rails serverを立ち上げるときに必要だったり、rdbgがUnixソケットを使っていたり、Docker networkを作るのに欲しくなったりする。
やろうと思えばポート制限はできるが、面倒。
network-inboundも許可してしまって、危ういものは別の機構で防ぐでも良い気がする……けどここはまだ自信がない。
ポート制限をしてもよいが、結局Rails serverのためにユーザーポートを許すなら意味ない気もする。
Mairuを勝手に使われて構わないのか?、みたいな話はある。
ipc-posix*やipc-sysv*をどのように許したらよいかよく分かっていない。
セマフォや共有メモリーの利用に必要。
全部を許可すると何がどこまで出来るのか理解できていないので必要に駆られたら許可することにしている。
system.sbで一部のipc-posix-nameについては許可されている。
気軽に禁止するには便利だが、コーナーケースの考慮漏れがあっても気付きづらく、やはりちゃんとやるにはコンテナを使う方が良いかも……。
検証環境:macOS Sequoia 15.5、Claude Code 1.0.43
デバッグ方法
(deny default)にして必要なものをallowしていく方式だと、よく知らないものについても知る必要があり、うまく動かなかったときにデバッグが難しい。
幸い、sandboxの中でdenyが起こった場合にログが出ており、これを確認する方法がある。
ひとつがConsole.appを使う方法。素のままだとログが多すぎるが "sandbox" で検索した状態でログを出すとそれっぽい感じになる。 こんな感じ:
https://scrapbox.io/files/68686ba8324708a082c4d8fc.png
もうひとつがlog(1)を使う方法。
log stream --style compact --predicate 'sender=="Sandbox"'
log stream --style compact --predicate 'sender=="Sandbox" and eventMessage contains "node"'
試しているバイナリの名前が必ずしもエラーに含まれるとは限らないので注意。
こんな感じ:
code:console
% log stream --style compact --predicate 'sender=="Sandbox"'
Filtering the log data using "sender == "Sandbox""
2025-07-05 09:03:44.259 E kernel0:1986cbe (Sandbox) Sandbox: sandbox-exec(24553) deny(1) file-read-metadata /Users/nekketsuuu/.volta/bin/node 2025-07-05 09:03:44.259 E kernel0:1986cbe (Sandbox) 1 duplicate report for Sandbox: sandbox-exec(24553) deny(1) file-read-metadata /Users/nekketsuuu/.volta/bin/node 自分が試しているもの以外でsandboxを使っているバイナリのログも当然出てくるので注意。
時々denyがログに出ていないこともある気がする。
ログに出てきた足りない権限を確認し、許してよいものか調べて、足していくのが良さそう。
自分が試してみたときは/System/Library/Sandbox/Profilesなどの既存の設定ファイルに類似の許可があるかどうか調べたり、ChatGPTにコレを許可したときに起こり得る危険について説明してもらって関連キーワードを得て詳細を調べたりした。そのまま許可するのでなく条件付きで許可するのがベターな場合があったりするので念の為。
参考文献