ModsCode日本語ブログ Claude から Mod のコード例を探す

ModsCode / 日本語ブログ / 組織で Claude Code Mods を管理する:許可・禁止・審査

組織で Claude Code Mods を管理する:許可・禁止・審査

Claude Code の Mods を組織で管理する方法を解説。allowManagedModsOnly で持ち込みを止める、自社の Mod を先に走らせる、方針を検査する Mod を書く手順を、管理設定の例と確認済みのコードで紹介します。

Claude Code の Mod は、ユーザーの権限で動くコードです。社内のマシンで使う場合、管理者は「Mod を動かすか」「どれを動かすか」「どの順番で動かすか」を、管理設定(managed settings)で決められます。この記事は、Claude Code の管理設定を配る立場の方向けです。公式ドキュメント(組織向けの Mods 管理)の内容を整理し、方針を検査する Mod の例を、Claude Code 2.1.289 の claude plugin validate と claude plugin test で確認したコードで補います。

先に結論

  • 何も設定しなければ、Mod はオンです。ユーザーは、許可されたマーケットプレイスの Mod を入れ、--plugin-dir で任意のフォルダを読み込めます。
  • ユーザーの持ち込みだけを止めるには、内蔵ガード(cc-plugin-sec-default@builtin)の allowManagedModsOnly を管理設定でオンにします。ユーザーの設定フックやステータスラインは動き続けます。
  • ユーザーは、管理設定のこの値を変えられません。ユーザー、プロジェクト、ローカルの設定ファイルに同じ項目を書いても、効果がありません。
  • 自社の Mod を配るには、決められた 3 つの条件を満たす必要があります。条件を外すと、ユーザーの Mod として扱われます。
  • 「一部の Mod だけ許可する」方針は、plugin.register を扱う検査用の Mod で書きます。

何も設定しないと、どうなるか

公式ドキュメントによると、Mod の設定を何もしていない場合の挙動は次のとおりです。

  • Mod はオン:許可されたマーケットプレイスの Mod を入れられ、フォルダの読み込みもできます。
  • 内蔵ガードが先に走る:sec-default@builtin という Claude Code 内蔵の Mod が、ユーザーのどの Mod よりも先に読み込まれます。ユーザーは止められません。ガードが読み込まれるのは、マシンに管理設定がある場合、または Team / Enterprise プランでサインインしているユーザーの場合です。API キー、Amazon Bedrock、Google Cloud の Agent Platform、Microsoft Foundry で認証しているユーザーは、管理設定があるマシンでのみガードが動きます。
  • ガードが守るもの:ユーザーの Mod は、管理フックが受け取るものと決めること、システムプロンプト、管理する CLAUDE.md などの指示、管理 MCP サーバーのツールと説明を変えられません。
  • それ以外は許可:ファイルの読み書き、プロセスの起動、通信、ツール呼び出しやプロンプトの書き換え、ツール呼び出しの拒否、確認が出るはずの呼び出しの許可、画面の描画は、すべてそのユーザーの権限で可能です。
  • deny ルールと管理フックは優先:ガードが読み込まれている環境では、ユーザーの Mod は、deny ルールで拒否される呼び出しを許可できません。管理設定の PreToolUse フックの遮断も最終です。ただし、これは Claude のツール呼び出しに対してです。Mod 自身の $.fs や $.process の呼び出しには適用されません。Read(.env) を拒否していても、Mod は $.fs.read でそのファイルを読めます。

ポリシーの選び方

やりたいこと設定
インストールした Mod はすべて動かさない(設定フックはそのまま)allowManagedModsOnly をオンにし、自社の Mod は配らない
インストールした Mod も、管理フックを含むすべてのフックも止めるdisableAllHooks を true にする
自社の Mod だけ動かすallowManagedModsOnly をオンにし、自社の Mod を条件に合わせて配る
承認したマーケットプレイスの Mod は許可するマーケットプレイスの制限を維持し、disableSideloadFlags を true にする
どの Mod も許可し、自社の Mod で他の Mod を検査する自社の Mod を配り、prependPlugins に sec-default@builtin と並べる

設定ごとの違いは次のとおりです。

  • allowManagedModsOnly:内蔵ガードのオプションです。ユーザーの Mod のフックがすべて動かなくなります。ユーザーの設定フック、ステータスライン、/goal は動き続けます。
  • allowManagedHooksOnly:もっと広い設定です。自社の Mod と Claude Code 内蔵の Mod だけが読み込まれ、ユーザーの設定ファイルのフックも止まります。設定する前に、公式の設定リファレンスで、何が動き続けるかを確認してください。
  • disableAllHooks:最も広い設定です。管理設定に書くと、自社の Mod を含めてすべてのインストール済みプラグインの Mod が止まり、管理設定の PreToolUse フックも何も止めなくなります。ステータスラインと /goal も止まります。
  • disableSideloadFlags:--plugin-dir と --plugin-url を起動時に拒否し、Claude が書いた Mod の読み込みも止めます。--agents と --mcp-config も拒否されます。

ユーザーの持ち込みを止める設定

managed-settings.json に、次のように書きます。キーは pluginConfigs の下の cc-plugin-sec-default@builtin です。

managed-settings.json
{
  "pluginConfigs": {
    "cc-plugin-sec-default@builtin": {
      "options": {
        "allowManagedModsOnly": true
      }
    }
  }
}

この設定で、次のことが起きます。

  • ユーザーのインストールしたプラグインの Mod、--plugin-dir で読み込んだ Mod、セッション中に Claude が書いた Mod のフックは、すべて動かなくなります。
  • GitHub などのリモートのマーケットプレイスから有効にしたプラグインも、ユーザーの Mod として扱われ、拒否されます。claude.ai で組織がメンバーに有効にしたものも同じです。
  • ユーザーは取り消せません。ガードがこの値を読むのは、管理設定からだけです。
  • ファイルや MDM で配った場合は、Amazon Bedrock などのプロバイダーを使っていても同じように効きます。claude.ai の管理コンソールから配る場合は、公式の対応状況の表を確認してください。

効いているかは、ユーザーのマシンで claude --plugin-dir ./任意のMod を実行して確かめます。Mod のフックは動かず、トランスクリプトとデバッグログに、Mod の名前と allowManagedModsOnly を含むガードのメッセージが出ます。メッセージの意味は Mod が動かないときの確認手順 にあります。

CLAUDE_CODE_ENABLE_FUNCTION_HOOKS は、v2.1.287 以降では値にかかわらず無視されます。早期アクセスのときに 0 を設定していたなら、Mod は止まっていません。この項目の allowManagedModsOnly に置き換えてください。

自社の Mod を配る:3 つの条件

Claude Code が「組織の Mod」だと認めるのは、次の 3 つがすべて満たされたときだけです。

  1. 管理設定の enabledPlugins で、そのプラグインが true になっている。
  2. 管理設定が、そのプラグインのマーケットプレイスを、ユーザーのマシン上のディレクトリとして、絶対パスで指している(extraKnownMarketplaces がこれをします)。
  3. マーケットプレイスが、そのプラグインを相対パスで載せている。Claude Code がそのディレクトリからそのまま読み込むため。

デバイス管理で、マーケットプレイスのディレクトリを全マシンの同じパスにコピーしてください。そのディレクトリと、上位のディレクトリは、管理設定のファイルと同じく、管理者だけが書き込める状態にします。書き込める人は、あなたの Mod を書き換えられるからです。claude.ai の管理コンソールで配った管理設定にもキーは入れられますが、ディレクトリをマシンに置くことはできません。

配布するディレクトリの構成
/opt/example/claude-plugins/
├── .claude-plugin/
│   └── marketplace.json
└── plugins/
    └── org-guard/
        ├── .claude-plugin/
        │   └── plugin.json
        └── hooks/
            ├── hooks.json
            └── register.js

marketplace.json は、プラグインを相対パスで載せます。

/opt/example/claude-plugins/.claude-plugin/marketplace.json
{
  "name": "example-tools",
  "owner": { "name": "Example" },
  "plugins": [
    { "name": "org-guard", "source": "./plugins/org-guard", "description": "Example policy mod" }
  ]
}

管理設定は、マーケットプレイスの登録、有効化、実行順序を指定します。

managed-settings.json
{
  "extraKnownMarketplaces": {
    "example-tools": {
      "source": { "source": "directory", "path": "/opt/example/claude-plugins" }
    }
  },
  "enabledPlugins": { "org-guard@example-tools": true },
  "prependPlugins": ["org-guard@example-tools", "sec-default@builtin"]
}

注意点が 3 つあります。

  • GitHub、git、URL、npm から Claude Code がキャッシュにコピーするプラグインは、enabledPlugins で有効にしても、ユーザーの Mod として扱われます。prependPlugins はそれを読み飛ばし、allowManagedModsOnly は拒否します。
  • prependPlugins を管理設定で設定すると、デフォルトの並びが置き換えられます。内蔵ガードを残すには、リストに sec-default@builtin を書いてください。
  • prependPlugins と appendPlugins は、管理設定からだけ読まれます。リポジトリの設定ファイルや、--settings で渡したファイルは効きません。

Mod が走る順番

同じイベントに対するフックは、1 本の連なり(ミドルウェア)になります。最初の Mod が一番外側で、イベントを先に見て、結果を最後に見ます。後の Mod は、先の Mod がイベントを見るのを止められません。順番は、出どころで次のように決まります。

  1. 内蔵ガード、prependPlugins に書いた組織の Mod、appendPlugins にない組織の Mod
  2. ユーザーがインストールした Mod
  3. appendPlugins に書いた組織の Mod
  4. そのほかの内蔵 Mod

管理設定の PreToolUse の設定フックは、どの Mod の tool.call よりも前に動き、その遮断は最終です。ほかの設定ファイルやプラグインの PreToolUse フックは、最後の Mod が next を呼んだ後に動きます。

方針を検査する Mod を書く

「ユーザーの Mod は全部止める」だけなら、Mod は要りません。「一部は許し、一部は断る」「何が起きたか記録する」ときに、検査用の Mod を書きます。ほかの Mod が読み込まれる直前に、plugin.register というイベントが届き、claude plugin validate が出す一覧と同じ内容が e.uses に入っています。

次の Mod は、環境変数や設定を読み、同時に通信やプログラム起動もするユーザーの Modを、読み込ませません。「読んだ値を外へ出せる組み合わせ」の例です。これは例であり、推奨する方針ではありません。自分の組織の事情に合わせて決めてください。

org-guard/hooks/register.js
// 読んだ値を外へ出せる組み合わせ。読む側と、出す側
const READS = ['env.get', 'settings.read']
const SENDS = ['http.fetch', 'process.run', 'process.spawn']

// 判定は関数に出しておく。.catch から同じ形で使うため
async function checkMod($, e, next) {
  if (e.tier === 'user') {
    const reads = e.uses.calls.filter((call) => READS.includes(call))
    const sends = e.uses.calls.filter((call) => SENDS.includes(call))
    if (reads.length > 0 && sends.length > 0) {
      // refuse を返すと、その Mod は読み込まれない。文字列が理由になる
      return { refuse: '環境変数や設定を読む Mod は、通信やプログラム起動と併用できません: ' + reads.join(', ') + ' と ' + sends.join(', ') }
    }
  }
  return next(e)
}

export function register(on) {
  on('plugin.register', checkMod).catch(async ($, e, next) => {
    // 組織の Mod と内蔵の Mod は通す
    if (e.tier !== 'user') return next(e)
    // 検査できなかったユーザーの Mod は、読み込まない(fail closed)
    return { refuse: '方針の検査に失敗したため、この Mod は読み込まれません' }
  })
}

ポイントは次のとおりです。

  • e.tier は、その Mod がどこで走るかを示す値です(prepend、user、append、builtin)。人がインストールした Mod は、すべて user です。
  • e.uses.calls には、Mod が呼ぶ Mods API が、$. なしの 名前空間.メソッド で入ります(http.fetch のように)。
  • .catch を付けると、検査自体が失敗したときに、ユーザーの Mod を読み込ませない側に倒せます。付けない場合、検査のフックが例外を投げたり時間を超えたりすると、Claude Code はそのフックを飛ばし、検査していた Mod がそのまま読み込まれます。
  • 特定の呼び出しだけを止めたいときは、その呼び出しの名前のフック(fs.write など)で { deny: '理由' } を返します。

このコードは、claude plugin validate で hooks: plugin.register が読まれることを確かめ、claude plugin test で次の 2 つの動作を確認しました。

  1. env.get と http.fetch を呼ぶ Mod は、leaky: refused by org-guard: … のメッセージで読み込まれない。
  2. env.get だけを呼ぶ Mod は、読み込まれて動く。

テストの書き方は Mod のテストの書き方 で説明します。検査用の Mod のテストでは、ファイルの先頭で tier('prepend') を呼び、test の第 2 引数の plugins に、検査される側の Mod を書きます。

限界を知っておく

  • 検査用の Mod は、インストールされる Mod の呼び出しの種類しか見られません。通信先やコマンドの中身までは分かりません。
  • 内蔵でない Mod が動くワーカーが 3 回止まると、Claude Code は内蔵でない Mod をすべて外します。あなたの Mod も外れます。ユーザーが /reload-plugins を実行するか、新しいセッションを始めるまで続きます。
  • ユーザーが claude --safe-mode で起動すると、組織のものを含め、インストールした Mod なしで動きます。
  • Mod は、どの設定でもサンドボックスの中では動きません。

まとめ

迷ったら、まず allowManagedModsOnly で持ち込みを止め、必要な Mod だけを自社の配布物として入れるのが、最も単純です。ユーザーに自由を残すなら、内蔵ガードと検査用の Mod を組み合わせます。審査の観点は、他人の Mod を入れる前の安全チェック にまとめました。