Claude Codeを使っていて、「あれ、このAI、自分のホームディレクトリのファイルまで自由に読み書きできちゃうの……?」ってドキドキしたこと、ありませんか?
便利なのはわかってるんですけど、プロジェクトと関係ないフォルダまで覗かれるのは、なんだか落ち着かないですよね。
Claude Codeの「フック(hooks)」と「パーミッション(permissions)」を組み合わせれば、操作範囲をカレントディレクトリ配下だけに、ぎゅっと制限できます。
僕が実際にやった手順を、ハマったところもそのまま共有しますね。
そもそも、何を制限したいのか
Claude Codeには、ざっくり5種類の「ツール」があります。
- Bash — ターミナルコマンドの実行
- Read — ファイルの読み取り
- Edit / Write — ファイルの編集・作成
- Glob — ファイル名でのパターン検索
- Grep — ファイル内容の検索
デフォルトだと、これらのツールがかなり広い範囲にアクセスできてしまうんです。
たとえば ls /Users/自分のユーザー名/Documents/ とか、普通に実行できちゃう。あっ、それはちょっと……ってなりますよね。
じゃ、どうすれば、いいのか?
やりたいことはシンプルです。
- Claude Codeを開いたフォルダ → 自由に操作OK
- それより上の階層 → 全部ブロック
この「ここから先は触らんといて」っていう境界線を、はっきり引きたいわけです。
macOS Tahoeではサンドボックスが動かない問題
Claude Codeには sandbox という設定があって、settings.json で有効にできます。
"sandbox": {
"enabled": true
}
「よし、これで安心やな」と思って、Bashコマンドを実行すると……
sandbox-exec: sandbox_apply: Operation not permitted
ここでハマりました。
ls すら動かない。pwd も動かない。
調べてみると、macOS Tahoe(Darwin 25.x)では sandbox-exec の挙動が制限されていて、Claude Codeのサンドボックス機能がまるごと使えない状態でした。
というわけで、サンドボックスは諦めて、別のアプローチでセキュリティを確保することにしました。
3層構造でセキュリティを確保する
サンドボックスの代わりに、以下の3つの仕組みを組み合わせます。
- パーミッション(permissions) —
settings.jsonのallow/denyルール - フック(hooks) — ツール実行前にシェルスクリプトで検証する仕組み
- 設定ファイル保護 — セキュリティ設定自体を Claude Code から変更できなくする
この3つが揃うことで、サンドボックスなしでもしっかり制限できるらしいです。
設定ファイルの全体像
完成形のファイル構成はこんな感じです。
.claude/
├── settings.json # パーミッション + フック定義
├── settings.local.json # サンドボックス無効化
└── hooks/
├── validate-command.sh # Bash用の検証
├── validate-file-access.sh # Read/Edit/Write用の検証
└── validate-search-access.sh # Glob/Grep用の検証
3つのフックスクリプトで、Claude Codeの全ツールをカバーしています。
ステップ1:サンドボックスを無効化する
まず .claude/settings.local.json を編集します。
{
"permissions": {
"allow": [
"Skill(update-config)",
"WebSearch"
],
"defaultMode": "acceptEdits"
},
"sandbox": {
"enabled": false
}
}
sandbox.enabled を false にするだけです。これで Bash が動くようになります。
「え、サンドボックス切って大丈夫なん?」って思いますよね。
大丈夫です。ここからフックで守っていきます。
ステップ2:パーミッションを設定する
.claude/settings.json の permissions セクションで、基本的なルールを定義します。
"permissions": {
"deny": [
"Read(./.env)",
"Edit(./.env)",
"Write(./.env)",
"Edit(./.claude/settings.json)",
"Write(./.claude/settings.json)",
"Edit(./.claude/hooks/**)",
"Write(./.claude/hooks/**)",
"Bash(rm -rf *)",
"Bash(sudo *)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(git push --force *)",
"Bash(git reset --hard *)"
],
"allow": [
"Edit(./**)",
"Write(./**)",
"Read(./**)"
]
}
ポイントは2つです。
allowで./**(カレントディレクトリ配下)のみを許可denyで機密ファイルと危険コマンドをブロック
で、ここで特に重要なのが、.claude/settings.json と .claude/hooks/ 自体への Edit / Write を deny に入れているということ。
これがないと、Claude Codeがセキュリティ設定そのものを書き換えちゃう可能性があるんです。鍵をかけたら、その鍵自体も守らないといけない、という感じですね。
ステップ3:フックでBashコマンドを検証する
パーミッションだけだと、実はBashコマンドの中身までは制限できません。
# パーミッションでは防げない例
cat /Users/自分/Documents/important.txt
cd /Users/自分/Desktop && ls
こういう絶対パスを使った操作は、Bashツール自体は許可されているので通っちゃう。
ここをフックで守ります。
validate-command.sh(Bash用)
settings.json の hooks セクションで、Bashツールにフックを登録します。
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/validate-command.sh"
}
]
}
]
}
フックスクリプトの重要な部分を見ていきますね。
まず、cd で外部に逃げようとするパターンをブロックします。
# カレントディレクトリを取得
PROJECT_DIR="$(pwd)"
# cd で外部に移動しようとしたらブロック
if echo "$COMMAND" | grep -qE '\bcd[[:space:]]+/'; then
CD_TARGET=$(echo "$COMMAND" | grep -oE '\bcd[[:space:]]+/[^[:space:];&|"]*' | head -1 | sed 's/cd[[:space:]]*//')
if [ -n "$CD_TARGET" ]; then
case "$CD_TARGET" in
"$PROJECT_DIR"|"$PROJECT_DIR"/*) ;; # プロジェクト内はOK
/dev/*|/tmp/*|/private/tmp/*) ;; # システムパスはOK
*) echo "BLOCKED: カレントディレクトリ外への移動は許可されていません" >&2; exit 2 ;;
esac
fi
fi
case 文で、プロジェクトディレクトリ内とシステムパス(/usr/bin とか /dev/null とか)だけを許可しています。
node --version みたいなシステムコマンドは、ちゃんと動きます。ここは安心。
さらに、絶対パスでのファイルアクセスもチェックしています。
# 絶対パスの引数をすべて抽出してチェック
ABS_PATHS=$(echo "$COMMAND" | grep -oE '(^|[[:space:]=])/[a-zA-Z][^[:space:];&|"]*' | sed 's/^[[:space:]]*//')
if [ -n "$ABS_PATHS" ]; then
while IFS= read -r APATH; do
case "$APATH" in
"$PROJECT_DIR"|"$PROJECT_DIR"/*) ;;
/dev/*|/tmp/*|/private/tmp/*) ;;
/usr/*|/bin/*|/sbin/*|/opt/*) ;;
*) echo "BLOCKED: カレントディレクトリ外の絶対パスへのアクセスは許可されていません" >&2; exit 2 ;;
esac
done <<< "$ABS_PATHS"
fi
これに加えて、../ でのパストラバーサル、sudo や curl の実行、.env や .pem への機密ファイルアクセスもブロックしています。
抜け道を一つひとつ塞いでいく感じですね。
ステップ4:Read/Edit/Writeのアクセスを制限する
Bashだけじゃなくて、Claude Code の Read / Edit / Write ツールも制限しておく必要があります。
これらのツールは Bash を経由せずに直接ファイルにアクセスするので、Bash用のフックだけでは守れないんです。
validate-file-access.sh(Read/Edit/Write用)
PROJECT_DIR="$(pwd)"
# 絶対パスの場合、プロジェクト外ならブロック
if echo "$FILE_PATH" | grep -qE '^/'; then
case "$FILE_PATH" in
"$PROJECT_DIR"|"$PROJECT_DIR"/*) ;;
/dev/*|/tmp/*|/private/tmp/*) ;;
*) echo "BLOCKED: カレントディレクトリ外のファイルアクセスは許可されていません" >&2; exit 2 ;;
esac
fi
考え方は Bash用と同じです。フック登録は settings.json で。
{
"matcher": "Read|Edit|MultiEdit|Write",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/validate-file-access.sh"
}
]
}
ステップ5:Glob/Grepの検索範囲を制限する
ここ、意外と見落としがちなポイントです。
Glob(ファイル名検索)と Grep(ファイル内容検索)には、デフォルトではフックもパーミッション制限もかかりません。
つまり、プロジェクト外のディレクトリも検索できちゃう。あっ、それはまずい。
validate-search-access.sh(Glob/Grep用)
PROJECT_DIR="$(pwd)"
SEARCH_PATH=$(echo "$INPUT" | jq -r '.tool_input.path // empty' 2>/dev/null)
# path が未指定ならカレントディレクトリ(OK)
[ -z "$SEARCH_PATH" ] && exit 0
# 絶対パスの場合、プロジェクト外ならブロック
if echo "$SEARCH_PATH" | grep -qE '^/'; then
case "$SEARCH_PATH" in
"$PROJECT_DIR"|"$PROJECT_DIR"/*) ;;
*) echo "BLOCKED: カレントディレクトリ外の検索は許可されていません" >&2; exit 2 ;;
esac
fi
フック登録はこう。
{
"matcher": "Glob|Grep",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/validate-search-access.sh"
}
]
}
これで全ツールにフックが設定できました。
動作テスト — ちゃんとブロックされるか確認する
設定が完了したら、Claude Codeを再起動してテストします。
通るべきもの
| テスト | 結果 |
|---|---|
pwd | ✅ カレントディレクトリが表示される |
ls | ✅ ファイル一覧が表示される |
node --version | ✅ バージョンが表示される |
| サブディレクトリのファイル読み取り | ✅ 正常に読める |
ブロックされるべきもの
| テスト | 結果 |
|---|---|
ls /Users/自分/Documents/ | ❌ BLOCKED |
cd /Users/自分/Desktop | ❌ BLOCKED |
Read で外部ファイル指定 | ❌ BLOCKED |
Glob で外部ディレクトリ検索 | ❌ BLOCKED |
全部ちゃんとブロックされました。ふぅ。これで安心です。
セットアップスクリプトで一括適用できます
「5つもファイルを手動で編集するの、めんどくさい……」ってなりますよね。
そこで、一括セットアップスクリプトも用意しました。
chmod +x setup-security.sh
./setup-security.sh
スクリプトは何度実行しても同じ結果になる仕組みになっており、「あれ、ちゃんと適用されたっけ?」と不安になっても、安心して再実行できます。既に適用済みの変更は自動でスキップされます。
↓ zipで圧縮しています
#!/bin/bash
# ============================================================
# Claude Code セキュリティ設定一括スクリプト
#
# 実行内容:
# 1. settings.local.json のサンドボックス無効化
# 2. validate-command.sh に絶対パス制限を追加
# 3. validate-file-access.sh にカレントディレクトリ制限を追加
# 4. validate-search-access.sh を新規作成(Glob/Grep制限)
# 5. settings.json に Glob/Grep フックを追加
# ============================================================
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CLAUDE_DIR="$SCRIPT_DIR/.claude"
HOOKS_DIR="$CLAUDE_DIR/hooks"
echo "=== Claude Code セキュリティ設定を適用します ==="
echo ""
# ── 1. settings.local.json のサンドボックス無効化 ──
echo "[1/5] settings.local.json を更新..."
cat > "$CLAUDE_DIR/settings.local.json" << 'EOF'
{
"permissions": {
"allow": [
"Skill(update-config)",
"WebSearch"
],
"defaultMode": "acceptEdits"
},
"sandbox": {
"enabled": false
}
}
EOF
echo " → サンドボックスを無効化しました"
# ── 2. validate-command.sh に絶対パス制限を追加 ──
echo "[2/5] validate-command.sh に絶対パス制限を追加..."
if grep -q 'カレントディレクトリ外の絶対パス制限' "$HOOKS_DIR/validate-command.sh"; then
echo " → 既に追加済みです。スキップします"
else
python3 << 'PYEOF'
import sys
insert_block = r'''# ── カレントディレクトリ外の絶対パス制限 ──
PROJECT_DIR="$(pwd)"
# cd による外部ディレクトリへの移動をブロック
if echo "$COMMAND" | grep -qE '\bcd[[:space:]]+/'; then
CD_TARGET=$(echo "$COMMAND" | grep -oE '\bcd[[:space:]]+/[^[:space:];&|"]*' | head -1 | sed 's/cd[[:space:]]*//')
if [ -n "$CD_TARGET" ]; then
case "$CD_TARGET" in
"$PROJECT_DIR"|"$PROJECT_DIR"/*) ;; # プロジェクト内はOK
/dev/*|/tmp/*|/private/tmp/*) ;; # システムパスはOK
*) echo "BLOCKED: カレントディレクトリ外への移動は許可されていません: $CD_TARGET" >&2; exit 2 ;;
esac
fi
fi
# ファイル操作コマンドでの外部絶対パス参照をブロック
ABS_PATHS=$(echo "$COMMAND" | grep -oE '(^|[[:space:]=])/[a-zA-Z][^[:space:];&|"]*' | sed 's/^[[:space:]]*//')
if [ -n "$ABS_PATHS" ]; then
while IFS= read -r APATH; do
case "$APATH" in
"$PROJECT_DIR"|"$PROJECT_DIR"/*) ;; # プロジェクト内
/dev/*|/tmp/*|/private/tmp/*) ;; # システム許可パス
/usr/*|/bin/*|/sbin/*|/opt/*) ;; # システムコマンドパス
/etc/alternatives/*) ;; # alternatives
*) echo "BLOCKED: カレントディレクトリ外の絶対パスへのアクセスは許可されていません: $APATH" >&2; exit 2 ;;
esac
done <<< "$ABS_PATHS"
fi
'''
marker = '# ── パストラバーサル ──'
filepath = '.claude/hooks/validate-command.sh'
with open(filepath, 'r') as f:
content = f.read()
if marker in content:
content = content.replace(marker, insert_block + marker)
with open(filepath, 'w') as f:
f.write(content)
else:
print(f"ERROR: マーカー '{marker}' が見つかりません", file=sys.stderr)
sys.exit(1)
PYEOF
echo " → 追加しました"
fi
# ── 3. validate-file-access.sh にカレントディレクトリ制限を追加 ──
echo "[3/5] validate-file-access.sh にカレントディレクトリ制限を追加..."
if grep -q 'カレントディレクトリ外のファイルアクセス制限' "$HOOKS_DIR/validate-file-access.sh"; then
echo " → 既に追加済みです。スキップします"
else
python3 << 'PYEOF'
import sys
insert_block = r'''# ── カレントディレクトリ外のファイルアクセス制限 ──
PROJECT_DIR="$(pwd)"
# 絶対パスの場合、プロジェクト外ならブロック
if echo "$FILE_PATH" | grep -qE '^/'; then
case "$FILE_PATH" in
"$PROJECT_DIR"|"$PROJECT_DIR"/*) ;; # プロジェクト内はOK
/dev/*|/tmp/*|/private/tmp/*) ;; # システム許可パス
*) echo "BLOCKED: カレントディレクトリ外のファイルアクセスは許可されていません: $FILE_PATH" >&2; exit 2 ;;
esac
fi
# 相対パスでのパストラバーサル(既存チェックの強化)
if echo "$FILE_PATH" | grep -qE '(^|/)\.\.(/|$)'; then
echo "BLOCKED: パストラバーサルが検出されました: $FILE_PATH" >&2
exit 2
fi
'''
marker = '# ── .claude/ 設定ファイルの改ざん防止 ──'
filepath = '.claude/hooks/validate-file-access.sh'
with open(filepath, 'r') as f:
content = f.read()
if marker in content:
content = content.replace(marker, insert_block + marker)
with open(filepath, 'w') as f:
f.write(content)
else:
print(f"ERROR: マーカー '{marker}' が見つかりません", file=sys.stderr)
sys.exit(1)
PYEOF
echo " → 追加しました"
fi
# ── 4. validate-search-access.sh を新規作成 ──
echo "[4/5] validate-search-access.sh を作成..."
cat > "$HOOKS_DIR/validate-search-access.sh" << 'SCRIPT'
#!/bin/bash
# ============================================================
# PreToolUse フック: Glob/Grep のカレントディレクトリ外アクセス制限
#
# exit 0 = 許可 | exit 2 = ブロック
# ============================================================
INPUT=$(cat)
if ! command -v jq &> /dev/null; then
echo "BLOCKED: jq が未インストールのためセキュリティチェックを実行できません。brew install jq を実行してください。" >&2
exit 2
fi
PROJECT_DIR="$(pwd)"
# path パラメータを取得(検索対象ディレクトリ)
SEARCH_PATH=$(echo "$INPUT" | jq -r '
.tool_input.path //
empty
' 2>/dev/null)
# path が未指定ならカレントディレクトリ(OK)
[ -z "$SEARCH_PATH" ] && exit 0
# 絶対パスの場合、プロジェクト外ならブロック
if echo "$SEARCH_PATH" | grep -qE '^/'; then
case "$SEARCH_PATH" in
"$PROJECT_DIR"|"$PROJECT_DIR"/*) ;;
*) echo "BLOCKED: カレントディレクトリ外の検索は許可されていません: $SEARCH_PATH" >&2; exit 2 ;;
esac
fi
# 相対パスでのパストラバーサル
if echo "$SEARCH_PATH" | grep -qE '(^|/)\.\.(/|$)'; then
echo "BLOCKED: パストラバーサルが検出されました: $SEARCH_PATH" >&2
exit 2
fi
exit 0
SCRIPT
chmod +x "$HOOKS_DIR/validate-search-access.sh"
echo " → 作成しました"
# ── 5. settings.json に Glob/Grep フックを追加 ──
echo "[5/5] settings.json に Glob/Grep フックを追加..."
if grep -q 'Glob|Grep' "$CLAUDE_DIR/settings.json"; then
echo " → 既に追加済みです。スキップします"
else
jq '.hooks.PreToolUse += [{
"matcher": "Glob|Grep",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/validate-search-access.sh"
}
]
}]' "$CLAUDE_DIR/settings.json" > "$CLAUDE_DIR/settings.json.tmp"
mv "$CLAUDE_DIR/settings.json.tmp" "$CLAUDE_DIR/settings.json"
echo " → 追加しました"
fi
echo ""
echo "=== 設定完了 ==="
echo ""
echo "変更内容:"
echo " [1] settings.local.json → サンドボックス無効化"
echo " [2] validate-command.sh → Bash の絶対パス制限追加"
echo " [3] validate-file-access.sh → Read/Edit/Write のディレクトリ制限追加"
echo " [4] validate-search-access.sh → Glob/Grep のディレクトリ制限(新規)"
echo " [5] settings.json → Glob/Grep フック登録"
echo ""
echo "Claude Code を再起動してテストしてください。"
まとめ
Claude Codeの操作範囲をカレントディレクトリだけに制限するポイントをまとめます。
- macOS Tahoe では
sandbox-execが動かないので、フック + パーミッションで代替する - Bash、Read/Edit/Write、Glob/Grep の3種類すべてにフックを設定する
- システムコマンド(
/usr/bin等)は許可しつつ、ユーザーディレクトリへのアクセスはブロック - セキュリティ設定ファイル自体を Claude Code から変更できないようにする(ここ大事)
- フックの仕組みはシンプルで、
exit 0で許可、exit 2でブロック
AIに自由にコードを書いてもらいつつ、操作範囲はきっちり制限する。
「自由にやっていいけど、この部屋の中だけね」っていう感じです。この「自由」と「安全」のバランスが、Claude Codeを安心して使うコツかなと思います。



![[日刊 20131207 Vol.155] ”ひらがな”と”カタカナ”の違い](https://mono96.jp/wp-content/uploads/2013/12/IMG_5054-1-150x150.jpg)



