
【Claude Code中級編 #7】Claude Codeに危険なコマンドを止めさせる——Hooksによるセキュリティ設計
スキルを作るとき、allowed-tools に Bash を入れておくと便利だが、これは「Bashコマンドが自動的に実行される可能性がある」という設定になる。ファイル削除・git push・APIへの書き込みも通ってしまう。
この記事ではスキルを安全に動かすための設計パターン——allowed-toolsの粒度設定とHooksによるPreToolUseチェックの組み合わせを整理する。
スキルのセキュリティリスク
スキルの allowed-tools を広めに設定すると、確認を求めずにツールを実行できるようになる(ユーザーの操作なしに実行される場合がある)。
# 危険:すべてのBashコマンドが確認なしで通る
allowed-tools: ["Bash", "Write", "Edit"]
この設定でスキルが動いているとき、Claudeが次のようなコマンドを実行しようとしても止まらない:
rm -rf ./dist
git push --force origin main
curl -X POST https://api.example.com/delete -d '{"id": "all"}'
スキルの内容が明確で信頼できるなら問題ないが、スキルが複雑になるほど予期しないコマンドが発生するリスクが上がる。
① allowed-toolsの粒度を絞る
Bash を許可するときは、コマンドレベルで絞り込める。
# コマンドプレフィックスで自動承認を絞る
allowed-tools:
- Bash(git status)
- Bash(git log)
- Bash(git diff)
- Bash(npm test)
- Grep
- Read
allowed-tools は「スキル実行中に確認ダイアログなしで自動実行できるリスト」として機能する。Bash(git status) と書くと git status は自動実行されるが、git push や git reset はリストにないため確認ダイアログが表示される。
ただし、settings.json の permissions.allow でグローバルに許可されているコマンドは、allowed-tools の設定に関わらず確認なしで通る。確実に止めたい場合は permissions.deny か PreToolUse Hooks を使う。
スキルの性質別に設定を分ける
スキルが何をするかによって、必要な権限は大きく変わる。
| スキルの種類 | 推奨する allowed-tools |
|---|---|
| 調査・情報収集 | Read, Glob, Grep のみ |
| レビュー・分析 | Read, Glob, Grep, Bash(git log) 等 |
| コード生成 | Read, Write, Edit |
| デプロイ・実行系 | 個別に精査して最小限に絞る |
「このスキルに本当にBashが必要か」を最初に問う習慣をつけると、過剰な権限を防げる。
② PreToolUseでコマンドの中身を検査する
allowed-tools はコマンドのプレフィックスで制限するが、より細かい制御が必要なときはHooksを使う。
PreToolUseフックはツールが実行される直前に呼ばれるため、コマンドの内容を見て止めることができる。
Hooksの設定(settings.json)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/hooks/check_bash.py"
}
]
}
]
}
}
チェックスクリプト(check_bash.py)
import json
import sys
input_data = json.loads(sys.stdin.read())
cmd = input_data.get("command", "")
# ブロックするパターン
dangerous_patterns = [
"rm -rf",
"git push --force",
"git reset --hard",
"DROP TABLE",
"DELETE FROM",
"> /dev/",
"chmod 777",
]
for pattern in dangerous_patterns:
if pattern in cmd:
print(json.dumps({
"decision": "block",
"reason": f"危険なコマンドをブロック: {pattern}"
}))
sys.exit(0)
このスクリプトが危険なパターンを検出すると、Claude Codeはコマンドを実行せずにユーザーに理由を表示して止まる。
③ 二段構えの設計
allowed-tools と Hooks はそれぞれ別の役割を持つ。組み合わせることで漏れのないセキュリティ設計になる。
スキル実行
↓
allowed-tools チェック(ツールの種類で判断)
└── 許可されていないツール → 確認ダイアログ表示
↓
PreToolUse フック(コマンドの中身で判断)
└── 危険なパターン → ブロック+理由表示
↓
ツール実行
| 層 | 何を判断するか | 設定場所 |
|---|---|---|
| allowed-tools | ツールの種類・コマンドプレフィックス | スキルのSKILL.md |
| PreToolUse Hook | コマンドの中身・引数・オプション | settings.json + スクリプト |
allowed-toolsだけでは不十分な理由:
allowed-tools はあくまで「自動承認リスト」であり、リスト外のコマンドは確認ダイアログが出るだけで実行は可能。またグローバルの permissions.allow が優先されるため、確実なブロックにはならない。git push --force のような危険なコマンドを絶対に通したくない場合はHooksが必要。
Hooksだけでは不十分な理由:
パターンの書き漏れや想定外のコマンド形式があると素通りする。allowed-tools で先にツール種別を絞っておくと、そもそもHooksが呼ばれるケースを減らせて管理しやすくなる。
④ スキル別のセキュリティポリシーを決める
スキルが増えてきたら、スキルごとにセキュリティレベルを定義しておくと管理しやすくなる。
レベル1:読み取り専用スキル
# SKILL.md
allowed-tools: ["Read", "Glob", "Grep"]
調査・ドキュメント参照・コードリーディング系のスキルはこのレベルで十分。書き込み系ツールを一切含めない。
レベル2:生成・編集スキル
# SKILL.md
allowed-tools: ["Read", "Glob", "Grep", "Write", "Edit"]
コード生成・ドキュメント作成系。Bashを含めないことでシステム操作のリスクをゼロにできる。
レベル3:実行系スキル(Bash含む)
# SKILL.md
allowed-tools:
- Read
- Bash(git status)
- Bash(git log)
- Bash(npm test)
- Bash(npm run lint)
Bashが必要なスキルは必要最小限のコマンドだけを列挙する。グローバルのHooksと組み合わせて二段構えにする。
⑤ ブラウザ操作系スキルは用途を絞る
playwright-cli のようなブラウザ自動操作ツールは特に注意が必要。コマンドのプレフィックスで Bash(playwright-cli:*) と絞っていても、ブラウザ自体はどのサイトにもアクセスでき、認証情報の入力やセッションの乗っ取りリスクも含むフォームに何でも入力できる。
たとえば AWS Pricing Calculator を操作するスキルを作った場合、playwright-cli open の向き先が calculator.aws に限定されていなければ、原理的には別サイトへの遷移やログインフォームへの入力も可能になる。
HooksでURLを縛る:
settings.jsonで if フィールドを使って playwright-cli open のときだけフックを発火させる。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(playwright-cli open*)",
"command": "python3 ~/.claude/hooks/check_playwright_url.py"
}
]
}
]
}
}
# ~/.claude/hooks/check_playwright_url.py
import json, sys
input_data = json.loads(sys.stdin.read())
cmd = input_data.get("tool_input", {}).get("command", "")
# playwright-cli open のURLを特定サイトに限定
if "playwright-cli open" in cmd:
allowed_urls = ["calculator.aws"]
if not any(url in cmd for url in allowed_urls):
print(json.dumps({
"decision": "block",
"reason": f"playwright-cli: 許可されていないURLへのアクセス: {cmd}"
}))
sys.exit(0)
ブラウザ操作スキルの設計原則:
--headedを必須にする(ユーザーが操作を目視できる)- 操作対象サイトをHooksでURLレベルで制限する
fill・click系の操作が必要な場合は許可リスト方式で制限する
スキルを「特定サイト専用」として設計し、そのサイト以外へのアクセスをHooksで弾く構成が最も安全。
まとめ
スキルのセキュリティ設計の原則は4つ:
allowed-toolsは必要最小限に絞る——Bashを丸ごと許可しない。コマンドプレフィックスで制限する- グローバルのPreToolUse Hooksで中身を検査する——スキル側でなくHooks側で一括管理する
- スキルの性質でセキュリティレベルを決める——読み取り専用・生成・実行系で permitted tools を分ける
- ブラウザ操作系スキルはURLレベルで制限する——
playwright-cli系は操作対象サイトをHooksで縛る
スキル側はシンプルに保って、セキュリティポリシーはHooksに集約する設計が保守しやすい。
← 第6回:マルチエージェント構成を作る——役割分担の設計 | 第8回:コンテキスト管理の技術——トークンを無駄にしない →