Claude Codeのセキュリティ対策—Auto modeとHooksで固める3層防御

Claude Codeのセキュリティ対策—Auto modeとHooksで固める3層防御 | mohablog

先日Xを眺めていたら、「Claude Codeが.envを勝手にgit commitしてしまい、数分後には攻撃者にAPIキーを悪用されていた」という個人開発者の投稿が流れてきました。公開リポジトリに鍵を置けばクローラーに即発見されるのは昔から知られた話ですが、「AIエージェントが勝手にやった」パターンの事故はここ数ヶ月で確実に増えています。

Claude Codeはデフォルトでは読み取り中心の比較的安全な構成ですが、軽い気持ちで--dangerously-skip-permissionsを使ったりallowを広げたりすると一気に防御が崩れます。筋の良い落としどころは、Sandbox / Permissions / Hooks の3層を組み合わせて「一層破られても次が効く」状態にしておくこと。2026年4月時点のClaude Code v2.1系を前提に、事故を踏まないための具体的な設定を整理しておきます。

目次

Claude Codeで実際に起きた事故と、なぜ防げなかったのか

よくある事故パターン

X(旧Twitter)やコミュニティに流れてきた事故事例をまとめると、次のような顔ぶれになります。

  • .envgit addしてpushされ、数分でAPIキーが悪用された
  • 自律モードで回している最中にrm -rf ~/Downloadsを走らせてしまった
  • 本番DBに直接つながる接続情報を持ったまま、マイグレーションを試みられた
  • 知らないMCPサーバーにトークンを送信してしまった

共通しているのは「権限を広めに渡したまま、見てない間に走らせた」という点です。AIエージェント固有の問題というより、設定のサボりが事故を生んでいる側面が大きいですね。

なぜ「デフォルトのまま」では不十分なのか

公式のセキュリティドキュメントによると、Claude Codeのデフォルトは厳密な読み取り専用パーミッションで、Bashの実行やファイル編集には明示的な承認が必要とされています。ここだけ読むと十分安全に聞こえますが、現実には次のように崩れがちです。

  • 都度確認が面倒でついallowを広げてしまう
  • チームに配布されたsettings.jsonがそもそも緩い
  • 長時間の自動実行で、目を離す瞬間が生まれる

つまり個別のパーミッションだけに頼ると運用フェーズで崩壊するので、複数レイヤーでの重ねがけが必要という話です。

3層防御モデルの全体像

公式のセキュリティページで紹介されている組み込み保護は、大まかにサンドボックス、パーミッション、フックの3つです。それぞれの守備範囲を表で整理します。

主な役割設定場所強み
SandboxOSレベルでファイル/ネットワークを物理的に分離/sandboxコマンド + 設定ファイルAIが誤指示してもカーネルで弾かれる
Permissionsツール呼び出しのallow/deny/askルール.claude/settings.jsonプレフィックス単位で細かく制御
Hooks実行前に任意スクリプトで判定settings.jsonhooksセクション正規表現や外部APIまで使った判定ができる

Sandboxが一番外側、Permissionsがツール単位、Hooksが最後の番人。どれも単独では穴があるので、重ねて使うのが前提です。

第1層:SandboxでOSレベルの境界を作る

Sandboxを有効にする設定

SandboxはBashツールをファイルシステムとネットワークの両面で分離して実行する仕組みです。CLI上で/sandboxコマンドを打つと、現プロジェクト用のサンドボックス設定を作成できます。

/sandbox
Sandbox configured for /Users/moha/workspace/sample
  writable roots: /Users/moha/workspace/sample
  network: allow-listed domains only
Run /sandbox status to review current rules.

出力形式はバージョンによって差があります。正確な形式は/sandbox helpで確認するのが確実です。

プロジェクト外への書き込みを防ぐ

デフォルトで「プロジェクトルート配下のみ書き込み可能」という制約が効きます。ここにrm -rf ~/Downloadsのような指示が飛んでも、OSレベルで弾かれます。

# Claude Codeのセッション内からbashを走らせた場合
$ rm -rf ~/Downloads
rm: cannot remove '/Users/moha/Downloads': Operation not permitted (sandbox)

注意点が1つあって、Sandboxは親ディレクトリを守る代わりにプロジェクト外のファイル書き換えを全面禁止します。リポジトリを跨いだ作業をしたい時は都度外す必要があるので、副作用として覚えておきましょう。

ネットワークアクセスのホワイトリスト

Sandbox内ではネットワークアクセスもドメイン単位で制限できます。設定ファイル側で許可先を列挙する形です。

{
  "sandbox": {
    "allowedDomains": [
      "registry.npmjs.org",
      "api.github.com",
      "pypi.org"
    ]
  }
}

これだけで「知らないMCP経由で外部に秘密を送り出す」経路が物理的に塞がります。CIやバッチで放置する場合ほど、このレイヤーが効いてくる印象です。

第2層:Permissionsのallow / deny / askを設計する

allowは「確認不要」を積み上げるリスト

settings.jsonpermissions.allowは、いちいち承認しなくていい操作のホワイトリストです。広げすぎると事故のもとなので、読み取り系と無害なgit操作に絞るのが基本形になります。

{
  "permissions": {
    "allow": [
      "Read",
      "Grep",
      "Glob",
      "Bash(git status:*)",
      "Bash(git diff:*)",
      "Bash(git log:*)",
      "Bash(npm test:*)"
    ]
  }
}

Bash(command:*)の形式はプレフィックス一致で、*は任意の末尾引数にマッチします。ドキュメントによれば、マッチしないコマンドはフェイルクローズド、つまり黙って通ることはなく承認待ちに回ります。

denyで危険コマンドを明示的に止める

denyはallowより強く、allowに含まれていてもdenyが優先されます。壊れたらリカバリが面倒な操作を列挙しておきます。

{
  "permissions": {
    "deny": [
      "Bash(rm -rf:*)",
      "Bash(git push --force:*)",
      "Bash(git reset --hard:*)",
      "Bash(curl * | sh)",
      "Read(./.env)",
      "Read(./.env.*)"
    ]
  }
}

Read(./.env)のようにファイルパスもパターンにできるのがポイントです。.env自体を読ませないようにしておけば、そもそも「誤ってcommitする内容をAIが知らない」という状態にできるので、事故の芽を根元から断てます。

/permissionsで定期的に棚卸しする

CLIで/permissionsを叩くと、現在効いているallow/deny/askの一覧と、どこで定義されたか(user / project / local)が出ます。

/permissions
Effective permissions:
  allow (from project .claude/settings.json):
    Read, Grep, Glob, Bash(git status:*), Bash(npm test:*), ...
  deny (from user ~/.claude/settings.json):
    Bash(rm -rf:*), Bash(curl * | sh), Read(./.env)
  ask:
    Bash(git push:*), Bash(npm publish:*)

増えすぎたallowを削るだけでも防御は一気に上がります。棚卸しは月1くらいでやるのが現実的なペースですね。

第3層:Hooksでツール実行前にブロックする

PreToolUse Hookの仕組み

Permissionsはあくまで「ツール名 + プレフィックス」のマッチングです。もっと複雑な条件、たとえば「.envという単語がgit commitの引数に含まれていたら止める」というような判定をしたい場合はHooksの出番です。

PreToolUseフックはツールが実行される直前に呼ばれます。settings.jsonhooksセクションに登録します。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/block-dangerous.sh"
          }
        ]
      }
    ]
  }
}

matcherはツール名のフィルタで、Bashと書くとBashツールの呼び出しのみがフックされます。EditWriteなど他のツールに絞るのも同じ要領です。

Hooksはセッション起動時にスナップショットされる

地味に効く仕様として、公式ドキュメントにはHooksスクリプトの内容はセッション起動時にスナップショットが取られ、セッション中にファイルが書き換えられても適用されるのはスナップショット版という記述があります。

These hooks are snapshotted at session start, so any modifications during the session do not affect the running session.

これは「Claude Codeにフック自体を書き換えさせてバイパスする」という攻撃経路を塞ぐための設計です。ユーザー側の視点だと「セッション途中でhook修正しても効かない」という罠でもあるので、再起動が必要という点は覚えておきましょう。

.envやrm -rfをブロックする実装例

具体的なスクリプトを1本置いておきます。標準入力からJSONでツール情報が渡り、exit 2で「ブロック」を伝える契約です。exit 0は通常処理、exit 1は警告のみ(ユーザーには見えるがエージェントには伝わらない)という使い分けになっています。

#!/usr/bin/env bash
# ~/.claude/hooks/block-dangerous.sh
set -euo pipefail

payload=$(cat)
tool=$(printf '%s' "$payload" | jq -r '.tool_name')
cmd=$(printf '%s' "$payload" | jq -r '.tool_input.command // ""')

[[ "$tool" != "Bash" ]] && exit 0

if [[ "$cmd" =~ git[[:space:]]+(add|commit).*\.env ]]; then
  echo ".env を git に載せようとしています。中止しました。" >&2
  exit 2
fi

if [[ "$cmd" =~ rm[[:space:]]+-rf[[:space:]]+(/|\$HOME|~) ]]; then
  echo "ホーム直下 / ルートへの rm -rf は禁止です。" >&2
  exit 2
fi

if [[ "$cmd" =~ curl[[:space:]]+.*\|[[:space:]]*sh ]]; then
  echo "curl | sh 形式のインストールは禁止です。" >&2
  exit 2
fi

exit 0
$ echo '{"tool_name":"Bash","tool_input":{"command":"git add .env"}}' \
    | ~/.claude/hooks/block-dangerous.sh
.env を git に載せようとしています。中止しました。
$ echo $?
2

1回書けばプロジェクトを跨いで使えるので、ユーザー設定側(~/.claude/settings.json)に置いておくのが取り回しが良いです。Hooksをさらに自動化用途で活用する話はClaude Codeで「イベント駆動型ワークフロー自動化」ー Hooks × Scheduler × Skills 3つの基本機能を組み合わせる実践ガイドで整理しているので、設定ファイルの書き方の土台として合わせて読むと掴みやすいと思います。

Auto modeは–dangerously-skip-permissionsの代替になるか

Auto modeが導入された背景

2026年3月24日にAnthropicがリリースしたAuto modeは、いわゆる「全許可モード」である--dangerously-skip-permissionsのより安全な代替として提案されたものです。

--dangerously-skip-permissions(通称YOLOモード)は名前の通り全てのツール実行を自動承認します。長時間の自動タスクを流したい時には便利ですが、「ユーザーの確認」という最後のゲートを外してしまう設定です。

何ができて、何ができないのか

Auto modeは「denyとHooksは効かせたまま、askと都度承認だけ自動化する」という振る舞いをします。比較表にするとこんな形です。

機能通常モードAuto mode–dangerously-skip-permissions
allow外の操作都度承認自動承認自動承認
deny指定の操作ブロックブロックブロック
PreToolUse Hook実行される実行される実行される
Sandbox境界有効有効有効
askルールの扱い都度聞く自動通過自動通過

Auto modeではフックやdenyは生きたままなので、「Claude Codeを放置して作業させたいが最低限の防御は残したい」というユースケースに自然にフィットします。

どのケースで使うべきか

個人の運用では、次の使い分けが現実的でした。

  • 短時間の対話作業 → 通常モード(都度承認)
  • 数十分〜1時間のリファクタリング作業 → Auto mode + denyを広めに設定
  • 完全オフラインのCIや使い捨てのバッチ → --dangerously-skip-permissions(ただし隔離されたコンテナ内でだけ使う)

日常的に--dangerously-skip-permissionsを叩くのはおすすめしませんが、devcontainerやCIコンテナなど「壊れてもすぐ捨てられる環境」では依然として使い所はあります。「壊れて困る環境では必ずAuto modeまでに留める」を個人ルールにしておくと、指が滑っても事故にはなりにくいです。

チーム配布用settings.jsonの設計パターン

settings.jsonとsettings.local.jsonの使い分け

Claude Codeの設定は、ユーザー / プロジェクト / ローカルの3レイヤーで読まれます。配布前提で設計するなら、次の切り分けが扱いやすいです。

ファイル目的バージョン管理
~/.claude/settings.jsonユーザー個人の好み / 個人用denyしない(dotfilesリポジトリで別管理)
.claude/settings.jsonプロジェクト共通のallow / deny / hooksする
.claude/settings.local.jsonローカル一時的な上書きしない(.gitignoreに入れる)

プロジェクト共通のdenyリストを.claude/settings.jsonに入れてgit管理しておけば、新しいメンバーがcloneした直後から同じ防御レベルで始められます。Reviewerの観点から見ても「このリポジトリではこれが禁止」が明示されているほうが安心です。

機密情報はenvではなくOSのキーチェーンへ

APIキーをsettings.jsonenv.envに直接書くのは避けましょう。理由はシンプルで、コミット事故を招きやすいからです。macOSのsecurityコマンドや1password-cli、Linuxのsecret-toolなどでOSのキーチェーンから引き出す形にしておくと、そもそもファイルに平文で出現しなくなります。

# macOSのキーチェーンから取り出す例
export ANTHROPIC_API_KEY=$(security find-generic-password -s anthropic -w)
export OPENAI_API_KEY=$(security find-generic-password -s openai -w)
claude
Claude Code v2.1.101
Anthropic API key loaded from environment.
Ready.

dotfiles管理のシェル起動スクリプトに仕込んでおけば、新しいマシンでセットアップするときも鍵の扱いで迷いません。

CLAUDE.mdによる運用ルールとの関係

CLAUDE.mdは「運用上の約束」を書く場所で、強制力はありません。一方でsettings.jsonのdenyやHooksはハーネスが実行時に強制するレイヤーです。両者は役割が違うので、ルールを文章で書いたからといってsettings.json側を緩めてはいけない、というのが大事な認識になります。

CLAUDE.mdの設計そのものはClaude CodeのCLAUDE.md設計 — チーム開発ルールを統一する仕組みで整理しているので、設定側のルールと読み合わせると役割分担がはっきりします。

まとめ

今回扱った内容の要点をまとめます。

  • Claude Codeのセキュリティは Sandbox / Permissions / Hooks の3層防御が基本形
  • Sandboxは/sandboxコマンドで有効化し、OSレベルでプロジェクト外の書き込みやネットワーク通信を制限する
  • Permissionsはallowを狭く、denyを広く取り、/permissionsで月1の棚卸しを習慣化する
  • Hooksはセッション起動時スナップショット仕様で、バイパスされにくいのが強み
  • .envrm -rfのような事故コマンドはPermissionsとhooksの両方で二重に塞ぐ
  • --dangerously-skip-permissionsの日常利用は避け、代替としてAuto modeを使う
  • チーム運用では.claude/settings.jsonをgit管理してdeny/hooksを共有する
  • CLAUDE.mdは運用の約束、settings.jsonは強制ルール。この役割分担を混同しない

3層のうちどこか1つでも欠けると、実際に起きている事故の半分くらいはするっと通ってしまいます。忙しいときほど一番外側のSandboxだけでも先に入れておくと、大怪我は避けやすいです。最初の30分だけ時間を使って.claude/settings.jsonのdenyとHookを整備しておけば、そのあと数ヶ月は「設定を見直さなきゃ」と焦らずに済むはずです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次