アフィリエイト広告を利用しています

Claude Code を安全に「自走」させたい人へ。cage で囲む方法を、つまずきながら書きました

Claude Code の bypassPermissions モード、使っていますか?

いちいち「このコマンド実行していいですか?」と聞かれなくなるので、作業がめちゃくちゃ快適になります。ただ、Claude が何でもできる状態になるので、正直ちょっとドキドキします。

プロジェクトと関係ないファイルを消されたら? 勝手に git push されたら? curl でデータを外部に送られたら?

「Claude Code 本体にも sandbox 機能あるやん」と思うかもしれませんが、bypassPermissions の状態だと Claude が自力で sandbox を突破するケースが報告されています。

じゃあどうするか。Claude の外側から囲んでしまえばいい。

この記事では、cage というサンドボックスツールを使って「開いているフォルダの中だけ自由に操作できる」環境を作る方法を、実際につまずいたポイントも含めてお伝えします。

なお、3層構造の防御設定をまとめた Claude Code Guard を GitHub で公開しています。セットアップスクリプト1発で導入できます。

Claude Code Guard(GitHub)

この記事は以下を参考にさせていただきました。感謝です。

202602個人的claude code設定(kawarimidoll)

3層のガードレールで守る

Claude 本体の機能に頼るのではなく、外側のツールで防御するアプローチです。

レイヤーツール役割
1cageOS レベルでファイルの書き込み先を制限
2hooksコマンド実行前に中身を検証
3settings.jsondeny リストで二重防御

この記事ではレイヤー1の cage を中心に、実際のセットアップ手順を詳しく書きます。

cage ってなに?

cage は、macOS 上でプロセスのファイル書き込み先を制限できるサンドボックスツールです。

cage claude で起動すると、Claude Code が書き込めるディレクトリをこちらで指定できます。指定していないディレクトリには書き込めません。OS レベルで制限をかけるので、Claude が突破しようとしても突破できません。

読み込みは自由にできるので、コードを読んで理解する作業には影響しません。あくまで「書き込み」だけを制限する仕組みです。

Step 1: cage をインストールする

Homebrew でインストール

brew install --cask Warashi/tap/cage

ここで最初のつまずき

元々の公式ドキュメントには --no-quarantine フラグを付けるよう書かれています。ところが Homebrew 5.0 でこのフラグが廃止されました。付けると以下のエラーが出ます。

Error: Calling the `--[no-]quarantine` switch is disabled! There is no replacement.

なので、フラグなしでインストールした後に、手動で quarantine 属性を解除します。

sudo xattr -rd com.apple.quarantine "$(brew --caskroom)/cage"

動作確認

cage --help

ヘルプが表示されればインストール完了です。

Step 2: cage の設定ファイルを作る

cage にはプリセット機能があり、「claude を起動したら自動的にこの許可設定を使う」という指定ができます。

設定ディレクトリを作成

mkdir -p "$HOME/Library/Application Support/cage"

設定ファイルを作成

nano "$HOME/Library/Application Support/cage/presets.yaml"

以下を貼り付けます。

presets:
  claude-code:
    allow:
      - "."
      - path: "/tmp"
        eval-symlinks: true
      - "$HOME/.npm"
      - "$HOME/.npm-global"
      - "$HOME/.claude"
      - "$HOME/.claude.json"
      - "$HOME/.config/claude"
      - "/dev/tty"
      - "/dev/ttys*"

auto-presets:
  - command: claude
    presets:
      - claude-code

保存して閉じます(Ctrl + XYEnter)。

各行の意味

それぞれ「ここへの書き込みを許可する」という意味です。

  • . — 今いるフォルダ(プロジェクトフォルダ)。これが核心。ここだけ自由に書き込める
  • /tmp — 一時ファイル置き場。macOS では /tmp は /private/tmp へのシンボリックリンクなので eval-symlinks: true で実体パスに変換
  • $HOME/.npm / $HOME/.npm-global — npm のキャッシュとグローバルパッケージ。ビルドに必要
  • $HOME/.claude — Claude Code の設定・履歴
  • $HOME/.claude.json — Claude Code が使う設定ファイル(後述するつまずきポイント)
  • $HOME/.config/claude — Claude Code の追加設定
  • /dev/tty / /dev/ttys* — ターミナルへの出力(後述するつまずきポイント)

auto-presets の意味

auto-presets の部分は「cage claude と打ったら自動的に claude-code プリセットを適用する」という設定です。毎回 -preset claude-code と打たなくてよくなります。

Step 3: .zshrc に関数を追加する

毎回 cage claude --dangerously-skip-permissions と打つのは面倒なので、claude と打つだけで cage 経由で起動するようにします。

nano ~/.zshrc

以下を追加します。

# claude を cage 経由で起動する(サンドボックス + 確認スキップ)
function claude() {
  cage claude --dangerously-skip-permissions "$@"
}

# cage なしで直接起動したいとき用
alias claude-raw="command claude"

保存して反映します。

source ~/.zshrc

これで使い分けができます。

  • claude → cage サンドボックス内で確認スキップ起動
  • claude-raw → cage を通さず通常モードで起動

Step 4: settings.json を設定する

~/.claude/settings.json を編集します。

nano ~/.claude/settings.json

以下のように設定します(既存の設定がある場合はマージしてください)。

{
  "permissions": {
    "deny": [
      "Bash(git push:*)",
      "Bash(git add -A:*)",
      "Bash(git add --all:*)"
    ]
  }
}

ポイントは "defaultMode": "bypassPermissions" を設定ファイルに書かないことです。確認スキップは .zshrc の関数で --dangerously-skip-permissions フラグとして渡しています。設定ファイルに書くと、cage を通さずに起動したときも確認スキップになってしまうからです。

deny リストでは git push と git add -A をブロックしています。hooks と合わせた二重防御です。

Step 5: Homebrew の IN_CAGE 対策

cage の中で Homebrew のコマンドが実行されると、こんなエラーが出ることがあります。

/opt/homebrew/Library/Homebrew/cmd/shellenv.sh: line 18: /bin/ps: Operation not permitted

cage が /bin/ps への書き込み(正確にはプロセス情報へのアクセス)をブロックしているためです。

nano ~/.zprofile

以下の行を探して、

eval "$(/opt/homebrew/bin/brew shellenv)"

こう書き換えます。

if [[ -z $IN_CAGE ]]; then
  eval "$(/opt/homebrew/bin/brew shellenv)"
fi

cage は起動時に IN_CAGE=1 という環境変数をセットします。この条件分岐で「cage の中では Homebrew の初期化をスキップする」という意味になります。cage の外からすでに必要な環境変数は引き継がれているので、問題ありません。

実際につまずいたポイント

ここからが本題というか、この記事を書いた理由です。設定して「はい完了!」とはいきませんでした。

つまずき1: ホームディレクトリでは動くのに、プロジェクトフォルダでダンマリになる

cage の設定を済ませて、ホームディレクトリで claude を打つと起動する。よしよし。

ところが、プロジェクトフォルダに移動して claude を打つと……何も表示されない。エラーも出ない。ただ、ダンマリ。

切り分け方

まず cage が原因かどうかを確認しました。

cd ~/your-project
command claude

command を付けると .zshrc の関数をスキップして Claude Code を直接起動します。これで動いたので、cage が原因と特定できました。

次に、cage の制限を全部外して試しました。

cage -allow-all claude --dangerously-skip-permissions

これで動く。ということは、書き込み制限のどこかが引っかかっている。

つまずき2: どのパスがブロックされているかわからない

cage は制限に引っかかったとき、エラーメッセージを出してくれません。ただ黙って止まるだけ。ここが一番困りました。

解決策は、macOS のシステムログを監視することです。macOS はサンドボックスがブロックした操作をカーネルログに記録しています。

ターミナルを2つ開きます。

ターミナル1でログ監視を開始。

log stream --predicate 'eventMessage contains "deny" and eventMessage contains "file-write"' --style compact

ターミナル2で claude を起動。

cd ~/your-project
claude

するとターミナル1にこんなログが流れてきました。

Sandbox: bash(11477) deny(1) file-write-data /dev/tty
Sandbox: 2.1.63(11474) deny(1) file-write-create /Users/nobuhito/.claude.json.lock
Sandbox: 2.1.63(11474) deny(1) file-write-data /Users/nobuhito/.claude.json

原因は2つ。

  1. /dev/tty — ターミナルへの文字表示そのものがブロックされていた。そりゃダンマリになる
  2. ~/.claude.json — ホームディレクトリ直下の設定ファイル。~/.claude/ フォルダは許可していたけど、~/.claude.json というファイルは別物

この2つを presets.yaml に追加したら、無事に起動しました。

教訓

cage のトラブルシューティングは log stream コマンドが必須です。cage 自体はエラーを教えてくれないので、OS のログから拒否されたパスを拾うしかありません。

今後、別のプロジェクトで同じようにダンマリになったら、このコマンドを使えば原因を特定できます。

log stream --predicate 'eventMessage contains "deny" and eventMessage contains "file-write"' --style compact

セキュリティの考え方

cage が守ってくれることと、守れないことを整理しておきます。

守れること

  • 許可していないディレクトリへのファイル書き込み
  • システムファイルの破壊
  • プロジェクト外のファイル改ざん

守れないこと(cage の仕様上の制限)

  • ファイルの読み取り(どこでも読める。これは仕様)
  • ネットワーク通信(hooks で別途対策)
  • コマンドの実行自体(hooks で別途対策)

設定で考えるトレードオフ

最初の設定では allow-keychain: true(キーチェーンへのアクセス許可)や $HOME/.cache$HOME/.local なども許可していました。

でも考えてみると、キーチェーンには他のサービスのパスワードも入っています。Claude Code に触らせる必要はない。~/.cache~/.local も範囲が広すぎる。

最終的に、必要最小限のパスだけ許可する設定に落ち着きました。キーチェーンへのアクセスが必要な場面(Claude Code のログインなど)は、claude-raw で cage を通さずに起動すれば対応できます。

この「まず最小限にして、必要になったら追加する」という方針がおすすめです。

hooks でコマンドを検証する(レイヤー2)

cage でファイルアクセスは守れますが、ネットワーク送信や危険なコマンドは別途止める必要があります。

Claude Code の PreToolUse フック機能を使って、コマンド実行前に中身をチェックする validate-bash.sh を作りました。

止めるもの

5つのカテゴリに分けて紹介します。

間接実行の防止

Claude は賢いので、直接的なコマンドを禁止すると迂回しようとすることがあります。eval、パイプ経由のシェル実行(| bash)、base64 デコード、変数経由のコマンド組み立てなどを先回りして塞いでいます。

ネットワーク送信の防止

curl の POST/PUT/PATCH/DELETE、wget のデータ送信、Python や Node.js の送信系 API を検出します。

ここでもハマりました。インラインのコマンドだけ止めても、Claude が Python スクリプトを書いてから python3 script.py で実行すると、スクリプト内のネットワーク送信コードは検出できません。そこで、スクリプトファイルの実行を検出したらファイルの中身をスキャンして、ネットワーク関連コードが含まれていないかチェックするようにしました。

WebFetch ツールの制御

Claude の WebFetch ツールも、URL にデータを埋め込んで外部送信される可能性があります。クエリパラメータが異常に長い場合は拒否します。通常のドキュメント閲覧はそのまま通ります。

環境変数の読み取り防止

envprintenv による全環境変数の一覧表示を止めています。API キーやトークンが環境変数に入っていることは多いので。ただし echo $PATH のような個別の参照は止められません。ここはトレードオフです。

危険コマンドの防止

git push、git add -A、カレントディレクトリ外での rm -rf などを止めています。

ブロックされたら?

すべての拒否メッセージに「コマンド全文をユーザーに提示し、手動実行を依頼してください」という指示が自動で付きます。なので、業務上 curl -X POST が必要なときは、Claude がコマンド全文を表示してくれて、ユーザーが中身を確認して自分で実行する流れになります。業務は止まりません。

セットアップスクリプトで一発導入

ここまでの設定を手作業でやるのは大変です。setup.sh で自動化しました。

git clone https://github.com/mono96/claude-code-guard.git
cd claude-code-guard
chmod +x setup.sh
./setup.sh

setup.sh がやってくれること:

  • cage が未インストールなら brew install で自動インストール
  • cage の presets.yaml を配置
  • validate-bash.sh を配置して実行権限を付与
  • settings.json を既存設定とマージ(既存の env や enabledPlugins を保持)
  • 全ステップの結果を色付きで表示
  • 既存の設定ファイルは自動でバックアップ

起動方法

cage claude

これだけです。.zshrc に関数を追加していれば claude だけでOK。

パラメータもそのまま渡せます。

claude --continue
claude -p "テストを実行して"

残っている限界

完璧ではないので、把握しておいてほしい点があります。

個別の環境変数は読める。 env は止めていますが、echo $SECRET_KEY のように変数名を直接指定されると止められません。完全に塞ぐと echo $PATH のような正常な操作まで壊れるので、トレードオフとして許容しています。

WebSearch は未対策。 検索クエリに機密情報を埋め込む可能性はゼロではありませんが、実用上のリスクは低いと判断しています。

cage はダンマリで止まる。 エラーメッセージを出してくれないので、問題が起きたら log stream コマンドでシステムログを確認する必要があります。

まとめ

Claude Code を bypassPermissions で自走させるなら、Claude の外側からガードレールを張るのが安全です。

  • cage で OS レベルのファイルアクセス制限 → カレントディレクトリの中だけ自由
  • hooks でコマンド・ネットワーク・環境変数を検証 → 危険な操作は理由付きで拒否
  • settings.json で deny リスト → 二重防御

実際にセットアップしてみると、ドキュメント通りにはいかない場面がいくつもありました。Homebrew 5.0 で --no-quarantine が廃止されていたり、/dev/tty~/.claude.json の許可が必要だったり。ただ、log stream コマンドさえ知っていれば、ブロックされているパスを特定して解決できます。

cage は「ダンマリになる」のが最大の落とし穴ですが、一度設定が決まれば、安心して Claude Code を走らせられる環境が手に入ります。

Claude Code Guard(GitHub)

あわせて読みたい

この記事の続編として、allow-keychain を外したときに認証が通らなくなった問題と解決策を書きました。

【続・cage】allow-keychain を外したら Claude Code にログインできなくなった話と、その解決策

この記事を書いた人

大東 信仁

カンパチが好きです。

プロフィールはこちら

10月14日開催 参加者募集中
(画像をタップ→詳細へ)

ミッションナビゲート モニター
(画像をタップ→詳細へ)

広告