👌

Claude Codeのbashコマンドを柔軟に制御するツールを作ってみた(rm -rf ~/も防げます)

に公開

はじめに

Claude Code は便利ですが、rm -rf ~/のような危険なコマンドも実行してしまう可能性があります。そこで、Claude Code のhooks 機能を活用して、bash コマンドの実行を安全かつ柔軟に制御するツールを作ってみました。

rm -rf ~/をブロックする実行例

実際に rm -rf ~/をブロックしているところ

セットアップ

https://github.com/miyaoka/claude-hooks

1. リポジトリのクローン

git clone https://github.com/miyaoka/claude-hooks.git

2. settings.json へのフック設定

Claude Code のsettings.jsonに以下を追加:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "[cloneしたリポジトリ内のスクリプトパス]"
          }
        ]
      }
    ]
  }
}

3. ルールファイルの配置

.claude/hooks.config.jsonを作成し、制御ルールを定義します

ルール設定

{
  "PreToolUse": {
    "Bash": {
      "ls": [
        {
          "reason": "lsコマンドは安全なので自動承認",
          "decision": "approve"
        }
      ],
      "git": [
        {
          "pattern": "^(status|log|diff)",
          "reason": "読み取り専用のgitコマンドは自動承認",
          "decision": "approve"
        },
        {
          "pattern": "^push",
          "reason": "⚠️ pushはユーザーが行います",
          "decision": "block"
        }
      ],
      "npm": [
        {
          "pattern": "^install.*(-g|--global)",
          "reason": "グローバルインストールは確認が必要"
        }
      ],
      "curl": [
        {
          "reason": "⚠️ curlの代わりにWebFetchツールを使用してください",
          "decision": "block"
        }
      ]
    }
  }
}

ルールの構成要素

本ツールはClaude Code の PreToolUse Decision Control仕様に準拠し、patternフィールドを独自拡張として追加しています。

項目 説明
pattern コマンド引数に対する正規表現(独自拡張・省略可) 正規表現文字列
decision マッチ時の動作(Claude 仕様準拠) blockapprove、未指定(確認)
reason Claude へのメッセージ(Claude 仕様準拠) 文字列

ルール評価の優先順位

  1. pattern 指定ありのルールが優先
  2. pattern 未指定はデフォルトルール
  3. block が最優先(安全側に倒す)

動作例

実行例:git pushとcurlをブロック

上記の設定により:

  • git push:ブロック(ユーザーが手動実行すべき)
  • curl:ブロック(WebFetch ツールへ誘導)
  • git status:自動承認(読み取り専用)

高度な設定

設定ファイルの優先順位

CLAUDE.md と同様に、グローバル設定とプロジェクトローカル設定の両方が利用可能です:

  1. グローバル設定:~/.config/claude/hooks.config.json
  2. プロジェクト設定:.claude/hooks.config.json

設定は以下の順序でマージされます:

// グローバル (例: ~/.config/claude/hooks.config.json)
{
  "PreToolUse": {
    "Bash": {
      "rm": [
        {
          "pattern": "-rf",
          "reason": "強制削除は危険",
          "decision": "block"
        }
      ]
    }
  }
}

// プロジェクトローカル (.claude/hooks.config.json)
{
  "PreToolUse": {
    "Bash": {
      "rm": [
        {
          "pattern": "\\.tmp$",
          "reason": "一時ファイルの削除はOK",
          "decision": "approve"
        }
      ]
    }
  }
}

// 実際に評価される結合結果
{
  "rm": [
    // グローバルルールが先
    {
      "pattern": "-rf",
      "reason": "強制削除は危険",
      "decision": "block"
    },
    // ローカルルールが後
    {
      "pattern": "\\.tmp$",
      "reason": "一時ファイルの削除はOK",
      "decision": "approve"
    }
  ]
}

複合コマンドと複数引数の処理

{
  "touch": [
    {
      "reason": "touchコマンドのデフォルトは承認",
      "decision": "approve"
    },
    {
      "pattern": "/etc",
      "reason": "⚠️ システムファイルへのtouchは禁止",
      "decision": "block"
    },
    {
      "pattern": "\\.conf$",
      "reason": "設定ファイルの作成は要確認"
    }
  ],
  "echo": [
    {
      "pattern": "hello",
      "reason": "⚠️ あいさつ禁止",
      "decision": "block"
    }
  ]
}

各種コマンドの実行例

評価結果

コマンド 結果 理由
touch foo.txt ✅ 承認 デフォルトルールで承認
echo hello && touch bar.txt ❌ ブロック "hello"が禁止パターン
echo world && touch /etc/bar.txt ❌ ブロック /etc への touch が禁止
touch file.txt /etc/hosts app.conf ❌ ブロック 引数に/etc が含まれる

ルール評価アルゴリズム

マッチング優先順位

  1. pattern 指定ルールが最優先
  2. pattern 未指定はデフォルトルール
  3. 複数マッチ時は安全側(block > 確認 > approve)を採用

処理フロー

  1. コマンドごとに全ルールを評価
  2. block ルールにマッチしたら即座に実行停止
  3. 確認が必要なルールがあればユーザーに確認
  4. すべて approve の場合のみ自動実行

ルールの安全なテスト方法

実際にパターンマッチが意図通り指定できているか確認するため、Claude Code を通さずに、シェルスクリプトで直接テストできます:

# 危険なコマンドのテスト
echo '{"tool_input": {"command": "rm -rf ~/"}}' | ./hooks/pretooluse/bash/hook.sh
# → {"decision": "block", "reason": "⚠️ ホームディレクトリの削除は禁止"}

# 安全なコマンドのテスト
echo '{"tool_input": {"command": "ls -la"}}' | ./hooks/pretooluse/bash/hook.sh
# → {"decision": "approve", "reason": "lsコマンドは安全なので自動承認"}

# ルール未定義コマンドのテスト
echo '{"tool_input": {"command": "echo test"}}' | ./hooks/pretooluse/bash/hook.sh
# → {} (ユーザー確認が必要)

ブロックするだけじゃない。permissions 設定より柔軟で正確な制御

従来のsettings.jsonpermissions.allow設定では、&&;で連結されたコマンドの評価が難しく、意図通りに動作しないケースがありました。

// 従来の方法(settings.json)
{
  "permissions": {
    "allow": ["Bash(ls)", "Bash(git status)", "Bash(npm run test:*)"],
    "deny": ["Bash(rm -rf)", "Bash(curl:*)"]
    // 問題点:
    // - ワイルドカード(*)のみで正規表現が使えない
    // - 同一コマンドで引数によるallow/denyの分岐が難しい
    // - 複合コマンド(&&や;)の評価が不明確
  }
}

本ツールのアプローチ:

// 本ツールの方法
{
  "git": [
    { "pattern": "^(status|log|diff)", "decision": "approve" },
    { "pattern": "^push", "decision": "block" }
  ],
  "npm": [
    { "pattern": "^install$", "decision": "approve" },
    {
      "pattern": "^install.*(-g|--global)",
      "reason": "グローバルインストールは確認が必要"
    }
  ]
}

主な利点

  1. 複合コマンドの正確な評価&&;で連結された各コマンドが個別に評価される
  2. 柔軟なパターンマッチング:正規表現で引数の一部をマッチできる
  3. approve と block の混在:同じコマンドでも引数によって異なる動作を設定可能
  4. hooks 機能の活用:reason で代替手段の提案や理由を Claude に伝えられる

まとめ

Claude Code の hooks 機能を活用して、正規表現による柔軟なパターンマッチングを実現するツールを作りました。従来の permissions 設定では難しかった複合コマンドの制御や、同一コマンドでの approve/block の混在が可能になり、より安全で効率的な開発環境を構築できます。

詳細なドキュメントはGitHub リポジトリをご覧ください。


(本記事は人力で書きましたが最終的にclaudeに推敲して編集してもらいました)

Studio Tech Blog

Discussion