playpark
ホーム会社概要サービスソリューションブログお知らせお問い合わせ
playpark

あらゆる仕事を楽しむ

会社概要サービスソリューションお問い合わせ特定商取引法に基づく表記

© 2019-2026 合同会社playpark All Rights Reserved.

  1. ホーム
  2. ブログ
  3. 技術Tips
  4. 【Claude Code Hooks 安全設計】170超のdenyルールとPreToolUseフックで「やらかし」を防ぐ実践パターン
ブログ一覧に戻る
技術Tips

【Claude Code Hooks 安全設計】170超のdenyルールとPreToolUseフックで「やらかし」を防ぐ実践パターン

Claude Code Hooksのprotect-branches.pyによるブランチ保護、auto-approve設計、170超のdenyルール分類を実コード付きで解説。PreToolUse・PostToolUse・Notificationの使い分けも紹介します。

2026年3月10日32分で読める
Claude CodeClaude Code Hooksセキュリティ設定settings.jsonAI開発環境
【Claude Code Hooks 安全設計】170超のdenyルールとPreToolUseフックで「やらかし」を防ぐ実践パターン

2026-03-07 追記: 本記事で紹介したHooksベースの安全設計は、現在Permissions + deny rulesベースの構成に移行しています。

「え、mainにpushされてる...誰が...あ、Claude Codeか」

AIエージェントにコードを書かせるの、もう日常ですよね。でも権限まわりの設計をサボると、ある日突然「本番ブランチに直pushされました」事件が起きる。人間ならgitのブランチルール叩き込まれてるけど、AIはそんな空気読めません(読めたら怖い)。

settings.jsonのpermissions.denyに「git push --forceは禁止ね」と書いておけば安心? いや、AIは賢いからgit push -fとかgit push origin HEAD:refs/heads/mainとか、微妙にパターンを変えてすり抜けてくるんですよ。正確には「すり抜けようとしている」わけじゃなくて、単にバリエーションが多すぎてdenyリストの網目から漏れるだけなんですが。結果は同じ。

「denyリスト書いたのに防げなかった」「毎回の承認ダイアログが多すぎて作業が進まない」...こんな経験、ありませんか?

私たちのdotfilesリポジトリでは、この問題に対して3つの防御レイヤーで対処しています。denyリスト(静的ブロック)、Pythonフック(動的検査)、auto-approveパターン(安全な操作の自動承認)。この記事では、実際に運用しているコードと設計意図を全部見せます。

この記事で学べること

  • settings.jsonのpermissions.denyを170超のルールに体系化する設計手法
  • protect-branches.pyの正規表現・refspecパース・gh API連携の仕組み
  • auto-approveフックで「安全な操作」だけを自動承認するパターン
  • PreToolUse / SessionStart / Notification、4種のHookの使い分け

前提知識

  • Claude Codeの基本操作を理解している
  • settings.jsonのhooks設定について概要を把握している
  • gitのrefspec記法(src:dst)を知っていると理解が早い

全体アーキテクチャ:3層の防御ライン

まず全体像から。Claude Codeのツール実行に対して、3つのレイヤーで安全性を確保しています。

レイヤー仕組み役割
L1: 静的denypermissions.denyパターンマッチで即座にブロック
L2: 動的フックPreToolUse + Pythonコマンド内容を解析して判定
L3: 自動承認PreToolUseのallow応答安全な操作の承認ダイアログを省略

L1で大半の危険操作を止め、L2でL1の隙間を埋め、L3で安全な操作のUXを改善する。**「止めるべきものは確実に止め、通すべきものはスムーズに通す」**がコンセプトです。

L1:170超のdenyルール設計

なぜこんなに多いのか

「git push --forceを禁止」だけで済むと思いますよね。ところがClaude Codeは以下のようなバリエーションでpushしてくることがある。

git push --force origin main        # 明示的なforce push
git push -f origin feature           # 短縮フラグ
git push origin HEAD:refs/heads/main # refspecで直接指定
git push --set-upstream origin main  # upstream設定と同時にpush
git push --force-with-lease origin HEAD:refs/heads/dev # 安全そうに見えて保護ブランチ宛て
git push origin main:main           # ブランチ名:ブランチ名形式
git push origin --delete main       # ブランチ削除

1つのdenyルールでは1パターンしかブロックできません。全バリエーションをカバーするには、組み合わせ爆発に付き合うしかないんです。

7カテゴリの分類体系

170超のルールを整理するために、以下の7カテゴリに分類しています。

1. Git危険操作(force push・保護ブランチ)

最も重要なカテゴリ。保護ブランチ(main, master, dev, develop, development)への全pushパターンを網羅します。

"Bash(git push --force:*)",
"Bash(git push -f:*)",
"Bash(git push origin HEAD:refs/heads/main)",
"Bash(git push --set-upstream origin main)",
"Bash(git push --force-with-lease origin HEAD:refs/heads/main)",
"Bash(git push origin main:main)",
"Bash(git push origin --delete main)",

保護ブランチ5種 x pushパターン7種 = 35ルール。ここだけでルール数の2割を占めます(几帳面すぎ?いや、事故った時のダメージを考えたら安い)。

2. Git破壊的操作

"Bash(git reset --hard:*)",
"Bash(git clean -f:*)",
"Bash(git clean -fd:*)",
"Bash(git checkout -- .:*)",
"Bash(git restore .:*)",
"Bash(git branch -D:*)",

git reset --hardとgit cleanの組み合わせで未コミットの変更が全部消える。AIが「きれいにしておきますね」と善意でやってくれることがあるので、確実にブロックします。

3. GitHub CLI / API操作

"Bash(gh repo delete:*)",
"Bash(gh pr merge:*)",
"Bash(gh api --method DELETE:*)",
"Bash(gh api -X POST:*)",
"Bash(gh api graphql:*)",

gh CLIの読み取り操作(gh pr view、gh issue list)は許可しつつ、変更・削除系のAPIメソッドはブロック。MCP経由の操作も同様に制御します。

"mcp__gh__merge_pull_request",
"mcp__gh__delete_repo",
"mcp__gh__delete_ref",
"mcp__gh__secret_set",

MCPツール名はmcp__gh__プレフィックスで識別できるので、ツール名そのものをdenyリストに入れます。

4. ファイル破壊・権限変更

"Bash(rm -rf:*)",
"Bash(rm -fr:*)",
"Bash(rm -r -f:*)",
"Bash(/bin/rm -rf:*)",
"Bash(/bin/rm -r:*)",
"Bash(chmod -R:*)",
"Bash(chown -R:*)",

ここで注目してほしいのが/bin/rmのパターン。シェルエイリアスでrmを安全な削除コマンド(ripなど)に置き換えている場合、AIが「エイリアスが効かないのでフルパスで実行します」と/bin/rm -rfを叩いてくることがある。エイリアスのバイパスも想定して塞ぐのがポイントです。

5. ネットワーク・パッケージ管理

"Bash(curl:*)",
"Bash(wget:*)",
"Bash(ssh:*)",
"Bash(nc :*)",
"Bash(brew:*)",
"Bash(pip install:*)",
"Bash(npm publish:*)",

curlやwgetは開発中に使いたい場面もありますが、AIが勝手に外部からスクリプトをダウンロードして実行する可能性がある。確認ダイアログを出すprompt相当にしたいところですが、denyリストは即座にブロックなので、必要な場合はpermissions.allowで個別に穴を開ける運用にしています。

6. システム・OS操作

"Bash(sudo:*)",
"Bash(reboot:*)",
"Bash(shutdown:*)",
"Bash(defaults write:*)",
"Bash(diskutil:*)",
"Bash(launchctl unload:*)",
"Bash(security find-generic-password:*)",
"Bash(security dump-keychain:*)",

macOS固有のコマンドも入っています。defaults writeでシステム設定を変えたり、securityコマンドでキーチェーンにアクセスしたり。開発ツールの設定中に「ちょっと最適化しておきますね」とAIがシステム設定を弄り始める事故は、実際に起きます(体験談)。

7. 機密ファイルの読み取り保護

"Read(./.env)",
"Read(./.env.*)",
"Read(./.ssh/**)",
"Read(./.gnupg/**)",
"Read(./.aws/**)",
"Read(./.config/gh/**)",

denyリストはBash実行だけでなく、Claude CodeのReadツールにも適用できます。.envにAPIキーが入っている場合、AIにそれを読ませるとコンテキストウィンドウに載ってしまう。読ませないこと自体が防御です。

L2:Pythonフックによる動的検査

denyリストはパターンの完全一致なので、どうしても漏れが出ます。そこでPreToolUseフックのPythonスクリプトが動的にコマンドを解析して判定します。

protect-branches.py:3段構えのブランチ保護

最も重要なフックがこれ。80行ほどのPythonで3つの検査を行います。

検査1:gh pr mergeの宛先チェック

PROTECTED = {"main", "master", "dev", "develop", "development"}

if re.match(r"^(?:env\s+\S+=\S+\s+)*gh\s+pr\s+merge\b", norm):
    result = subprocess.run(
        ["gh", "pr", "view", "--json", "baseRefName", "-q", ".baseRefName"],
        capture_output=True, text=True, timeout=10,
    )
    base = result.stdout.strip()
    if base in PROTECTED:
        deny(f"Blocked: merging into protected branch {base}")

gh pr mergeが実行されようとしたら、そのPRのマージ先ブランチをgh APIで確認する。denyリストでは「どのブランチにマージしようとしているか」までは判定できないので、実際にAPIを叩いて動的に判定しています。

ポイントはenv \S+=\S+のプレフィックスも許容していること。AIがenv GH_TOKEN=xxx gh pr mergeのように環境変数付きで実行するケースにも対応します。

検査2:保護ブランチからのpush検出

result = subprocess.run(
    ["git", "rev-parse", "--abbrev-ref", "HEAD"],
    capture_output=True, text=True, timeout=5,
)
br = result.stdout.strip()
if br in PROTECTED:
    deny(f"Blocked: pushing from protected branch {br}")

現在のブランチが保護ブランチなら、pushそのものをブロック。feature branchからのpushは通す。

検査3:refspecの宛先パース

ここが一番複雑で、一番重要な部分です。

parts = norm.split()
# "push"キーワードの位置を特定
i = max(j for j, p in enumerate(parts) if p == "push")
# オプションフラグをスキップしてリモート名を飛ばし、refspecを収集
j = i + 1
while j < len(parts) and parts[j].startswith("-"):
    j += 1
j += 1  # skip remote
refspecs = []
while j < len(parts):
    if parts[j].startswith("-"):
        j += 1
        continue
    refspecs.append(parts[j])
    j += 1

for rs in refspecs:
    rs = rs.lstrip("+")       # force prefixを除去
    if ":" in rs:
        _, dst = rs.split(":", 1)  # src:dst の dst を取得
    else:
        dst = rs
    if dst.startswith("refs/heads/"):
        dst = dst.rsplit("/", 1)[-1]  # refs/heads/main → main
    if dst in PROTECTED:
        deny(f"Blocked: pushing to protected branch {dst}")

refspecはsrc:dst形式で「ローカルのsrcブランチをリモートのdstブランチにpushする」という指定。+プレフィックスはforce pushを意味します。このパーサーは以下のパターンを全部検出します。

git push origin feature:main           # feature を main に push
git push origin +feature:main          # force push で main に
git push origin HEAD:refs/heads/main   # HEADを main に
git push -u origin feature:dev         # upstream 設定付きで dev に

denyリストだけではgit push origin feature:mainのようなローカルブランチ名が可変のパターンに対応できません。このPythonパーサーがあることで、denyリストの「網目」を補完しています。

auto-approve-git-safe.py:安全なgit操作の自動承認

denyリストとprotect-branches.pyで危険を止めた上で、今度は安全な操作のUXを改善します。

SAFE_SUB = re.compile(
    r"^git\s+(?:add\b.*|commit\b.*|status\b.*|restore\s+--staged\b.*)$"
)

# チェーンされたgitコマンドも全部安全なら自動承認
parts = re.split(r"\s*(?:&&|\|\||;)\s*", norm)
if parts and all(SAFE_SUB.match(p) for p in parts if p):
    allow("auto-approve safe git sequence")

git add && git commit -m "..."のようなチェーンコマンドを&&、||、;で分割し、全パートが安全なコマンドなら自動承認。1つでも安全リストに含まれないコマンドが混じっていたら承認しません。

git commitはさらに個別の正規表現でもカバーしています。

if re.search(
    r"(^|\s)(env\s+\S+=\S+\s+)*git(\s+-c\s+\S+=\S+)*\s+commit(\s+(-m|-F)\b|\b)",
    norm,
):
    allow("auto-approve git commit")

env GIT_AUTHOR_NAME=xxx git -c user.email=xxx commit -m "msg"のような環境変数・config付きコミットにも対応。Claude Codeがco-author情報を付けてコミットするケースを想定しています。

auto-approve-gh-mcp.py:MCPツールの読み取り操作を自動承認

DANGEROUS = re.compile(r"(merge|delete|transfer|archive|secret|token|ref|workflow)")

if name.startswith("mcp__gh__") and not DANGEROUS.search(name):
    allow("auto-approve safe gh MCP tool")

gh MCP経由のツール呼び出しで、ツール名に破壊的キーワードが含まれていなければ自動承認。mcp__gh__list_issuesやmcp__gh__get_pull_requestは通して、mcp__gh__merge_pull_requestやmcp__gh__delete_refはブロック。シンプルだけど効果的。

Hookイベントの使い分けガイド

settings.jsonで使用可能な4種のHookイベントと、それぞれの適切な使い所を整理します。

PreToolUse:ツール実行前の門番

"PreToolUse": [
  {
    "matcher": "Bash",
    "hooks": [{
      "type": "command",
      "command": "python3 \"$HOME/.claude/hooks/protect-branches.py\""
    }]
  }
]

使い所:

  • コマンドの内容を検査して許可/拒否を判定
  • 安全な操作の自動承認でUXを向上
  • matcherでツール名を絞り込み、不要な発火を防止

matcherには前方一致で"Bash"や"mcp__gh__"を指定できます。全ツールに発火させたい場合は空文字""を使いますが、パフォーマンスへの影響を考えて絞り込む方がベターです。

SessionStart:セッション開始時のコンテキスト注入

"SessionStart": [
  {
    "matcher": "compact",
    "hooks": [{
      "type": "command",
      "command": "echo 'Context compacted. Reminder: Read CLAUDE.md for project context. Run git status before making changes.'"
    }]
  }
]

使い所:

  • トークン圧縮後のリマインダー注入
  • セッション固有の環境情報の提供
  • matcher: "compact"でコンテキスト圧縮時のみ発火

長い会話でトークン圧縮(compact)が走ると、冒頭のCLAUDE.mdの内容が薄まることがあります。compactのタイミングで「CLAUDE.md読み直してね」とリマインドすることで、会話が長くなっても品質を維持できます。

Notification:人間への通知

"Notification": [
  {
    "matcher": "",
    "hooks": [{
      "type": "command",
      "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
    }]
  }
]

使い所:

  • 承認待ちなどでAIが止まった時のデスクトップ通知
  • macOSのosascript、Linuxならnotify-sendを使用
  • matcher: ""で全通知に対応

Claude Codeを裏で走らせてると、承認ダイアログで止まってることに気づかない。**数分間放置してから「あ、待ってたの?」**となる。Notification Hookでデスクトップ通知を飛ばせば、その無駄な待ち時間がなくなります(地味だけど効果抜群)。

実践Tips:deny設計で陥りがちな罠

罠1:ワイルドカードの:*を忘れる

// NG: 完全一致のみブロック
"Bash(git push --force)",

// OK: 後続の引数も含めてブロック
"Bash(git push --force:*)",

:*をつけないと「git push --force」という完全一致のみブロックし、「git push --force origin main」はすり抜けます。ほぼ全てのdenyルールに:*が必要です。

罠2:bare pushの扱い

// これをdenyに入れると feature branchのpushも全部止まる
"Bash(git push:*)",

// 代わりにbare pushだけを狙い撃ち
"Bash(git push)",
"Bash(git push origin)",

:*なしの完全一致で「引数なしのgit push」だけをブロックし、git push -u origin feature/xxxは通す。ワイルドカードの有無で挙動が大きく変わるので注意。

罠3:allowとdenyの優先順位

"permissions": {
  "allow": ["Bash", "mcp__*"],
  "deny": ["mcp__gh__merge_pull_request"]
}

Claude Codeではdenyが常にallowより優先されます。allowでmcp__*を全許可しても、denyに入っている特定ツールはブロックされる。この仕様を理解した上で、allowは広めに取ってdenyで穴を塞ぐ設計がお勧めです。

Nixで設定をコード管理する

ここまでの設定を手動で管理するのは現実的じゃない。私たちはNix(home-manager)でsettings.jsonとhookスクリプトをdotfilesリポジトリから自動デプロイしています。

# home-manager activation script(抜粋)
# settings.json へのシンボリックリンク
ln -sf "$DOTFILES_CLAUDE/settings.json" "$CLAUDE_DIR/settings.json"

# hooks ディレクトリ内のスクリプトへのシンボリックリンク
if [ -d "$DOTFILES_CLAUDE/hooks" ]; then
  mkdir -p "$CLAUDE_DIR/hooks"
  for f in "$DOTFILES_CLAUDE"/hooks/*.py; do
    target="$CLAUDE_DIR/hooks/$(basename "$f")"
    ln -sf "$f" "$target"
  done
fi

nix run .#update一発で、settings.jsonもhookスクリプトも最新の状態にデプロイされる。新しいマシンでも同じセキュリティ設定が再現されます。dotfilesでのNix管理については別記事で詳しく紹介しています。

このアプローチの良いところは、セキュリティルールの変更がGitの履歴で追跡できること。「いつ、誰が、どのルールを追加/変更したか」が全部残ります。170超のルールをメンテナンスするには、この追跡可能性が欠かせません。

まとめ

Claude Code Hooksの安全設計は、「止めるべきものを確実に止め、通すべきものをスムーズに通す」の一言に尽きます。

  • L1の静的denyで170超のパターンを即座にブロック
  • L2のPythonフックでrefspecパースやAPI連携による動的検査
  • L3の自動承認で安全な操作のUXを改善

どれか1つでは穴がある。3つを組み合わせて初めて「AIに任せても安心」な環境ができます。settings.jsonは1回書いたら終わりじゃなくて、新しいパターンを発見するたびに育てていくもの。170ルールは多く見えますが、それぞれに「こういう事故があったから追加した」という理由がある。AIエージェント時代の安全設計は、地道なパターン潰しの積み重ねです。

なお、本記事で紹介したHooks + Pythonスクリプトによる安全設計は、その後Permissions + deny rulesベースの構成に移行しました。

あわせて読みたい

  • 【2026年版】AIコーディングツール完全比較 — Claude Code・Codex・Antigravityの選び方
  • 【Claude Code Hooks】テスト自動化で品質を仕組み化 — テスト忘れゼロ件を実現する設定ガイド

AIコーディングツール活用 完全ガイド — この記事を含む11本の記事で、AIコーディングツールの比較・導入から実践活用までを体系的に解説しています。

この技術を活用した事例

記事の技術が実際のプロジェクトでどう活かされているかをご紹介します

【自社導入事例】ブログ運用を完全自動化 - GitHubリポジトリから記事・サムネイル・SNS投稿まで

「ブログ書くのしんどい」「SNS投稿めんどくさい」を解決。GitHubリポジトリから記事生成、サムネイル作成、SNS投稿文まで自動化した、playpark自身の導入事例を紹介します。

事例を読む

【PDF RAG】社内文書AI検索で検索時間90%削減 - RAG導入の実践事例

PDFをアップロードするだけで自然言語で質問できるAI検索システム。RAGで文書検索時間を90%削減し、社内ナレッジの活用率を3.5倍に向上させた事例をご紹介します。

事例を読む

【美容室・サロン向け】指名予約と連動するAIシフト作成アプリ|シフト管理の時間を80%削減

指名予約のお客様とスタッフのシフトが自動で連動。「担当者が休みだった」をゼロに。美容院・ネイルサロン・整体など予約制サロン(スタッフ5〜50名)に特化したAIシフト作成アプリ「Shift Bud」で、シフト管理の作成時間を80%削減。

事例を読む
AI開発の導入支援

Claude CodeやAIコーディングツールの導入・カスタマイズでお困りですか?playparkでは、AI開発環境の構築から運用まで、実践に基づいた技術支援を行っています。

サービス
AI開発について相談する
ブログ一覧に戻る

関連記事

すべての記事
【Claude Code】settings.json完全ガイド - CLAUDE.mdとの使い分けで最適なAI開発環境を構築
実験レポート
2026年2月17日40分で読める
【Claude Code】settings.json完全ガイド - CLAUDE.mdとの使い分けで最適なAI開発環境を構築

Claude Codeのsettings.jsonとCLAUDE.mdをカスタマイズして自分専用のAI開発環境を構築する方法。hooks設定・MODE切替・Skill化の手順を実例付きで解説します。

Claude CodeClaude Code カスタマイズsettings.json+6
AIコーディング入門2026 — 非エンジニアでもわかるAI開発の始め方
技術Tips
2026年3月10日16分で読める
AIコーディング入門2026 — 非エンジニアでもわかるAI開発の始め方

AIコーディングとは何か?非エンジニアでもわかるように、仕組み・始め方・主要ツールの選び方を解説。2026年の最新動向を踏まえた入門ガイドです。

AIAIコーディングClaude Code+3
【2026年版】AIコーディングツール完全比較 — Claude Code・Codex・Antigravity の選び方
技術Tips
2026年3月4日32分で読める
【2026年版】AIコーディングツール完全比較 — Claude Code・Codex・Antigravity の選び方

Claude Code・Codex・Antigravityの3大AIコーディングツールを最新情報で徹底比較。モデル・価格・セキュリティ・ユースケース別の選び方を解説します。

AIClaude CodeCodex+4

この技術を活用したサービス

記事の技術を使って、業務課題を解決しませんか?playparkのソリューションをご覧ください。

ソリューション一覧へ