自律エージェントを安全に走らせる足場:Codexの暴走を止める7つのガード
Codexのような自律AIに作業を任せて事故らないために。サンドボックス、権限の最小化、出力の検証、破壊的操作のガード、秘密の保護を、コピペで動く設定と僕の失敗談で。
夜中の2時、僕は自分のターミナルを見て固まりました。
「テストが落ちてるとこ、自分で直して通しといて」。そう投げて寝るつもりだったエージェントが、テストを通すためにテスト自体を書き換えていたんです。expect(result).toBe(42) を expect(true).toBe(true) に。緑のチェックマークは並んでいました。中身は空っぽでした。
笑い話で済んだのは、たまたまローカルだったから。これがもし本番デプロイの権限まで持っていたら、と思うとゾッとします。
自律型のAIエージェント、たとえばOpenAIのCodexは、頼んだ仕事を一人でガリガリ進めてくれます。便利です。でも「一人で進める」というのは、裏を返せば僕が見ていない間に何でもできるということ。賢いかどうかは、ここではあまり関係ありません。賢い新人だって、金庫の鍵を渡したその日に使い方を間違えます。
今日の話は、攻撃の仕方じゃありません。逆です。自律エージェントに、どこまで許して、どこで必ず止めるか。その足場(ハーネス)の組み方を、僕がこのサイト運用で実際に踏んだ地雷つきで並べます。守りの話です。
この記事の要点
- 自律エージェントの事故は「モデルが悪い」ではなく「足場が緩い」で起きる。賢さより置き方を先に固める。
- 守りは7つのガードで考えると漏れない: 隔離・最小権限・読ませない・破壊的操作の停止・出力の検証・秘密の保護・記録。
- 権限は「全部禁止から始めて、安全だと分かったものだけ開ける」。逆順にすると必ず事故る。
- 一番効くのは凝った検出ロジックじゃなく、サンドボックス(隔離された作業場)と、破壊的操作を人間の承認待ちにする一行。
- Codexにも Claude Code にも同じ発想がある。名前は違っても、やりたいことは同じ。
なぜ「自律」だと急に危ないのか
少し前まで、AIに頼むのは「コードを書いて」止まりでした。出てきたものを人間が読んで、自分の手でコミットする。間に必ず人間が挟まっていた。だから事故っても、貼り付ける前に気づけた。
自律エージェントは、ここが変わります。読む・書く・コマンドを実行する・失敗を見て直す・また実行する、このループを人間を待たずに回す。速い。でも、ループの途中に確認ポイントがありません。冒頭のテスト書き換えも、僕が一回でも目を通していれば即バレでした。誰も見ていなかったから、緑のまま朝を迎えた。
しかもエージェントは、目的を達成するためなら手段を選ばないことがあります。「テストを通せ」と言われたら、コードを直すのが筋なのに、テストを消すという最短ルートを平気で取る。悪意じゃありません。指示に忠実すぎるだけ。だからこそ、指示でお願いするのではなく、足場で物理的に塞ぐ必要があるんです。
Claude Code の公式ドキュメントにも、はっきりこう書いてあります。権限ルールはモデルではなく Claude Code 自身が強制する、プロンプトや CLAUDE.md の指示は「何をしようとするか」を変えても「何が許されるか」は変えない、と(Configure permissions)。つまり「お願い」は守ってもらえない前提で組む。これが守りの出発点です。
守りは7つのガードで考える
足場と言われても漠然としますよね。僕は7つに分けて潰しています。一個ずつは難しくありません。
| # | ガード | やること | 効く事故 |
|---|---|---|---|
| 1 | 隔離 | 隔離された作業場(サンドボックス)で動かす | ホーム配下や他プロジェクトの巻き込み |
| 2 | 最小権限 | 必要な道具だけ渡す。残りは禁止 | 想定外のコマンド実行 |
| 3 | 読ませない | .env や鍵を読み取り禁止に | 秘密の露出・流出 |
| 4 | 破壊的操作の停止 | 削除・本番・送信は人間の承認待ち | 取り返しのつかない操作 |
| 5 | 出力の検証 | 「OK」を機械でチェックしてから採用 | 嘘の成功・空のテスト |
| 6 | 秘密の保護 | 環境変数の注入・ログのマスク | ログやPRへの秘密混入 |
| 7 | 記録 | 何をしたかログに残す | 事故後の原因追跡 |
この7つが、エージェントの周りをぐるっと囲む輪っかです。1つでも抜けると、だいたいその穴から事故が漏れます。順番に見ていきます。
ガード1〜2:隔離して、道具を絞る
まず大前提。自律エージェントを、自分のホームディレクトリでそのまま走らせないでください。 これだけで事故の半分は消えます。
Codex には sandbox(隔離された作業場)の概念があります。公式ドキュメントによると、モードは3つ。read-only(読むだけ)、workspace-write(作業フォルダ内なら書ける・ローカルコマンドも回せる、ローカル作業の標準)、danger-full-access(制限なし、別名 --yolo)です(Agent approvals & security)。名前のとおり、最後のは原則使わない。普段は workspace-write に閉じ込めておく。これが「隔離」です。
Claude Code 側も同じ発想を持っています。権限(どの道具を使えるか)と、OS レベルのサンドボックス(Bash の読み書き・ネットワークを OS が物理的に制限)は別レイヤーで、両方かけるのが defense-in-depth(多層防御)だと公式が言っています。サンドボックスが効いていれば、仮にプロンプトインジェクションでモデルの判断がだまされても、Bash は境界の外に手を出せません(Configure permissions)。モデルを信じる代わりに、OS を信じる。ここがミソです。
道具を絞る、は地味ですが効きます。僕は最初「便利だろう」と道具を盛りまくって、エージェントが余計なコマンドを連発する羽目になりました。今は「このタスクに本当に要るものだけ」。残りは禁止。少ないほうが、エージェントも迷いません。
ガード3〜4:秘密を読ませない、破壊的操作で止める
次が本丸です。.env を読ませない。削除・本番・送信で止める。 ここを死守するだけで、夜中にゾッとする回数が激減します。
Claude Code なら .claude/settings.json に書けます。評価順は deny → ask → allow で、deny が最優先。最初にマッチしたルールが勝ちます(Configure permissions)。だから「危ないもの」を deny に書いておけば、うっかり allow があっても潰せる。下が僕の実運用に近い最小セットです。コピペして、自分のコマンド名に差し替えてください。
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(npm run build)",
"Bash(npm run test *)",
"Bash(npm run lint)"
],
"ask": [
"Bash(git push *)",
"Bash(npm publish *)",
"Bash(wrangler pages deploy *)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(git reset --hard *)",
"Bash(git clean *)",
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}
ポイントは2つ。deny は「なんとなく危なそう」で書かない。 rm -rf、git reset --hard、.env の読み取り、本番デプロイ系。具体的なコマンド名と具体的なパスで書きます。曖昧なルールは、曖昧な隙間から漏れます。
もう1つ。Read(./.env) を deny に入れると、Claude Code は .env 自体を読めなくなるだけでなく、cat .env のようにファイルを読む Bash も塞いでくれます。ただし公式の注意書きどおり、Python や Node のスクリプトが内部で勝手にファイルを開く経路までは止められません。そこは OS レベルのサンドボックスの出番。だから「権限 deny」と「サンドボックス」は両方、なんです(Configure permissions)。
Codex でも考え方は同じ。approval policy(承認の方針)で on-request(作業フォルダ外の編集やネットワークアクセスは承認を求める)や untrusted(既知の安全な読み取りだけ自動、状態を変える操作は承認)を選べます。設定はセッション中なら /permissions、毎回同じにしたいなら config.toml に書く(Agent approvals & security)。「ここまでは勝手にやっていい、ここからは人間に聞く」を仕切る。 ツールが変わっても、やることは変わりません。権限設計の早見表は Claude Code 権限設定リファレンス に、厳しめから安全に緩める順番は 権限の“はしご”:安全に allow を広げる5段 にまとめてあります。
ガード5:出力を、人間の目じゃなく機械で検証する
冒頭のテスト書き換え事故。あれを止めるのが、このガードです。
エージェントは「終わりました」と平気で言います。でもその「終わった」が本当かは別問題。緑のチェックマークが、空っぽのテストの緑かもしれない。何をもって OK とするかを、先にコードで決めておく。 これだけで嘘の成功を弾けます。
たとえば「テストが本当に走って、本当に通ったか」を確かめる小さなガードです。テストの件数が前より減っていたら(=こっそり消された疑い)、成功扱いにしない。コピペで動きます。
#!/usr/bin/env bash
# verify-tests.sh — エージェントの「テスト通った」を信用しないためのガード
set -euo pipefail
BASELINE_FILE=".test-count-baseline"
# テストを実行し、件数を含むJSONを取り出す(jestの例)
OUTPUT=$(npm test -- --json --silent 2>/dev/null || true)
PASSED=$(echo "$OUTPUT" | jq -r '.numPassedTests // 0')
FAILED=$(echo "$OUTPUT" | jq -r '.numFailedTests // 0')
TOTAL=$(echo "$OUTPUT" | jq -r '.numTotalTests // 0')
echo "テスト結果: 合格 $PASSED / 失敗 $FAILED / 合計 $TOTAL"
# 1. 失敗が1件でもあればアウト
if [ "$FAILED" -ne 0 ]; then
echo "NG: 失敗テストが $FAILED 件あります"
exit 1
fi
# 2. テスト件数が前回より減っていたら「こっそり削除」を疑ってアウト
if [ -f "$BASELINE_FILE" ]; then
BASELINE=$(cat "$BASELINE_FILE")
if [ "$TOTAL" -lt "$BASELINE" ]; then
echo "NG: テスト件数が $BASELINE→$TOTAL に減りました。消されていませんか?"
exit 1
fi
fi
# 3. ここまで来たら本物の成功。件数を記録して次回の基準にする
echo "$TOTAL" > "$BASELINE_FILE"
echo "OK: テストは本物の合格です"
完璧な検証じゃありません。でも「空のテストで緑」「テストを消して成功」みたいな間抜けな事故は、これで止まる。エージェントの自己申告ではなく、こちらの基準で合否を決める。これを CI や、エージェントの完了フックに噛ませておくと効きます。出力の検証を足場に組み込む考え方は、ハーネスエンジニアリングの入門記事 でもう少し丁寧に書きました。
ちなみに Claude Code には PreToolUse フックという仕組みもあって、ツール実行の直前に自前のスクリプトで割り込み、exit code 2 でそのコマンドを止められます(Configure permissions)。「特定のパスへの書き込みだけは絶対ブロック」みたいな細かいガードは、これで作り込めます。
ガード6〜7:秘密は注入で渡す、何をしたか記録する
秘密の保護で一番やりがちなミスは、API キーをコードやプロンプトにベタ書きすることです。エージェントがそれをログに吐いたり、PR の差分に含めたりした瞬間、漏れます。
鉄則は、秘密を環境変数で実行時に注入して、ファイルにもプロンプトにも残さないこと。さらに、エージェントの出力をどこかに送る前に、それっぽい文字列をマスクする一段を挟む。地味ですが、これが最後の砦です。
# 秘密はファイルに書かず、実行時に環境変数で渡す
export ANTHROPIC_API_KEY="$(security find-generic-password -s anthropic -w)"
# エージェントのログを保存する前に、それっぽい秘密をマスクする
agent-run "$@" 2>&1 | sed -E \
-e 's/(sk-[A-Za-z0-9_-]{8})[A-Za-z0-9_-]+/\1***MASKED***/g' \
-e 's/(AKIA[0-9A-Z]{4})[0-9A-Z]+/\1***MASKED***/g' \
| tee "logs/agent-$(date +%Y%m%d-%H%M%S).log"
そして7つ目、記録。エージェントが何のコマンドを、いつ、どんな結果で実行したかをログに残す。上のように tee でタイムスタンプ付きファイルに落としておくだけで十分です。事故ったとき、「何をやらかしたのか」を後から追えるかどうかは天と地の差。僕は一度ログを取っていなくて、何が壊れたのか丸一日かけて再現する羽目になりました。二度とやりません。
僕がやらかした失敗3つ
正直に書きます。守りの足場、最初はガバガバでした。
ひとつ目。danger-full-access 相当で走らせて、node_modules ごと吹っ飛ばした。 「速いほうがいいだろう」と隔離を外したら、エージェントがキャッシュ掃除のつもりで深いディレクトリを rm -rf して、関係ないものまで巻き込みました。隔離は遅くなる代わりに、事故の被害をその箱の中に閉じ込めてくれる。速さと安全のトレードオフで、安全を取る。今はそうしています。
ふたつ目。deny を「お願い」で済ませようとした。 CLAUDE.md に「本番には絶対デプロイしないで」と書けば守ってくれると思っていました。違いました。公式が言うとおり、プロンプトや CLAUDE.md は「何をしようとするか」を変えるだけで、「何が許されるか」は変えない。指示は破れる前提で、ask/deny という足場側のルールで塞ぐ。文章でのお願いと、設定での強制は、別物です。
みっつ目。「あとで自分が確認すればいい」で回して、忙しい日に破綻した。 公開URLが404のまま、テストが空っぽのまま、気づかず進んでいた。目視チェックは、忙しいと必ず飛ばします。だから機械でわかる確認は機械にやらせる。ガード5の検証スクリプトを噛ませてからは、夜中のヒヤッとが激減しました。危ない依頼そのものの避け方は 危険なプロンプトを避ける実務チェック にまとめてあります。
始めるなら、ここから
いきなり全部を完璧にしないでください。順番があります。
- 隔離する。 まず Codex なら
workspace-write、Claude Code ならサンドボックスを有効化。エージェントを箱の中に入れる。これが一番効きます。 - 破壊的操作を全部
askに倒す。 削除・本番・送信・push・publish は、最初は問答無用で承認待ち。.envの読み取りはdeny。 - 「OK」を機械で決める。 ガード5の検証スクリプトを1つ書いて、完了の条件をコードにする。エージェントの自己申告を信じない。
- 安全だと分かったものだけ、あとから
allowに格上げする。 広げるのはあとでいい。最初は狭く。
この順番、つまり「全部禁止から始めて、検証が積み上がった操作だけ開けていく」のがコツです。逆順、つまり「全部許してから危ないものを塞ぐ」は、塞ぎ漏れた1個で事故ります。各段の昇格条件は 権限の“はしご” に細かく書いたので、運用に乗せるときに見てください。
よくある質問
Q. サンドボックスに入れれば、もう権限設定はいらない? A. いいえ、両方かけてください。サンドボックスは Bash の読み書き・ネットワークを OS が制限するもの。権限ルールは、そもそもエージェントに特定の操作を試させない前段です。公式も「権限の deny で試行自体を止め、サンドボックスで境界外への到達を止める」多層防御を推奨しています(Configure permissions)。片方だけだと、片方が破られたとき素通しです。
Q. CLAUDE.md に「危ないことはしないで」と書けば十分では?
A. 不十分です。CLAUDE.md やプロンプトはエージェントが「何をしようとするか」を変えるだけで、「何が許されるか」は変えません。強制力があるのは ask/deny のルールやフックです。文章でのお願いは、守られたらラッキー、くらいに考えておくのが安全です。
Q. 全部 ask(承認待ち)にすると、毎回ボタンを押すのが面倒では?
A. 最初はそれでいいんです。回してみて「これは毎回 OK を押している」という安全な操作だけ、allow に格上げする。面倒さは、安全が確認できた操作を昇格させることで自然に減っていきます。最初から広く開けて事故るより、ずっと安いコストです。
Q. Codex と Claude Code で、守りの作り方は違う?
A. 名前は違いますが、考え方は同じです。Codex は sandbox モード(read-only/workspace-write/danger-full-access)と approval policy(untrusted/on-request/never)、Claude Code は権限ルール(allow/ask/deny)とサンドボックス。どちらも「隔離する・最小権限・破壊的操作で止める」の3点が骨です。一度この発想を身につければ、ツールが変わっても応用が効きます。
Q. プロンプトインジェクションが怖い。エージェントが外部の文章にだまされたら? A. だからこそモデルの判断に頼り切らない設計が要ります。OS レベルのサンドボックスが効いていれば、仮にモデルが悪意ある指示にだまされても、Bash は境界の外(本番や他フォルダ)に手を出せません(Configure permissions)。「だまされない賢いモデル」を待つより、「だまされても被害が箱の中に収まる足場」を先に作るほうが確実です。
実際に試した結果
このサイト運用で、自律エージェントに雑務を任せながら守りの足場を育ててきての結論です。
効果が一番大きかったのは、意外にも凝った検出ロジックではなく、**サンドボックスと「破壊的操作を ask にする一行」**でした。これを入れただけで、被害が出るタイプの事故はほぼゼロになった。冒頭のテスト書き換えも、ガード5の件数チェックを噛ませてからは一度も貼り付けまで到達していません。緑のチェックを疑う癖がついたのが、地味に大きい。
逆にハッキリ失敗だったのは、お願いベースで守ろうとしたことです。CLAUDE.md にいくら「気をつけて」と書いても、忙しいエージェントはあっさり最短ルートを取る。指示で守るのはあきらめて、足場で物理的に塞ぐ。こっちのほうが圧倒的に夜眠れます。
賢いエージェントを探すより、転んでもケガしない足場を先に作る。遠回りに見えて、これがいちばん速い、というのが今の実感です。自律型は本当に便利です。便利だからこそ、鍵を渡す前に、金庫の壁を立てておきましょう。
権限設計やCI、チームの運用ルールまで一緒に整えたい場合は、すぐ使えるテンプレートを 教材一覧 にまとめています。自社のリポジトリに合わせて伴走してほしいときは 研修・相談 からどうぞ。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
Claude Codeのチーム利用でコストが読めない時に作る予算ログ
チーム導入前に、誰が何に使い、どの成果が出たかを見える化する予算ログの作り方。
コミット前の3分チェック: Claude Codeが触った範囲を確認してから確定する
Claude Codeが勝手に広げた変更を、コミット前に3分で見抜く確認手順。差分の範囲、検証ログ、ステージするファイルの絞り込みを順番に解説します。
Claude Codeをチーム導入する前に作る「リスク台帳」の中身
Claude Codeを個人実験で終わらせずチーム導入するための、権限・CI・公開の事故を防ぐリスク台帳の作り方を実例とコードで解説します。