はじめに
この記事は、Claude Code のセキュリティガードレールを「概念」ではなく「動くコード」で示すことを目的にしています。
「ガードレールが必要だとは分かった。でも何をどう書けばいいのか」という段階の方に向けて書いています。
settings.json の permissions 設計、PreToolUse hook による危険コマンドの遮断、確認ゲートの実装、機密情報の流出防止という4テーマを扱います。
各セクションにコピペで使えるサンプルを置いています。
対象読者
- Claude Code を業務や個人開発に導入済みで、安全に回す仕組みを整えたい方
- 不可逆操作(ファイル削除・外部送信・コミット)をエージェントに任せることに不安がある方
- セキュリティ担当として、AI駆動開発のリスク制御の実例を探している方
実行環境・前提
- Claude Code(2026年6月時点の仕様に基づいています)
- macOS / Linux(スクリプトは bash 前提)
-
~/.claude/配下にグローバル設定、リポジトリ直下の.claude/にプロジェクト設定を置く構成
本記事のサンプルコードは動作原理の説明を目的にしています。
実際の運用では、自分の環境・権限設計に合わせてカスタマイズしてください。
特に hook スクリプトは exit 2 の動作(Claude Code のバージョンにより挙動が変わる可能性があります)を必ず事前確認してください。
なぜガードレールが要るのか
Claude Code はファイル読み書き・Bash 実行・外部 API コールを自律的に組み合わせて仕事を進めます。
これは開発速度を大きく上げてくれますが、人間のレビューを挟まずに「やってはいけないこと」をやり切ってしまう可能性も持ちます。
よくある事故の類型は、おおむね次の3つに集約されます。
-
破壊的操作の実行 —
rm -rfによるディレクトリ丸ごと削除、本番 DB への直接 UPDATE など - 外部への意図しない送信 — API キーや認証トークンを含む出力のコミット、外部サービスへの送信
- 権限の取りすぎ — 必要最小限を超えたファイルアクセスや、意図しないネットワーク操作
いずれも「あとから取り消せない」系の事故です。
ガードレールの目的は「AI を制限すること」ではなく、「取り返しのつかない操作の前に人間が判断できる余地を作ること」です。
1. settings.json の permissions で最小権限を設計する
Claude Code の動作は ~/.claude/settings.json(グローバル)と .claude/settings.json(プロジェクト)で制御できます。
permissions の allow と deny を使って、エージェントが触れてよいリソースを明示的に設計します。
基本構造
{
"permissions": {
"allow": [],
"deny": []
}
}
allow に列挙したルールだけを許可し、残りは拒否するホワイトリスト方式と、deny で特定パターンだけを禁止するブラックリスト方式を組み合わせられます。
グローバル設定のサンプル
まずグローバル設定で「絶対にやらせない」ことを宣言します。
プロジェクト設定はこの上に重ねる形になります。
{
"permissions": {
"deny": [
"Bash(rm -rf*)",
"Bash(sudo rm*)",
"Bash(git push --force*)",
"Bash(git push -f*)",
"Bash(chmod 777*)",
"Bash(curl * | bash)",
"Bash(wget * | bash)",
"Bash(eval*)"
]
}
}
ここでは「実行させたくないコマンドパターン」を deny に並べています。
rm -rf 系、フォースプッシュ、パーミッション全開放、curl | bash のようなスクリプト直接実行が主なターゲットです。
プロジェクト設定のサンプル
プロジェクト固有の設定はリポジトリ直下の .claude/settings.json に書きます。
グローバルの deny に加えて、このプロジェクトでは何を許可するかを絞り込みます。
{
"permissions": {
"allow": [
"Bash(npm run*)",
"Bash(npx*)",
"Bash(git status)",
"Bash(git diff*)",
"Bash(git add*)",
"Bash(git commit*)",
"Bash(git log*)",
"Bash(git checkout*)",
"Bash(git branch*)"
],
"deny": [
"Bash(git push*)",
"WebFetch(domain:internal.example.com)",
"WebFetch(domain:staging.example.com)"
]
}
}
ポイントは git push をプロジェクトレベルで明示的に deny していることです。
コミットまでは任せて、プッシュは人間が確認してから行う、という境界を引いています。
設計の考え方
permissions を設計するときは「この操作が Claude Code から起動されたら困るか」を1つずつ問い直すのが効果的です。
| 操作の種類 | allow / deny の判断基準 |
|---|---|
| 読み取り系(git log, cat, find) | 基本 allow |
| ローカル書き込み系(git add, npm install) | プロジェクト単位で allow |
| 外部送信系(git push, curl POST) | 原則 deny、手動で実行 |
| 削除・上書き系(rm, git reset --hard) | 原則 deny |
| 実行権限変更(chmod, chown) | 原則 deny |
2. PreToolUse hook で危険コマンドを遮断する
settings.json の deny は Claude Code のレイヤーで動きます。
hook はその下のシェルレイヤーで、より細かいパターンマッチと即座の中断が可能です。
Claude Code は特定のライフサイクルイベントで外部スクリプトを呼び出せます。
PreToolUse は Claude がツールを呼び出す直前に発火します。
スクリプトが exit 2 を返すと、Claude Code はそのツール呼び出しをキャンセルします。
hook の設置
.claude/settings.json で hook スクリプトのパスを指定します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/guard-dangerous-bash.sh"
}
]
}
]
}
}
危険コマンド遮断スクリプトのサンプル
#!/usr/bin/env bash
# PreToolUse hook: 危険な Bash コマンドを遮断する
# exit 2 を返すと Claude Code はそのツール呼び出しをキャンセルする
# stdin から JSON を受け取る
INPUT=$(cat)
# tool_input.command を抽出する
COMMAND=$(echo "$INPUT" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data.get('tool_input', {}).get('command', ''))
" 2>/dev/null)
if [ -z "$COMMAND" ]; then
exit 0
fi
# 禁止パターン(正規表現)
DANGEROUS_PATTERNS=(
'rm[[:space:]]+-[a-zA-Z]*r[a-zA-Z]*f'
'rm[[:space:]]+-[a-zA-Z]*f[a-zA-Z]*r'
'sudo[[:space:]]+rm'
':(){:|:&};:'
'mkfs\.'
'dd[[:space:]]+if='
'>[[:space:]]*/dev/sd'
'curl[^|]*\|[[:space:]]*(ba)?sh'
'wget[^|]*\|[[:space:]]*(ba)?sh'
'chmod[[:space:]]+777'
'git[[:space:]]+push[[:space:]]+(-f|--force)'
)
for PATTERN in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qE "$PATTERN"; then
# stderr へのメッセージは Claude Code のログに表示される
echo "Guard: 危険なコマンドパターンを検出したため実行をブロックしました: $COMMAND" >&2
echo "Guard: パターン: $PATTERN" >&2
exit 2
fi
done
exit 0
スクリプトは stdin から JSON を受け取り、コマンド文字列をパターンマッチします。
禁止パターンに引っかかった場合は exit 2 でキャンセルし、理由を stderr に書きます。
stderr の内容は Claude Code の出力に表示されるため、なぜ止まったかを確認できます。
パターンリストの補足
上記サンプルには代表的なパターンを入れています。
自分の環境に合わせて追加・削除を検討するパターンの例として、以下があります。
-
DROP TABLE/TRUNCATEを含む SQL の直接実行 -
.envファイルを読んで外部 URL に送るような組み合わせ -
crontabの書き換え
「禁止パターンを増やせば増やすほど安全」ではなく、「止めすぎると Claude Code が有効に使えなくなる」というトレードオフがあります。
パターンは実運用を通じて少しずつ育てていくものとして考えると、維持しやすくなります。
3. 不可逆・外部送信・機密操作の前に確認ゲートを挟む
hook でブロックするのとは別に、「実行する前に人間に確認を取る」パターンも有効です。
exit 2(遮断)の代わりに、操作の内容を表示して y/N を求めるスクリプトを使います。
確認ゲートのサンプル
#!/usr/bin/env bash
# PreToolUse hook: 不可逆操作の前に確認を求める
# exit 2 で遮断、exit 0 で通過
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data.get('tool_input', {}).get('command', ''))
" 2>/dev/null)
if [ -z "$COMMAND" ]; then
exit 0
fi
# 確認を求める操作のパターン
CONFIRM_PATTERNS=(
'git[[:space:]]+push'
'git[[:space:]]+reset[[:space:]]+--hard'
'git[[:space:]]+clean[[:space:]]+-f'
'git[[:space:]]+stash[[:space:]]+drop'
'npm[[:space:]]+publish'
'docker[[:space:]]+push'
)
NEEDS_CONFIRM=false
for PATTERN in "${CONFIRM_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qE "$PATTERN"; then
NEEDS_CONFIRM=true
break
fi
done
if [ "$NEEDS_CONFIRM" = false ]; then
exit 0
fi
# TTY に対して確認ダイアログを出す
echo "" > /dev/tty
echo "=== Claude Code: 確認が必要な操作 ===" > /dev/tty
echo "コマンド: $COMMAND" > /dev/tty
echo "" > /dev/tty
echo "この操作を実行してよいですか? [y/N]: " > /dev/tty
read -r ANSWER < /dev/tty
if [[ "$ANSWER" =~ ^[Yy]$ ]]; then
echo "Confirm: ユーザーが承認しました" >&2
exit 0
else
echo "Confirm: ユーザーがキャンセルしました" >&2
exit 2
fi
/dev/tty を使うことで、Claude Code のパイプ処理とは独立してターミナルに直接表示・入力を受け取れます。
確認ダイアログが stdout を汚さないため、Claude Code の JSON 通信に干渉しません。
遮断と確認の使い分け
| 操作の性質 | 推奨する対応 |
|---|---|
| 絶対に実行させたくない(rm -rf 等) | hook で exit 2 遮断 |
| 普段は不要だが状況次第で必要(git push 等) | 確認ゲートを挟む |
| 頻繁に使うが内容確認が要る(DB マイグレーション等) | 確認ゲートを挟む |
| 問題のない定型操作(git status 等) | 制限なし |
「絶対にダメ」と「確認してから」を分けることで、過剰な制限なく必要なゲートだけを維持できます。
4. 機密情報(API キー・トークン)を出力・コミットさせない
機密情報の流出は「書いてしまってからでは遅い」問題です。
2段構えで対策します。
4-1. .gitignore と .claudeignore で読ませない
Claude Code は .claudeignore を参照します。
.env ファイル群やシークレットを含む可能性があるファイルを、そもそも読ませないようにします。
# 環境変数・シークレット
.env
.env.*
*.pem
*.key
*.p12
*.pfx
secrets/
credentials/
# 認証情報ファイル
.aws/credentials
.ssh/
*.token
.gitignore に追加済みのファイルは Claude Code も参照しないことが多いですが、.claudeignore で明示することで意図を明確にできます。
4-2. PostToolUse hook でコミット内容をスキャンする
git add や git commit が完了した後のタイミングで、ステージング済みコンテンツを確認します。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/scan-staged-secrets.sh"
}
]
}
]
}
}
#!/usr/bin/env bash
# PostToolUse hook: ステージング済みファイルに機密情報が含まれていないかスキャンする
# PostToolUse はすでに実行済みのため、exit 2 は意味を持たない
# ここでは警告を出すだけにする
# git add が実行されたコンテキストかどうか確認
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data.get('tool_input', {}).get('command', ''))
" 2>/dev/null)
# git add か git commit に関係するコマンドでなければスキップ
if ! echo "$COMMAND" | grep -qE 'git[[:space:]]+(add|commit)'; then
exit 0
fi
# ステージング済みコンテンツをスキャン
STAGED_DIFF=$(git diff --cached 2>/dev/null)
if [ -z "$STAGED_DIFF" ]; then
exit 0
fi
# 機密情報のパターン
SECRET_PATTERNS=(
'AKIA[0-9A-Z]{16}'
'sk-[a-zA-Z0-9]{32,}'
'ghp_[a-zA-Z0-9]{36}'
'xoxb-[0-9a-zA-Z-]+'
'Bearer[[:space:]]+[a-zA-Z0-9._-]{20,}'
'password[[:space:]]*=[[:space:]]*[^$\n]{8,}'
'secret[[:space:]]*=[[:space:]]*[^$\n]{8,}'
'api_key[[:space:]]*=[[:space:]]*[^$\n]{8,}'
)
FOUND=false
for PATTERN in "${SECRET_PATTERNS[@]}"; do
if echo "$STAGED_DIFF" | grep -qiE "$PATTERN"; then
echo "SecretScan: 警告 — ステージング済みファイルに機密情報の可能性があるパターンを検出しました" >&2
echo "SecretScan: パターン: $PATTERN" >&2
echo "SecretScan: git reset HEAD <ファイル名> でアンステージしてから確認してください" >&2
FOUND=true
fi
done
if [ "$FOUND" = true ]; then
# PostToolUse では exit 2 は効かないため、警告のみ
# 必要に応じてアンステージを自動実行するロジックを足す
exit 1
fi
exit 0
PostToolUse の段階ではすでに git add が完了しています。
exit 2 で操作を止めることはできません。
このスクリプトの役割は「警告を出してユーザーに気づかせること」です。
コミットを止めたい場合は PreToolUse で git commit のタイミングに確認ゲートを挟んでください。
4-3. CLAUDE.md でエージェントへの指示として書く
hook やパーミッション設定に加えて、CLAUDE.md(Claude Code が読む設定ファイル)に直接ルールとして書くことも有効です。
## 機密情報の取り扱い
- `.env` ファイルの内容をそのままコード・コミット・出力に含めないこと
- API キー・認証トークン・パスワードは環境変数の参照(`process.env.API_KEY` 等)で扱い、値を直書きしないこと
- シークレットを含む可能性があるファイルを外部 URL に送らないこと
- コミットメッセージに機密情報を含めないこと
LLM は指示をある程度守ります。
hook で機械的に防ぐこととの両立が、より堅牢な構成になります。
5. 運用 Tips — ガードレールを"気づける形"にする
ガードレールを入れた後、「なぜ止まったか分からない」状態は運用コストになります。
検知したときに人間がすぐ気づける設計を意識すると、維持しやすくなります。
ログを残す
hook スクリプトで遮断したイベントをログファイルに書き出しておくと、後から振り返れます。
# ...(前述のスクリプトに以下を追加)
LOG_DIR="${HOME}/.claude/logs"
mkdir -p "$LOG_DIR"
LOG_FILE="${LOG_DIR}/guardrail-$(date +%Y-%m-%d).log"
log_event() {
local level="$1"
local message="$2"
echo "$(date -Iseconds) [$level] $message" >> "$LOG_FILE"
}
# 遮断した場合
log_event "BLOCKED" "コマンド: $COMMAND / パターン: $PATTERN"
echo "Guard: ブロックしました: $COMMAND" >&2
exit 2
ガードレールの対象外を明示する
「制限している」ことと「なぜ制限しているか」をドキュメント(CLAUDE.md)に書いておくと、チームで使うときに混乱が減ります。
## 自動で止まる操作(hook による遮断)
以下の操作は hook で自動遮断されます。止まった場合は手動で実行してください。
- rm -rf 系のコマンド
- git push --force
- curl / wget のパイプ実行(curl ... | bash 等)
- chmod 777
## 確認ゲートが出る操作
以下の操作は実行前に確認ダイアログが表示されます。
- git push(通常の push を含む)
- npm publish
- git reset --hard
定期的に見直す
ガードレールは「入れたら終わり」ではありません。
運用を続けていると「この制限は実は不要だった」「このパターンはまだ入っていなかった」という気づきが出てきます。
月に1度程度、ログを見て「何が止まっているか」を確認する習慣を持つと、過剰制限と抜け漏れの両方を発見しやすくなります。
まとめ
Claude Code のセキュリティガードレールを4テーマで整理しました。
| テーマ | 実装の起点 |
|---|---|
| 最小権限の設計 |
settings.json の permissions.deny から始める |
| 危険コマンドの遮断 |
PreToolUse hook + exit 2
|
| 不可逆操作の確認ゲート |
/dev/tty 経由の y/N 確認スクリプト |
| 機密情報の流出防止 |
.claudeignore + PostToolUse スキャン + CLAUDE.md への明文化 |
一度にすべてを導入する必要はありません。
「まず settings.json の deny を3行書く」だけでも、ノーガードより大きく変わります。
そこから実際に止まったログを見ながら、少しずつ育てていくのが現実的な進め方です。
ガードレールの目的は Claude Code を縛ることではなく、人間が関与すべきタイミングを設計することです。
「どこまで任せて、どこで確認するか」という境界を自分で決められると、AI 駆動開発の信頼性は大きく変わります。