役に立つ層。限界も分かっている
ai-jailは、信頼できるエージェントの誤りを止め、被害をプロジェクトの中にとどめるために作られています。影響範囲はプロジェクトディレクトリで、ホームや鍵、ネットワークはその外にあります。カーネルをホストと共有するプロセスサンドボックスなので、悪意のあるコードや本気の攻撃者に対する境界ではありませんし、使い捨ての仮想マシンの代わりにもなりません。
承認にサインする人へ、その次にセキュリティエンジニアへ。
何を止めるために作られたか
信頼できるエージェントでも、コマンドを打ち間違えたり、プロジェクトルートの上まで歩くスクリプトを実行したり、Webページやパッケージにそそのかされたりします。そのとき手を伸ばす先が次のもので、どれも見つかりません。
↔ 横にスワイプすると図全体を見られます
手が届かない
- ホームディレクトリと、その中の認証情報。エージェントには新しい空のホームが渡されるので、
~/.sshも~/.awsも中には存在しません。 - シェルにある秘密情報。環境変数は小さな許可リストに絞られるので、exportしたトークンは外に残ります。唯一の例外は起動したハーネス自身のAPIキー、たとえばClaudeの
ANTHROPIC_API_KEYで、ログイン情報と一緒にデフォルトでコピーされます。--no-agent-stateで止められます。 - ネットワーク。閉じていますが、フィルタ付きプロキシを通る例外が2つあります。ツールチェーンが有効なとき(Linuxのデフォルト)の短いパッケージレジストリ一覧と、既知のエージェント自身のAPIホスト(両プラットフォーム)です。テレメトリのホストは閉じたままです。
--no-networkは両方を閉じます。 - ホストの共有メモリ、ディスプレイサーバー、クリップボード。エージェントの出力はフィルターを通り、クリップボード操作やターミナルへの問い合わせのシーケンスはそこで落とされます。
- ターミナルへの入力。Linuxでは、キー入力を偽装する
TIOCSTIの呼び出しをブロックします。macOSでは、ターミナルの制御を、ai-jailがその実行用に作ったターミナル1つに限定します。 - ジェイル自身のルール。リポジトリ内の
.ai-jailファイルは信頼されず、サンドボックスを厳しくはできても、緩めることはできません。
壁が終わるところ
ジェイルとあなたのシステムは、同じカーネルの上に立っています。そのカーネルやドライバ、サンドボックスのバックエンドにバグがあれば、壁を回り込む道になり、ai-jailには打つ手がありません。仮想マシンは自前のカーネルを持ちます。境界としてより強いのはそのためです。
↔ 横にスワイプすると図全体を見られます
あなたのデータにとっての意味
- プロジェクト内の秘密情報。リポジトリ内の
.envファイルは、マスクしない限り読めます。ファイルをマスクする方法を見る。 - マスクの対象は、サンドボックスの起動時に存在するパスだけです。セッション中に後から作られた秘密情報のファイルは隠されません。
- 許可したホストは丸ごと信頼されます。
--allow-hostでgithub.comを許可すると、エージェントはそこにある、あなたのもの以外のリポジトリにもpushできます。 --networkはネットワーク全体を開けるので、エージェントが読めるものは、プロジェクトも含めて何でも外へ送れます。- ai-jailはエージェント自身のログイン情報をデフォルトで読み書き可能でマウントするので、騙されたエージェントは起動中ずっとそれを使えます。プロジェクトはこれを利便性とのトレードオフとして明記しています。
--no-agent-stateを付けるとログアウト状態での実行になります。 - ホスト側のgitは、書き込み可能なプロジェクトの
.git/を信頼します。.git/configに書かれたプログラム(core.fsmonitorやcore.pagerなど)や.git/hooks/に置かれたプログラムは、次にホストで該当するgitコマンドが動いたとき、サンドボックスの外であなたとして実行されます。core.fsmonitorなら素のgit statusで十分で、エディタがバックグラウンドで実行するものでも起き、git diffには何も出ません。--lockdownはプロジェクトを読み取り専用にしてこれを閉じ、--map .gitは通常の実行でもリポジトリを読み取り専用に保ちますが、中からコミットはできなくなります。様子のおかしかったセッションのあとは、ホストでgitを動かす前に、普通のファイル操作ツールで.git/configを読み、.git/hooksの中身を確かめてください。git config --list自体が設定されたページャーを起動します。 --maskと--deny-pathが変えるのは、サンドボックスから見えるものだけです。サンドボックスの外でエージェントの代わりに動くものは本物のファイルを読みます。--docker下のDockerデーモン、マップしたソケット経由で届くMCPサーバー、--forward-portの先のサービスがそれです。
セキュリティエンジニアへ
- カーネルとドライバの脆弱性。サンドボックス内のどのプロセスも、マシンのほかの部分と同じカーネルとやり取りします。
- ターミナルエミュレータのバグ。特に、出力フィルターを無効にする
--terminal-passthroughを使う場合です。 - サンドボックスのバックエンド自体の欠陥。Linuxではbubblewrap、Landlock、seccomp、macOSでは
sandbox-execです。 - カーネル自身のルールであるLandlockが抽象Unixソケットとシグナルを柵で囲むのは、カーネル6.12以降だけで、ベストエフォートです。それより古いカーネルにはファイルシステムとネットワークのルールしかないので、
--x11、--network、--systemd-userを使うと、ホストの抽象ソケットとシグナルは囲われません。 - サイドチャネルと、一部の種類のプロセス間通信。
- フィルタ付き外向き通信はTCPのみです。UDPはないので、UDPが必要なプログラムは動きませんし、UDP経由で漏れることもありません。
--allow-host使用時のDNS。Linuxではサンドボックス内の名前解決が失敗するので、DNSによる外への経路はありません。macOSではシステムのリゾルバに柵がかからないので、低帯域のDNSチャネルが残ります。Anthropicが自社のsandbox-runtimeについて記載しているのと同じ隙間です。- プロキシには、各接続のホスト、ポート、タイミングが見えます。その中を流れるものは両端の間にとどまります。フィルタ付き外向き通信の仕組み。
- ファントム認証情報は、信頼の置き場所をai-jail自身に移します。
--secretを使うと、結び付けたホストへのリクエストは、本物の鍵を入れるためにai-jailを平文で通り、出ていくときに再び暗号化されます。ほかのすべてのホストへのトンネルは不透明なままです。対象は、そのホストに向けてプロキシへ平文のHTTPを送れるクライアントだけです。 --github、--aws、--kube、--gcloud、--docker-configで渡した認証情報。読み取り専用はファイルの変更を防ぐだけで、認証情報の使用は防げません。サンドボックス内の何でも、その認証情報が有効な限り、あなたとしてそのサービスを操作できます。- macOSの
sandbox-execはAppleが非推奨にしており、Linuxの隔離と同等ではありません。macOSでの違いを見る。
壁に穴を開けるスイッチ
以下はすべて、有効にするまでは無効です。例外はデフォルトで有効な2つのスイッチ、Linuxでの--toolchainsと、全環境での--agent-stateです。ほとんどは、エージェントに実際のアクセスを与えます。設定方法は設定のページにあります。
すべてのスイッチと影響を表示
| スイッチ | サンドボックス内のものにできるようになること |
|---|---|
--network | 読めるものをすべて、どこへでも送れる。通信に制限はありません。デフォルトは既知のエージェント自身のAPIホストと、Linuxではパッケージレジストリだけを許可し、--allow-hostがより厳密な代替です。 |
--allow-host | 指定したホストとそのサブドメインだけに、組み込みのプロキシ経由のTCPで届く。指定した各ホストは丸ごと信頼されます。あなたのリストはエージェント自身のAPIホストに足されます。リストと一緒に--no-networkを渡すと、そのリストだけになります。 |
--forward-port | ホストのループバックにあるサービス1つを丸ごと使える。そのポートのMCPサーバー、エディタのソケット、データベースが受け付けるものなら何でもエージェントは要求でき、許可リストも検査も監査の記録もありません。ホストのほかのポートはすべて閉じたままです。Linuxのみで、--networkとの併用は拒否されます。 |
--toolchains | Linuxではデフォルトで有効。ツールチェーンを動かします。~/.cargo/binと~/.rustupを読み取り専用で見せ、ジェイル専用の依存キャッシュを~/.local/share/ai-jail/cache/に置いて実行をまたいで保持し、パッケージレジストリへのフィルタ付き外向き通信を許可します。ホスト自身のキャッシュは決してマウントしません。--lockdown下と--no-toolchainsで無効になります。 |
--secret | 何も増えません。そのキーの本物の値はサンドボックスに入りません。エージェントにはプレースホルダーが見え、プロキシは本物の値を、結び付けた1つのホストへの平文HTTPのリクエストにだけ差し替えます。ai-jail自身のプロセスには、そのリクエストが平文で見えます。 |
--audit-log | 何も増えません。このスイッチは何も弱めず、起動ごとに1行を、サンドボックスの外にあるモード0600のファイルに書くだけです。エージェントがそれを見ることはありません。 |
--docker | Dockerデーモンを通じて、ホストのrootとして振る舞う。 |
--kvm | カーネルの仮想化インターフェース/dev/kvmを開き、QEMU、Firecracker、Androidエミュレータをハードウェアの速度で動かします。Linuxのみ。ゲストはサンドボックスのネットワークしか使えず、/dev/net/tunは渡しません。 |
--x11 | ほかのX11ウィンドウのキー入力を記録し、スクリーンショットを撮る。 |
--systemd-user | systemdのユーザーマネージャーに、ホスト上でサービスを動かすよう依頼する。 |
--inherit-env | シェルの環境全体を、秘密情報も含めて読む。APIキーなら、--env-from-fileで代わりにファイル1つを渡せます。 |
--agent-state | デフォルトで有効。エージェントの保存済みのログイン情報、たとえばClaudeの~/.claudeやDevinの~/.local/share/devinと~/.config/devinを読み書き可能でマウントし、シェルに設定されていればそのAPIキーもコピーするので、ログイン済みで起動します。サンドボックス内のものは何でも、起動中ずっとその認証情報を使えます。--no-agent-stateなら隔離されたログアウト状態での実行になり、--lockdownでは無効です。 |
--github | GitHubであなたとして振る舞う。ghの設定ディレクトリは読み取り専用でマウントされ、有効なgithub.comのトークンはホスト側のキーリングから読まれて、その起動の間だけGH_TOKENとして転送されます。コマンドラインにもディスクにも載りません。読み取り専用が守るのはファイルで、トークンは有効な間ずっと使えます。 |
--aws | ~/.awsで、あなたとしてAWSを操作します。マウントは読み取り専用です。 |
--kube | ~/.kubeにあるすべてのクラスタを、あなたとして操作します。マウントは読み取り専用です。 |
--gcloud | ~/.config/gcloudで、あなたとしてGoogle Cloudを操作します。マウントは読み取り専用です。 |
--docker-config | ~/.docker/config.jsonにあるコンテナレジストリのログイン情報を使います。マウントは読み取り専用です。 |
--audio | 有効な間、音声を録音、再生する。 |
--no-private-home | 本物のホームディレクトリを見る。これは広い例外です。必要なパス1つだけをマップするほうが安全です。 |
迷ったら、起動を拒否する
指定より弱い状態で黙って起動するサンドボックスは、エラーで止まるサンドボックスより危険です。次の場合、ai-jailは停止します。
- 設定ファイルが存在するのに、不正であるか読めない場合。
- プロジェクトの
.ai-jailがシンボリックリンクである場合。グローバルの~/.ai-jailがシンボリックリンクなら、リンク先が、あなたが所有し、ほかの誰も書き込めず、プロジェクトの外にある通常ファイルのときだけたどります。 --lockdownの指定時に、カーネル側のファイルシステムルールであるLandlockがない、部分的にしか使えない、または適用に失敗した場合。--lockdown --no-landlockも拒否されます。--allow-tcp-portを指定した場合。このインターフェースではUDPを制限できないため、フラグは互換性のために受け付けますが、起動は失敗し、エラーは--allow-hostを案内します。--networkと--allow-host、または--browserと--allow-hostを同時に指定した場合。両者は矛盾するので、ai-jailは起動を拒否します。--forward-portと--networkを同時に指定した場合(ホストのループバックにはすでに届きます)、Linux以外、ポート0や1024未満のポート(サンドボックスの中でバインドできません)。グローバルファイルのベーステーブルにforward_portsがあると、ブラウザ起動とmacOSでの起動のたびに最初の2つに当たります。--env-from-fileのファイルが、シンボリックリンクである、グループや全員に読める、ほかの人の所有である、またはプロジェクトの中にある場合。BWRAP_BINが、root以外の誰かが差し替えられたかもしれないbubblewrapバイナリを指している場合。警告を出して無視し、信頼できるbubblewrapがなければ起動は失敗します。- オーバーレイを用意できない場合。ソースがない、宛先が別のマップと重なる、ストレージの作成に失敗する、のいずれかです。2.4からは、実ファイルをそのまま読み書きでバインドする形に落とさず、起動を中止します。
プロジェクトの検証体制
このプロジェクトは、1人のメンテナーが公開の場で運営しています。検証しているものと、していないものは次のとおりです。
- 23
- サンドボックス脱出の統合テスト。
/usrへの書き込み、~/.sshの読み取り、ptraceやbpfの呼び出しを試み、失敗することを確認します - 959
- バージョン2.9.0のソースツリーにあるユニットテスト
- 0
- 起動時のネットワークリクエスト。更新チェックは
--update-checkによるオプトインです
最近のセキュリティ修正
- 2.9.0:
--x11だけでX11を通します。以前は--displayなしでは黙って何もしませんでした。 - 2.9.0:自動保存は、信頼されない読み込みが拒否するオプトインをプロジェクトファイルに書かなくなり、保存したファイルが次の実行で警告を出すことはなくなりました。
- 2.9.0:macOSで、CodexがSeatbelt下で再び起動します。setuidのホストの
psの代わりに専用のpsシムを使います。 - 2.8.0:macOSで、seatbeltプロファイルがClaude CodeのKeychainログインを保ち、Codexの制御ソケットを許可するので、どちらもログイン済みで起動します。
- 2.8.0:ランチャーからのカーソル位置の問い合わせに仮想ターミナルが応答するので、ランチャーが止まらなくなりました。
- 2.6.2:
--lockdownは、--networkを付けても常にネットワーク名前空間を隔離します。以前は、この矛盾した組み合わせで名前空間が共有のままでした。 - 2.6.2:
--envと--env-from-fileの値は、あなたの権限で動くどのプロセスからも読めるランチャーのコマンドラインに置かれなくなりました。メモリ上のファイル経由で渡されます。 - 2.4.0:リポジトリのworktreeメタデータで、ホストの任意のディレクトリを読み書きでバインドできなくなりました。
- 2.4.0:環境変数の重複エントリで、
--secretのキーの実際の値がサンドボックスに入ることはなくなりました。 - 2.4.0:信頼されていないプロジェクトの
.ai-jailにあるブラウザコマンドで、ロックダウンが解除されることはなくなりました。 - 2.4.0:
TIOCSTIをカーネルと同じ32ビットで比較するので、上位ビットを立てた変種がseccompを通らなくなりました。 - 2.4.0:カーネル6.12以降では、Landlockが抽象Unixソケットとシグナルを囲います。
- 2.4.0:macOS:ターミナルへのアクセスを、この実行自身のターミナルだけに絞りました。
/dev/ttys*全体ではありません。 - 2.4.0:macOS:
--maskしたパスは、読み取りに加えて書き込みも拒否されます。 - 2.4.0:出力フィルタが二重のESCで崩れなくなりました。崩れると、クリップボードへの書き込みをすり抜けさせることができました。
- 2.4.0:プロキシのテスト用の抜け道は、リリースビルドから取り除かれています。
リリースの作り方
- リリースタグには署名があり、CIがリポジトリに固定された鍵のフィンガープリントと照合して1つずつ検証します。
- GitHub ActionはすべてコミットSHAで固定され、使えるactionは許可リストで制限されています。
- ビルドには、固定したRustツールチェーンと
--lockedの依存を使います。 - macOSのバイナリは署名され、Appleの公証を受けています。
- 各アーカイブにはSHA-256のチェックサムが付き、公開ジョブがそれをもう一度確認します。
リリース文書がまだ足りないとしているもの
- 署名用と公開用のシークレットはまだリポジトリレベルに保存されており、リリース用のenvironmentに移す必要があります。
- すべての
v*タグに署名を強制するrulesetは、まだありません。 - immutable releasesは有効になっていません。
- crates.ioへの公開には、まだトークンを使っています。trusted publishingへの移行を予定しています。
- メンテナーが1人なので、変更やリリースを確認する2人目のレビュアーがいません。
脆弱性を報告する
公開のissueは立てないでください。GitHubの非公開の脆弱性報告機能を使い、再現手順と影響を受けるバージョンを書いてください。修正を調整するための時間もください。
よくある質問
エージェントは脱出できますか?
ネットワークを有効にして動かしても安全ですか?
--networkでは、エージェントは読めるものを何でも送れますし、本気の攻撃者がそうするのを止めるようにai-jailは作られていません。中間にあるのが--allow-hostで、届くのは指定したホストだけになりますが、各ホストは丸ごと信頼されます。先にプロジェクト内の秘密情報をマスクしてください。マスクする方法を見る。監査は受けていますか?
エージェントを檻の中に
ai-jailは単一のバイナリで、デーモンもroot権限も要りません。いつものコマンドの前に1語足すだけです。
brew tap akitaonrails/tap && brew install ai-jailai-jail claude