security-proxy について
security-proxyは、MCPサーバーの前段でインジェクション検査、PIIマスキング、ポリシー適用、監査記録を行うセキュリティレイヤーです。複数サーバーを一つのゲートウェイにまとめ、サーバー間の危険なデータフローも追跡します。
主な機能
- 01プロンプトとツール結果のインジェクション検出
- 02応答内のPIIと秘密情報のマスキング
- 03ポリシーに基づくブロックと警告
- 04サーバー間の有害なデータフロー追跡と遮断
- 05リクエストの監査記録
活用シーン
- 01既存のMCPサーバーの前段に安全対策を追加
- 02複数サーバーを単一ゲートウェイで管理
- 03機密データの外部ツールへの送信を防止
MCP 対応
MCPプロキシとして上流サーバーに接続し、そのツールとプロンプトをクライアントに提供します。リクエストと結果を検査し、ポリシーを適用して監査イベントを記録します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
翻訳後に原文が更新されています。最新の内容は原文をご確認ください。
@guardbee/mcp-security-proxy
🇬🇧 英語 | 🇹🇷 トルコ語 | 🇨🇳 中国語 | 🇯🇵 日本語 | 🇰🇷 韓国語 | 🇧🇷 ポルトガル語 | 🇪🇸 スペイン語 | 🇩🇪 ドイツ語
あらゆる MCP サーバーの前段に配置する、透過的なセキュリティレイヤーです。プロンプトインジェクション攻撃をブロックし、レスポンス内の PII をマスキングして、すべてのリクエストを改ざん不可能な監査ログに記録します。
このパッケージは、デフォルトで使用状況のテレメトリーを GuardBee に送信します(ツール名と短いパラメーター。詳細は
@guardbee/mcp-telemetryを参照してください)。これは独自のローカル監査ログとは別個で、独立しています。GUARDBEE_TELEMETRY=0で無効にできます。
Claude ──► MCP Security Proxy ──► Any MCP Server
│
├─ Prompt injection detection (27 rules, EN + TR)
├─ PII + secret masking (TC, VKN, IBAN, card, phone, 22 key formats)
├─ Block or warn mode
├─ Toxic-flow (lethal trifecta) blocking
└─ Hash-chained audit log
機能
- プロンプトインジェクション対策 — 英語とトルコ語で27個のルールを適用します。精度の高いルールはブロックし、一般的な表現には警告のみを出します
- ツール結果へのインジェクション — 同じパターンをツール結果にも適用し、MCP06:2025 としてタグ付けします。ブロックモードでは結果を置き換え、警告モードでは警告を先頭に追加します
- セッションのツール固定 — 最初の
tools/listを固定します。その後の説明やスキーマの変更はラグプル(MCP03:2025)です。ブロックモードでは固定された定義の提供を続け、変更された呼び出しを拒否します - PII マスキング — TC Kimlik No、VKN、IBAN、カード番号、電話番号(チェックサム検証済み)、パスポート、SGK、MERSİS、生年月日、郵便住所と KEP アドレス、メールアドレス、オプトイン方式の IP/MAC/ナンバープレート、22種類のプロバイダーキー形式、秘密鍵、接続文字列をレスポンス内でマスキングします
- ブロック/警告モード — 各インターセプターを個別にブロックモードまたは警告モードで実行できます
- 監査ログ — コンソールまたはファイルに書き込むログを設定できます
- ゲートウェイモード — 複数の MCP サーバーを1つのプロキシの背後に配置し、1つの YAML ポリシー(許可/拒否/マスク/警告)を適用します
- 有害なフローのブロック — サーバーをまたいで致命的な三要素を追跡し、それを完成させる出力呼び出しをブロックします
- LLM API ゲートウェイ — OpenAI および Anthropic 互換のエンドポイントです。プロバイダーキーの代わりにゲートウェイキーを使用し、プロンプトの PII マスキングとインジェクションチェック、トークンおよび利用額の上限設定、監査ログへの使用状況とコストの記録を行います
- コーディングエージェントのフック —
guardbee-hookにより、Claude Code と Cursor 独自のシェル、ファイル、ウェブツールにも同じポリシーを適用します - コード変更不要 — 既存の MCP サーバーの前段に配置できます
クイックスタート
npm install -g @guardbee/mcp-security-proxy
claude_desktop_config.json に追加します。
{
"mcpServers": {
"secure-filesystem": {
"command": "npx",
"args": [
"-y", "@guardbee/mcp-security-proxy",
"--", "npx", "-y",
"@modelcontextprotocol/server-filesystem", "/tmp"
]
}
}
}
プロキシは
--の後に指定されたプログラムを、対象の MCP サーバーとして起動します。
ゲートウェイモード:複数のサーバーに1つのポリシー
guardbee-proxy.yaml を使って、すべての MCP サーバーを1つのプロキシの背後に配置します。init が設定を移行します。
npx -y @guardbee/mcp-security-proxy@^1 init --client claude-desktop # or cursor, claude-code, --file ./mcp.json
各 stdio サーバーを ~/.guardbee/guardbee-proxy.yaml(モード 0600)に移動し、クライアント設定をバックアップしてプロキシを参照するように変更します。HTTP サーバーはポリシーを迂回するため、そのまま残して報告します。--dry-run は書き込みを行わず、両方のファイルを表示します。または、手動で設定します。
{
"mcpServers": {
"guardbee": {
"command": "npx",
"args": ["-y", "@guardbee/mcp-security-proxy@^1", "--config", "/path/to/guardbee-proxy.yaml"]
}
}
}
version: 1
upstreams:
github:
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env: { GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_TOKEN}" } # read from the environment
postgres:
command: npx
args: ["-y", "@guardbee/mcp-db-gateway"]
labels: # override the heuristic labels
github__get_issue: [untrusted]
github__create_pull_request: [egress]
tools: # what the agent sees (globs on <upstream>__<tool>)
hide: ["*__delete_*"]
descriptions:
github__create_issue: "Create an issue in acme/* repositories only."
rules: # first match wins
- id: no-deletes
match: { tool: "postgres__delete_*" }
action: deny # allow | deny | mask | warn | approve
- match: { tool: "postgres__query", args: { table: "salaries" } } # dotted path → value, strings are globs
action: mask
mask: { fields: [salary, iban] } # blank these JSON keys
- match: { label: destructive }
action: approve # ask the person first
taint:
mode: strict # strict | approve | warn | off
basis: capability # capability | data — data: block only egress calls that carry sensitive data
interceptors:
anomaly: # off unless set; limits per session, sliding window
enabled: true
action: warn # warn | block
callBurst: { count: 60, windowSeconds: 60 }
audit:
sink: file
filePath: ./guardbee-audit.jsonl
- ツールとプロンプトは
<upstream>__<tool>として表示されます。 - 有害なフロー(致命的な三要素): 各ツールには、
untrusted、sensitive、egress、destructiveのいずれかのラベルが付けられます(名前や説明に基づくヒューリスティックで判定され、labelsで上書きできます)。セッションで信頼できないコンテンツ(untrustedツールまたは任意のリソース)と機密データ(sensitiveツールまたは結果内の任意の PII)が読み取られると、strictモードではegressの呼び出しがブロックされ、warnモードではログに記録されるだけです。このチェックはサーバーをまたいで行われます。GitHub から読み取った課題と CRM の顧客レコードの両方がある場合、3 台目のサーバーへの Webhook がブロックされます。 - ルールは
tool(glob)、upstream、label、session(clean|tainted)、argsに一致します。maskは、マスキングがオフの場合でも、そのツールに対して PII のマスキングを強制します。また、mask.fieldsは、テキストとstructuredContent内の指定された JSON キーも空欄にします。ルールのallowでは有害なフローのチェックはスキップされません。代わりにツールのラベルを変更してください。 - トピック(
topics、match.topic): ルールではツールの代わりに、対象となる話題を指定できます。「このエージェントを通じて、合併や給与に関する情報を一切外部に出さない」といったルールです。トピックは語句のリストです。異なる語句がminMatches(デフォルトは 1)個以上テキストに含まれていると、そのトピックに一致します。一致判定では大文字・小文字とトルコ語の文字の違いが無視され(BİRLEŞME=birlesme)、語句には接尾辞が付いていても一致します(maaşはmaaşlarıに一致し、satın almaはsatın almayıに一致します)。単語全体だけに一致させるには、語句を引用符で囲みます('"AI"')。ルールは呼び出しの引数と、返された内容(ツールの結果、リソース、プロンプト)の両方に対してチェックされるため、回答にしか現れない対象も検出されます。denyは内容の返却を阻止し、maskは PII のマスキングとmask.fieldsを強制し、warnはログに記録し、approveは承認を求めます(承認を求めるのは一度だけで、すでに承認された呼び出しの回答では再度求めません)。LLM API ゲートウェイでは、トピックルールはプロンプト全体に適用され、tool: llm:<model>を指定します。API 呼び出しでは承認を求める相手がいないため、ここでのapproveは拒否として扱われます。topics: - id: ma terms: [birleşme, "satın alma", "due diligence", acquisition, merger] - id: salary terms: [maaş, bordro, payroll, salary] minMatches: 1 rules: - { id: no-ma-out, match: { topic: ma, label: egress }, action: deny } - { id: contractors, match: { topic: salary, user: "*@contractor.com" }, action: approve } - { id: ma-to-llm, match: { topic: ma, tool: "llm:gpt-*" }, action: deny } - ツールの公開(
tools):exposeは、エージェントに表示される名前に対する glob の許可リストです(未設定の場合はすべてのツールが対象)。hideはその後にツールを除外します。非表示にしたツールはtools/listに含まれないため、モデルがその説明を読むことはありません(ポイズニングの攻撃対象を減らし、トークン数も抑えます)。また、そのツールへの呼び出しは拒否され、anomaly.repeatedBlocksのカウント対象になります。descriptionsは、ツールの説明を作成した文章に置き換えます。ドリフト検出はサーバー本来の説明を引き続き監視します。呼び出し時に拒否するdenyルールとは異なり、非表示にするとツールはエージェントから見えなくなります。 tools/listにおけるツールのポイズニング(interceptors.toolPoisoning、デフォルトで有効): エージェントに表示される前に、すべての定義をスキャンします。対象は説明(tools.descriptionsに記述した説明がある場合はその説明)、入力スキーマ内のパラメーターの説明と列挙値、読み取り専用のアノテーションです。モデルを狙った指示(<IMPORTANT>、"do not tell the user"、"ignore previous instructions")、ほかのツールへのなりすまし("when using the X tool, always…")、.ssh/.envの読み取り要求、不可視のゼロ幅文字、双方向制御文字、ANSI 文字、キリル文字やギリシャ文字の紛らわしい類似文字を含む単語、説明と矛盾するreadOnlyHintを検出します。blockモード(デフォルト。未設定の場合はpromptInjection.actionに従います)では、重大または高の検出結果があったツールはリストから除外され、その呼び出しも拒否されます。中程度の検出結果(エンコードされたデータ)は警告のみです。各検出結果はruleId: poisoning:<pattern>として一度だけ記録されます。ルールは@guardbee/mcp-tool-poisoning-scannerが使用するものと同じで、現在は guard-core を介して共有されています。- 外部へ送信される認証情報(
interceptors.egressSecrets、デフォルトで有効):egressツールの実行前に、実際に渡される引数に認証情報が含まれていないか確認します。対象はプロバイダーキー(AWS、GitHub、OpenAI、Anthropic、Stripe、Slack など)、秘密鍵、パスワード付き接続文字列です。PII マスキングで使用する固定形式のルールと同じものを使い、広範なキーワードルールは使用しません。データの汚染追跡で把握できるのは、このセッションのツールが返した情報だけですが、このチェックではモデルがプロンプト、ファイル、または自身のコンテキストから得たキーも検出します。block(デフォルト。未設定の場合はpromptInjection.actionに従います)は呼び出しを拒否し、warnは記録します。監査イベントでは、どちらの場合も認証情報がマスキングされます。PII トークンはpiiMasking.detokenizeForEgressを設定しない限り、外部送信ツールに対してもトークンのままです。そのため、値がチェックされるのは設定した場合のみです。allowToolsでは glob によってツールを除外できます(たとえば、DB URL を受け取るデプロイツールなど)。個人データは PII マスキングと汚染追跡に任せます。 - サンプリング(
interceptors.sampling、デフォルトで無効): サーバーはエージェントのモデルに補完を要求できます(sampling/createMessage)。悪意のあるサーバーは、この機能を使ってモデルに指示を紛れ込ませたり、データを持ち出したりする可能性があります。無効の場合、プロキシはサーバーにサンプリング機能を一切提供しません。有効の場合、サーバーのツール呼び出しの実行中に限り、その呼び出しを行ったセッションがリクエストに応答します(同じサーバーを同時に2つのセッションが呼び出している場合、どちらに属するリクエストか判別できないため拒否されます)。includeContextはnoneに固定されるため、サーバーが会話を受け取ることはありません。maxTokensには上限が設けられ(デフォルトは 1024)、呼び出しごとのリクエスト数にも上限が設けられます(maxPerCall、デフォルトは 3)。システムプロンプトとメッセージはインジェクションスキャンされ、approval: trueの場合は先にユーザーに確認します。モデルの回答はサーバーに返されるため、外部送信として扱われます。このセッションで取得した認証情報やデータが含まれている場合はブロックされ、個人データはマスキングされます。イベントにはtool: sampling:<upstream>とruleId: sampling:<check>が使用されます。この機能はプロキシの起動時に提供されるため、有効化または無効化には再起動が必要です。 - 承認:
approve(ルールのアクション、または有害なフローに対するtaint.mode: approve)は、MCP エリシテーションを通じてユーザーに「はい/いいえ」のフォームを表示します。フォームにはツール、理由、引数が含まれます。明示的な「はい」以外(拒否、キャンセル、approval.timeoutSeconds(デフォルトは 120)以内に回答がない場合)は、すべて呼び出しをブロックします。エリシテーションに対応していないクライアントでは承認できないため、その旨を示すメッセージとともに呼び出しがブロックされます。 - ダッシュボードでの承認:
approval.channels: [elicitation, dashboard]とaudit.dashboardを設定すると、プロンプトを表示できないクライアントでも、呼び出しがただちにブロックされなくなります。引数を PII マスキングして GuardBee ダッシュボード(MCP Gateway ページ)にリクエストを送り、ワークスペースのオーナーと管理者に通知します。その後、誰かがダッシュボードで承認または拒否するか、timeoutSecondsが経過するまで、プロキシは呼び出しを保留します。チャネルは順番に試され、最初に確認を要求できたチャネルの回答が使用されます。 - データに基づく汚染追跡(
taint.basis: data): デフォルトのcapability基準では、セッションが信頼できないコンテンツと機密コンテンツの両方を保持している場合、すべての外部送信呼び出しがブロックされます。dataを指定すると、プロキシは機密ツール(および PII を含む結果)が返した内容(個人データと認証情報、識別子のようなトークン、単語の連続部分)のハッシュ化フィンガープリントを保持します。引数にそのデータが含まれ(共通する単語が 12 個以上、または識別子が完全一致。PII トークンはその値として扱われます)、かつセッションが信頼できないコンテンツも読み取っている場合に限り、外部送信呼び出しを有害なフローと判定します。ブロックメッセージには、そのデータの取得元ツールが示されます。ライセンス文、Markdown の定型文、通常の結果または 3 つ以上のツールからも返されたテキストは無視されます。フィンガープリントはハッシュ化され、セッションごとにメモリ内に保持されます。トレードオフとして、データの言い換えや要約は検出されません。その点が重要な場合はcapabilityを使用してください。capabilityモードでは、同じ証拠が見つかった場合、ブロック理由に追加されます。 - 異常チェック(
interceptors.anomaly): 1 回の呼び出しだけでは分からない挙動を、スライディングウィンドウを使ってセッションごとにカウントします。callBurst— 60 秒間に 60 回を超えるツール呼び出し(ループや DoS 攻撃)。sensitiveSweep— 300 秒間に 25 種類を超えるsensitiveツールの読み取り(ストアのレコードを 1 件ずつ列挙する動作。同じレコードの再読み取りはカウントされません)。destructiveBurst— 60 秒間に 5 回を超えるdestructive呼び出し。repeatedBlocks— 300 秒間に 5 回を超える、ブロックまたは拒否された呼び出し(ポリシーの探査。承認プロンプトを表示できないクライアントはカウントされません)。各項目は{ count, windowSeconds }を設定でき、上記の数値はデフォルト値です。warnは各チェックで初めてしきい値を超えたときに記録します。blockはウィンドウが進んでしきい値を下回るまで上限を超える呼び出しを拒否し、ブロックが繰り返された場合は、そのセッションの以降すべての呼び出しを拒否します。イベントはruleId: anomaly:<check>を含むwarn/blockedで記録されるため、変更なしでダッシュボードに表示されます。設定されていない場合は無効です。 - クォータ(
quotas): 固定ウィンドウ内のツール呼び出し数に上限を設け、セッションをまたいでカウントします。interceptors.anomalyが 1 つのセッションを監視するのに対し、クォータはユーザーが 10 個のセッションを開いた場合にも上限を適用します。各項目にはmatch(toolとuserの glob、upstream、label、group。すべて省略可能)、limit、windowSeconds、perを指定します。perはuser(デフォルト。OIDC ユーザー、またはユーザーがいない場合はセッション)、session、gateway(有料の upstream など、全員で共有する 1 つの予算)から選びます。例:quotas: [{ id: exports, match: { label: egress, group: contractors }, limit: 20, windowSeconds: 3600 }]。一致するクォータはすべてチェックされ、呼び出しはすべてのクォータでカウントされるか、どのクォータでもカウントされないかのいずれかです。upstream に送られる直前の呼び出しだけがカウント対象となるため、ポリシーで拒否された呼び出しはクォータを消費しません。クォータが上限に達すると、再試行までの時間を示して呼び出しを拒否し、ruleId: quota:<id>を記録します。また、anomaly.repeatedBlocksのカウント対象にはなりません。カウンターはプロキシプロセス内に保持されるため、複数のレプリカはそれぞれ個別にカウントします。 - ダッシュボードからのポリシー設定:
policy: { source: dashboard, refreshSeconds: 60 }(audit.dashboardと併用)を設定すると、このファイルではなく、GuardBee ダッシュボードで編集したワークスペースポリシーからラベル、ルール、汚染追跡、承認、デフォルト設定、インターセプターを取得します。upstream、listen、audit はローカル設定のままです。プロキシは起動時にポリシーを取得し、ETag を使って再確認します。更新はすべてのセッションの次の呼び出しから適用されます(ラベルは次のtools/listから適用されます)。ダッシュボードにポリシーが保存されていない場合、ダッシュボードに接続できない場合、またはドキュメントが無効な場合は、ローカルまたは最後に取得した有効なポリシーを維持します。ポリシーなしで動作することはありません。 - ドキュメント(
interceptors.documents、デフォルトで無効): ツールの結果、埋め込みリソース、リソース読み取りに含まれる画像や Blob(PDF、スクリーンショット、スキャンしたページなど)は、@guardbee/mcp-document-scannerで読み取ります。PDF のテキストレイヤーに加え、オプションのtesseract.js(スキャンした PDF には@napi-rs/canvasも必要)がインストールされている場合は、画像やテキストのないページに対して OCR を実行します。個人データまたは認証情報を含むドキュメントは、blockモードでは通知に置き換えられます(ruleId: documents:pii)。warnモードでは記録されます。該当する件数はpiiHitsに加算され、テキスト内の PII と同様にセッションの汚染追跡対象になります。読み取りできなかったドキュメント(OCR が未インストール、またはmaxBytes超過)はdocuments:unreadとして記録されます。{ enabled: true, action: block, ocr: true, maxBytes: 26214400 }。 - 個人データの件数: PII を含むツールの応答とリソースの読み取りには、カテゴリごとの件数である
piiHitsが付きます(tc_kimlik、vkn、iban、credit_card、email、phone_tr。認証情報はsecret)。記録するのは件数のみで値は記録せず、マスキングが無効でもカウントされます。ダッシュボードはこれらの情報を使って KVKK レポートを作成します。 - トークン化:
interceptors.piiMasking.mode: tokenizeを設定すると、モデルには値の代わりに<pii:tc_kimlik:7f3a9b21>が表示され、別のツールにそのまま渡すこともできます。プロキシは、そのサーバーに送る際に実際の値に戻します。トークンはセッション中のみメモリに保持され、piiMasking.detokenizeForEgress: trueを設定しない限り、egressツールに対して値に戻されることはありません。 - プロンプト(
prompts/get)にも、ツールの結果と同じインジェクションスキャンと PII マスキングが適用されます。 interceptors.definitionDrift.recheck: on-changeを設定すると、呼び出しごとのtools/listを省略し、サーバーがtools/list_changedを送信した後にのみ再確認します。プロキシはこの通知もエージェントに転送します。- 監査ログはハッシュチェーンで構成されています。デフォルトでは引数の代わりに SHA-256 を保存し、結果は保存しません。引数と結果を記録するには
audit.includePayloads: trueを設定します。guardbee-proxy verify-audit ./guardbee-audit.jsonlでログを検証できます。 guardbee-proxy validate --config guardbee-proxy.yamlは、サーバーを起動せずに設定を検証します。--configを指定しない場合、プロキシは./guardbee-proxy.yamlまたはGUARDBEE_PROXY_CONFIG=<file>.yamlも読み込みます。 上記の単一サーバー用の--形式では、0.x の動作が維持されます。ツール名にはプレフィックスが付かず、ペイロードはログに記録され、有害なフローは警告としてのみ記録されます。
セキュリティレポート
guardbee-proxy report は、監査ログをセキュリティチームや監査担当者向けの定期レポートに変換します。レポートには、合計数、OWASP Top 10 for LLM Applications (2025) と MITRE ATLAS に分類・対応付けされた検出結果、個人データの件数、最も多くブロックしたルールとツール、ハッシュチェーンの状態が含まれます。ペイロードは一切含まれず、件数とログに記録された理由のみが含まれます。
openssl genpkey -algorithm ed25519 -out guardbee-report.key # once; keep it private
openssl pkey -in guardbee-report.key -pubout -out guardbee-report.pub
guardbee-proxy report ./guardbee-audit.jsonl --days 30 --lang tr --key guardbee-report.key --out ekim.html
guardbee-proxy report ./guardbee-audit.jsonl --from 2026-10-01 --to 2026-11-01 --format json --out october.json
guardbee-proxy verify-report ekim.html --pubkey guardbee-report.pub
| カテゴリ | OWASP LLM Top 10 | MITRE ATLAS |
|---|---|---|
| 引数とプロンプトへのプロンプトインジェクション | LLM01 | AML.T0051.000, AML.T0054 |
| ツールの結果、リソース、プロンプトに隠された指示 | LLM01, LLM05 | AML.T0051.001 |
| システムプロンプトの抽出の試み | LLM07, LLM01 | AML.T0056 |
| 汚染されたツール定義 | LLM01, LLM03 | AML.T0051.001, AML.T0053 |
| 承認後に変更されたツール定義(ラグプル) | LLM03 | AML.T0010, AML.T0053 |
| 外部に送信される機密データや認証情報(有害なフロー、認証情報の送信、ドキュメント) | LLM02, LLM06 | AML.T0057 |
| ポリシーによって拒否または保留された呼び出し | LLM06 | AML.T0053 |
| 急増、クォータ、予算 | LLM10 | AML.T0029, AML.T0034 |
- 形式:Markdown(デフォルト)、自己完結型 HTML(ライトとダーク)、JSON。
--lang en|trを指定できます。 - 完全性:レポートには、正規化された JSON の SHA-256 と、対象期間内の最後の監査イベントのハッシュが含まれるため、ハッシュチェーンに結び付けられます。
--key(Ed25519、PEM)を指定すると署名され、公開鍵も含まれます。verify-reportは.json、.html、.mdのレポートを検証します。署名者を固定するには--pubkeyを渡してください。埋め込み鍵だけでは、署名後にレポートが変更されていないことは証明できますが、署名者が誰かは証明できません。 - ハッシュチェーンが破損している場合、レポートと stderr に表示されます。
- マッピングは
@guardbee/guard-core(classifyEvent、THREAT_CATEGORIES)にあり、@guardbee/mcp-threat-rulesのframework_mapツールで確認できます。
オブザーバビリティ(OpenTelemetry)
すべてのツール呼び出しは、MCP のセマンティック規約属性(mcp.method.name、gen_ai.tool.name、gen_ai.operation.name: execute_tool、mcp.session.id、enduser.id(OIDC))に加え、guardbee.upstream、guardbee.labels、guardbee.pii_hits を持つ、tools/call <tool> という名前のスパン(kind SERVER)になります。アップストリームへの呼び出しは子スパン(kind CLIENT、server.address = アップストリーム)になります。呼び出しに関するすべての監査イベント(ブロック、有害なフロー、承認、警告、サーバーのサンプリング要求)はスパンイベントになります(guardbee.blocked など。guardbee.rule_id と guardbee.reason を含みます)。ブロックされた呼び出しには guardbee.blocked: true が付与されます。引数と結果はスパンに記録されません。
プロキシが依存するのは @opentelemetry/api のみなので、SDK がなければスパンのコストは発生しません。OTLP/HTTP 経由でエクスポートするには、プロキシと同じ場所に SDK をインストールし、標準の環境変数を設定します。
npm i -g @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.example.com:4318
export OTEL_EXPORTER_OTLP_HEADERS="authorization=Bearer …" # if your collector needs it
export OTEL_SERVICE_NAME=guardbee-gateway # default: guardbee-security-proxy
Docker イメージには SDK がすでに含まれています。OTEL_SDK_DISABLED=true または OTEL_TRACES_EXPORTER=none を設定すると無効になり、先に独自の SDK を登録したプロセス(NODE_OPTIONS によるゼロコードのインストルメンテーション)では、その SDK が使用されます。
リモートモード(HTTP)
プロキシは、単一のクライアントのサブプロセスではなく、共有ネットワークサービスとして実行できます。エージェントは Streamable HTTP 経由で接続でき、アップストリームもリモートの Streamable HTTP サーバーにできます。
version: 1
listen:
transport: http # stdio (default) | http
host: 0.0.0.0 # 0.0.0.0 in a container; 127.0.0.1 on a laptop
port: 8787
path: /mcp
apiKeys: ["${GUARDBEE_PROXY_KEY}"] # agents send Authorization: Bearer <key>
oidc: # and/or: users sign in with your identity provider
issuer: https://login.acme.com
audience: guardbee-gateway
maxSessions: 100
upstreams:
github:
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
linear:
url: https://mcp.linear.app/mcp # a Streamable HTTP upstream
headers: { Authorization: "Bearer ${LINEAR_TOKEN}" }
audit:
sink: file
filePath: /var/log/guardbee/audit.jsonl
dashboard:
url: https://app.guardbee.ai/api/v1/gateway/events
apiKeyEnv: GUARDBEE_API_KEY # workspace API key with the gateway.write scope
-
セッションは分離されています。 各 MCP セッションは独自の taint 状態、PII トークン、ツールピンを持つため、あるエージェントの有害なフローが別のエージェントをブロックすることはありません。すべてのセッションでアップストリーム接続を共有します。監査イベントには
sessionIdが含まれます。 -
キーがなければ、ネットワークに接続できません。
listen.apiKeysまたはlisten.oidcを設定せずにループバック以外でリッスンする設定は、設定エラーになります。キーは一定時間比較で照合され、キーが間違っているか、ない場合は401が返されます。 -
ディレクトリグループ(
listen.ldap): LDAP / Active Directory のグループは、呼び出し元がすでに属しているグループ(OIDC ユーザー、または LLM ゲートウェイキーのユーザー)に追加されます。これにより、ルール、クォータ、llm.access、予算のgroupマッチングで、組織がすでに管理しているグループを利用できます。ディレクトリによって呼び出し元が認証されることはありません。認証は引き続き API キー、OIDC、またはゲートウェイキーで行います。listen: ldap: url: ldaps://dc.acme.com # certificates verified; caFile for a private CA bindDn: "CN=svc-guardbee,OU=Service,DC=acme,DC=com" bindPasswordEnv: LDAP_BIND_PASSWORD baseDn: "DC=acme,DC=com" userFilter: "(|(mail={user})(userPrincipalName={user}))" # {user} is escaped (RFC 4515) groupAttribute: memberOf # AD; or groupSearch: { baseDn, filter: "(member={dn})", nameAttribute: cn } groupName: cn # CN=Platform Eng,OU=… → "Platform Eng"; dn keeps the DN cacheSeconds: 300 onError: deny # directory down: 503 (default) | token-groups: go on without itSAML はプロキシの設定ではありません。MCP と LLM のエンドポイントはベアラートークンで認証し、SAML に対応する ID プロバイダー(Entra ID、Okta、ADFS、Keycloak)も OIDC トークンを発行します。これらには
listen.oidcを使用し、トークンに含まれないグループにはlisten.ldapを使用してください。 -
ユーザー(
listen.oidc): プロキシは API キーに加えて、または API キーの代わりに、OpenID Connect プロバイダー(Okta、Entra ID、Auth0、Keycloak、Google など)の JWT を受け付けます。発行者の JWKS(ディスカバリードキュメントまたはjwksUriから取得)、発行者、audience(必須。他のサービス向けに発行されたトークンは拒否されます)、有効期間を検証します。ユーザーはuserClaim(既定値はsub。emailが一般的です)から、グループはgroupsClaim(既定値はgroups。リストまたはスペース区切りの文字列)から取得します。ルールではuser(glob。例:"*@contractor.com")とgroupで照合でき、すべての監査イベントにuserが含まれます。セッションは開始したユーザーに紐づきます。別のユーザーのトークンでそのセッションにアクセスすると403が返されます。401にはWWW-Authenticate: Bearer resource_metadata=…が含まれ、/.well-known/oauth-protected-resource(RFC 9728)は発行者を示します。そのため、認可仕様を実装している MCP クライアントはサインイン先を確認できます。リバースプロキシの背後で使用する場合は、oidc.resourceに公開 URL を設定してください。 -
SIEM エクスポート(
audit.exporters): すべての監査イベントは、リクエスト処理の経路外でバッチ送信され、Splunk、Elasticsearch/OpenSearch、または syslog コレクターにも送られます。認証情報は環境変数から取得されます。イベントはローカルログと同じく、ハッシュ化され、ペイロードを含みません(includePayloadsが設定されている場合を除きます)。audit: exporters: - { type: splunk, url: "https://splunk:8088/services/collector/event", tokenEnv: SPLUNK_HEC_TOKEN, index: security } - { type: elastic, url: "https://es:9200", index: guardbee-audit, apiKeyEnv: ES_API_KEY } # or usernameEnv / passwordEnv - { type: syslog, host: siem.acme.internal, port: 6514, protocol: tls, format: cef, caFile: /etc/ssl/acme-ca.pem }Splunk には HEC イベント(
sourcetype: guardbee:audit)が送られます。Elasticsearch には_bulkのcreate操作でイベントハッシュを_idとして送信するため、バッチを再試行しても重複しません(409は送信済みとして扱われます)。また、@timestampも送信されます。Syslog は RFC 5424 形式で UDP、TCP、または TLS を使用します(証明書は常に検証され、プライベート CA にはcaFileを使用します)。フレーミング方式はオクテットカウント(既定)または改行、ファシリティは既定で 13(ログ監査)、メッセージ形式は既定で CEF または JSON です。CEF ではact、suser、rt、msg、およびtool/upstream/ruleId/sessionId/piiHits/labelsをcs1–cs6に格納します。重大度はblockedとtoxic_flowが 8、warnとapprovalが 5、それ以外は 3 です。各送信先はそれぞれ独自のキュー(最大 10,000 件)を保持します。SIEM が停止している場合、イベントは後で送信されます。SIEM がイベントを拒否した場合(401、マッピングエラー)は、stderr に 1 回だけ報告されます。 -
GET /healthzは{"ok": true, "sessions": N}を返します。maxSessionsを超えるセッションには503が返されます。 -
ダッシュボード:
audit.dashboardを設定すると、すべての監査イベントがバッチ送信で GuardBee ダッシュボードに送られます。ワークスペース API キーはapiKeyEnvから取得されます。キーがファイル内に保存されることはありません。ダッシュボードに接続できない場合、イベントはメモリ内(最大 10,000 件)で待機し、後で送信されます。キーが拒否された場合は stderr に 1 回だけ報告されます。ツール呼び出しがダッシュボードを待つことはありません。 -
initは Streamable HTTP サーバー(url+headers)もプロキシ経由にするようになりました。レガシー SSE サーバーはクライアント設定に残され、報告されます。
Docker:
docker build -t guardbee/mcp-security-proxy packages/security-proxy
docker run -p 8787:8787 -e GUARDBEE_PROXY_KEY=... -e GUARDBEE_API_KEY=... \
-v "$PWD/guardbee-proxy.yaml:/etc/guardbee/guardbee-proxy.yaml:ro" guardbee/mcp-security-proxy
LLM API ゲートウェイ
HTTP モードでは、プロキシをアプリとモデルプロバイダーの間に配置することもできます。アプリは SDK のベース URL をプロキシに向け、ゲートウェイキーを使用します。実際のプロバイダーキーはプロキシの環境内に保持され、コード、ノートパソコン、CI に渡ることはありません。
version: 1
listen: { transport: http, host: 0.0.0.0, port: 8787 }
upstreams: {} # MCP servers are optional when llm is set
llm:
path: /v1 # POST /chat/completions, /responses, /embeddings, /messages; GET /models
providers:
openai: { api: openai, baseUrl: https://api.openai.com/v1, apiKeyEnv: OPENAI_API_KEY }
anthropic: { api: anthropic, baseUrl: https://api.anthropic.com, apiKeyEnv: ANTHROPIC_API_KEY }
local: { api: openai, baseUrl: http://ollama:11434/v1, apiKeyEnv: OLLAMA_KEY }
routes: # first match wins; a request only goes to a provider of its own API
- { model: "gpt-*", provider: openai }
- { model: "claude-*", provider: anthropic }
- { model: "llama*", provider: local }
- { model: "gpt-4o-internal", provider: openai, masking: off } # explicit passthrough, audited
keys: # gateway keys, one per person or app (values from the environment)
- { keyEnv: GB_KEY_ALICE, user: [email protected], groups: [eng] }
- { keyEnv: GB_KEY_BILLING, user: billing-svc, groups: [apps] }
pricing: # USD per 1M tokens, for cost in the audit log and costUsd budgets
"gpt-4o": { input: 2.5, output: 10 }
"claude-*": { input: 3, output: 15 }
budgets:
- { id: per-user-daily, per: user, windowSeconds: 86400, costUsd: 20 }
- { id: contractors, per: user, windowSeconds: 3600, requests: 100, match: { group: contractors } }
- { id: team-tokens, per: gateway, windowSeconds: 86400, tokens: 5000000, match: { model: "gpt-*" } }
access: # which models a caller may ask for; matching entries add up
- { group: contractors, allow: ["gpt-4o-mini"] }
- { user: "*@acme.com", allow: ["gpt-*", "claude-*", "llama*"] }
accessDefault: allow # for callers no entry matches: allow | deny
sensitive: # prompts with personal data go to your own model instead
model: llama3.1 # needs a route for the request's API, or the request is refused
on: { pii: true, categories: [tc_kimlik, iban, passport], topics: [ma] }
mask: false # the data stays in-house, so it is not masked (true: keep masking)
maskResponses: false # also mask PII the model writes (prompts are always checked)
timeoutSeconds: 120
maxTextChars: 2000000 # longest single text the proxy masks; longer → 413
interceptors:
piiMasking: { enabled: true, mode: tokenize }
from openai import OpenAI
client = OpenAI(base_url="https://guardbee.acme.internal/v1", api_key=os.environ["GB_KEY"])
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "https://guardbee.acme.internal", apiKey: process.env.GB_KEY });
すべてのリクエストは、次の順に処理されます。
- キー。
Authorization: Bearer(OpenAI SDK)またはx-api-key(Anthropic SDK)を使用します。llm.keysのゲートウェイキーからユーザーとグループが特定されます。それ以外に、listen.apiKeysとlisten.oidcのトークンも受け付けます。llm.keysが設定されていて、listenにキーがない場合、不明なキーはループバック上であっても必ず拒否されます。 - ルーティングとアクセス。 モデル名によってプロバイダーが選択されます。ルートのないモデルには
404が返されます。呼び出し元がリクエストできるモデルはllm.accessで決まります。ユーザー(glob)またはグループに一致する各エントリーによって、そのモデルの glob が追加されます。一致するエントリーがない場合はaccessDefaultが適用されます。モデルへのアクセスが拒否されると403(ruleId: llm:access)が返され、GET /modelsには呼び出し元が利用できるモデルのみが表示されます。 - ユーザーメッセージとツールメッセージ内のプロンプトインジェクション(
interceptors.promptInjection)を検査します。アプリ独自のシステムプロンプトと、モデルのそれ以前のターンは検査されません。 - プロンプト内のあらゆる場所にある認証情報(
interceptors.egressSecrets)を検査します。プロバイダーキー、秘密鍵、接続文字列は、プロバイダーに届く前にブロックされます(403)。 - 機密ルーティング(
llm.sensitive)。プロンプトに個人データ(必要に応じて一部のcategoriesのみ)または指定されたtopicsのいずれかが含まれる場合、リクエストは指定されたモデルではなく、実行環境で動作する Ollama、vLLM、または llama.cpp モデルであるsensitive.modelに送られます。監査ログには、両方のモデル名と理由を示すruleId: llm:route:sensitiveのwarnイベントが記録されるため、ルーティングされたリクエストはダッシュボードに表示され、使用状況イベントにはllm.routedFromが含まれます。ローカルモデルにリクエストの API 用のルートがない場合(OpenAI 互換のローカルモデルしかないのに Anthropic リクエストが届いた場合など)、避けるべきプロバイダーには送らず、リクエストを拒否します。予算はルーティング先のモデルに対して計上されます。 - 予算。 固定期間における、ユーザー(またはキー)単位、あるいはゲートウェイ全体のリクエスト数、トークン数、USD を管理します。一致するすべての予算に余裕がある場合にリクエストが許可されます。トークン数とコストが判明するのは応答後のため、上限を超えるリクエストは完了し、次のリクエストには
Retry-After付きの429が返されます。 - メッセージ本文、ツール呼び出しの引数、ツールの結果に含まれる個人情報(PII)(
interceptors.piiMasking)はマスキングされます。またはmode: tokenizeの場合、プロキシが応答内で実際の値に戻すトークンに置き換えられます。プロバイダーには実際の値が渡らず、ユーザーには表示されます。テキストまたはコンテンツをソースとする Anthropic のdocumentブロックのテキストもマスキングされます。画像データ、ID、ツール名はそのまま残されます。プロキシが読み取れないコンテンツは拒否されます(失敗時の動作を参照)。 - 監査。
tool_callイベント(tool: llm:<model>、upstream: <provider>、labels: [llm]、user、piiHits)と、llm: { model, inputTokens, outputTokens, costUsd }を含むtool_responseイベントが記録されます。拒否は、ruleIdがllm:injection、llm:credential、または予算 ID のblockedイベントとして記録されます。
エンドポイント。 POST /chat/completions、POST /responses(OpenAI Responses API)、POST /embeddings は api: openai のプロバイダーに送られます。POST /messages は api: anthropic に送られます。GET /models には、呼び出し元が利用できるリテラル(glob ではない)ルートが表示されます。Responses の処理手順は chat と同じです。instructions、メッセージ項目(input_text、以前の output_text)、関数およびカスタムツール呼び出しの引数、ツール呼び出しの出力、保存済みプロンプトの variables はマスキングされます。推論項目、項目参照、プロバイダー独自のツール呼び出しはそのまま返されます。input_file と input_audio は拒否されます(415)。mode: tokenize の場合、トークンは応答内で実際の値に戻されますが、previous_response_id を使用して続ける会話には、このリクエストでは元に戻せない以前のリクエストのトークンが含まれています。応答に実際の値を表示する必要がある場合は、履歴を input で送信してください。
チャット、Messages、Responses ではストリーミングが機能します。通常のストリームはイベントごとに中継されます。OpenAI の場合、プロキシはトークン数をカウントするために stream_options.include_usage を追加し、クライアントが要求していない場合は、その追加チャンクを破棄します。回答を書き換える必要がある場合(PII トークンを元に戻す場合や maskResponses の場合)は、ストリーミング中に書き換えます。テキスト(メッセージの内容、ツール呼び出しの引数、Anthropic のテキスト、JSON、思考の差分)は、安全に切り出せる位置までのみ保持されます(数字が両側にないスペース。IBAN やカード番号などの桁区切りされた数値を分割しないためです。秘密鍵ブロックは END 行まで、Adres: ラベルのある行はその行の終わりまで保持されます)。その後、すでに送信した直近 300 文字と合わせて書き換えるため、Pasaport No: のようなラベルが次のイベントの値にも適用されます。保持されている残りのテキストは、選択肢の finish チャンクまたはブロックの stop イベントの直前に送信されます。Responses API では、output_text、関数呼び出しの引数、reasoning-summary の差分が同じ方法で保持され、書き換えられます。各 .done イベント、アイテム、パート、最終的な response.completed には同じ書き換え済みテキストが含まれるため、SDK が組み立てた回答は差分と一致します。プロバイダーのエラーは変更せずにそのまま渡されるため、SDK の再試行とエラー種別は引き続き機能します。
埋め込み
POST /embeddings は、埋め込みを生成する前にすべての入力テキストをマスクします。ベクトルは変更されずに返されます。マスクしたテキストをインデックス化する前に、次の点に注意してください。
- ここでのマスキングは決定的です。 組み込み値には固定のプレースホルダー(
[TC-KİMLİK]、***@[EMAIL])が使われ、mode: tokenizeは使用されません(トークンはリクエストごとに異なるため、同じドキュメントでも毎回異なる埋め込みになります)。組織のブロックパターンには、リクエストごとではなくテキストごとに番号が付けられます。同じテキストは、どのバッチで送信しても常に同じベクトルになります。 - マスクされた値だけが異なるドキュメントは、同じ埋め込みになります。 人名または ID 番号だけが異なる 2 つのレコードは 1 つのベクトルに集約されるため、類似度検索ではその人物を区別できません。ベクトルの類似度ではなく、独自の ID フィールド(メタデータ)でフィルターしてください。
- インデックス化とクエリには同じ経路を使ってください。 マスクされたテキストから作成したベクトルは、マスクされた他のテキストから作成したベクトルとのみ比較できます。インデックス化するドキュメントと検索クエリの両方を、同じ
piiMasking設定でゲートウェイ経由で送信してください。 - 長さ制限は要素ごとに適用されます。
inputの各要素はllm.maxTextCharsに照らしてチェックされます。上限を超える要素が 1 つでもあると、バッチ全体が413で拒否されます。トークン ID の入力([[1, 2, 3]])はマスクできないため、415で拒否されます。テキストを送信してください。 - 埋め込みには、プロンプトインジェクションのスキャン(インジェクションに関するドキュメントも引き続きインデックス化できる必要があります)と
llm.sensitiveのルーティング(別のモデルのベクトルはインデックスと比較できません)は適用されません。認証情報、トピック、予算、アクセスの各チェックは通常どおり適用されます。
失敗時の動作
ゲートウェイはフェイルクローズ方式です。リクエストをチェックまたはマスキングできない場合、プロバイダーは呼び出されません。拒否時には、エラーボディ(OpenAI と Anthropic のエラー形式における error.code)と x-guardbee-error ヘッダーに固定コードが含まれ、監査ログには blocked イベントが記録されます。マスキングできなかったテキストが、レスポンスや監査ログに含まれることはありません。検出器自身のエラーメッセージは、エラークラスに置き換えられます。
| ステータス | コード | 発生条件 | 監査ログの ruleId |
|---|---|---|---|
502 | guardbee_masking_failed | 検出器またはスキャンで例外が発生した場合、ブロックパターンの実行が時間制限を超えた場合、または名前モデル(ner:)が応答しなかった場合です。呼び出し前のすべてのチェック(インジェクション、認証情報、トピック、機密情報のルーティング、マスキング)が対象です。 | pii:masking-failed、pii:pattern-timeout、pii:ner-failed |
415 | guardbee_unmaskable_content | リクエストに、プロキシが読み取れないコンテンツが含まれている場合です。OpenAI の input_audio または file パーツ、PDF(base64、url、または file)ソースを持つ Anthropic の document、不明なパーツまたはブロックのタイプ、テキストでもリストでもないコンテンツが該当します。検出された内容はメッセージに記載されます。 | pii:unmaskable |
413 | guardbee_text_too_large | 単一のテキスト(メッセージ、パーツ、ツールの結果)が llm.maxTextChars(デフォルトは2,000,000文字)を超えている場合です。制限はリクエストごとではなく、テキストごとに適用されます。テキストを複数のメッセージまたはパーツに分割してください。 | pii:text-too-large |
拒否されないもの:画像(image_url、Anthropic の image)は変更せずに転送され、検査されません。アシスタントのターンでは、プロバイダー自体が生成したブロック(thinking、redacted_thinking、server_tool_use、*_tool_result)はそのまま返されます。415 と 413 が適用されるのは、リクエストがマスキングされる場合のみです。piiMasking.enabled: false の場合、masking: off が設定されたルートの場合、または llm.sensitive によってリクエストが社内にルーティングされ、mask: true が指定されていない場合は、何もマスキングされず、マスキングできないことを理由に拒否されることもありません。
マスキングできない回答(maskResponses、またはトークンを元に戻す際のエラー)は返されません。テキストはストリームの途中でも [GuardBee: withheld, …] に置き換えられ、同じ ruleId でイベントが記録されます。
ルートごとのパススルー。 llm.routes エントリーに masking: off を設定すると、そのルートのプロンプト(および回答)はマスキングされずに送信されます。暗黙のパススルーはなく、明示的に記述する必要があります。このようなリクエストはすべて、ルート名を含む ruleId: pii:masking-off の warn イベントを記録します。PII は引き続き piiHits にカウントされます。このルートでも、アクションが mask のトピックルールはマスキングを行います。
MCP ゲートウェイでマスキングに失敗すると、ツールの結果は返されません。クライアントには data: { guardbee: "guardbee_masking_failed" } を含む MCP エラー(InternalError、Result withheld: …)が返され、tool_response イベントは記録されません。
コーディングエージェントのフック
プロキシが認識するのは MCP のトラフィックだけです。コーディングエージェント自身のツール(シェル、ファイルの読み取り、Web の取得)はプロキシを経由しないため、cat .env の後に curl -d @.env … を実行しても検知できません。guardbee-hook は、各エージェントのフックシステムを通じて、それらのツールにも同じポリシーを適用します。
npm i -g @guardbee/mcp-security-proxy
guardbee-hook install --claude-code # ~/.claude/settings.json (PreToolUse + PostToolUse)
guardbee-hook install --cursor # ~/.cursor/hooks.json (shell, MCP, file reads)
guardbee-hook install --claude-code --project # .claude/settings.json in this repo
guardbee-hook install --cursor --config ~/.guardbee/guardbee-proxy.yaml --fail-closed
guardbee-hook uninstall --claude-code # removes only GuardBee's entries
インストール時には、ほかの設定やフックはすべて保持され、自身のエントリーは再実行時に置き換えられ、.guardbee-backup のコピーが残されます。--dry-run は書き込みを行わずに結果を表示します。グローバルインストールから実行してください。npx のキャッシュ内にあるパスは削除されることがあります。
ラベル。 組み込みツールには、実行内容に応じて MCP ツールと同じ 4 種類のラベルが付与されます。
| ツール | ラベル |
|---|---|
.env*、~/.ssh/、id_rsa、~/.aws/credentials、.kube/config、.npmrc、.netrc、*.pem、*.key、*.tfstate、credentials*.json などを対象とする Read/Grep/Glob | sensitive |
これらのファイルを読み取る Bash、printenv、キーチェーンやクラウドトークン関連のコマンド | sensitive |
curl、wget、nc、scp、ssh、git push、npm publish、docker push、gh api、aws s3 cp などを使う Bash | egress(curl/wget/git clone には untrusted も付与) |
rm -rf、git push --force、git reset --hard、DROP TABLE、terraform destroy、kubectl delete などを使う Bash | destructive |
WebFetch、WebSearch | untrusted + egress(URL や検索クエリを通じてデータが外部に送信される可能性があります) |
mcp__server__tool | プロキシの名前に基づくヒューリスティクス |
チェック内容(ツール実行前):データを外部に送信できる呼び出しに含まれる認証情報(egress:credential)、rules(upstream を claude-code または cursor に、tool をツール名(Bash、Read、WebFetch、mcp__github__create_pull_request)に、args をその入力に、topic をそのトピックに設定します)、および致命的な三要素のチェックです。セッションが信頼できないコンテンツと機密データ(秘密情報を含むファイル、または任意の出力に含まれる個人識別情報(PII))の両方を読み取った後は、egress 呼び出しが拒否される(taint.mode: strict)、確認を求められる(approve)、または記録されます(warn)。ツール実行後は、出力内の PII によってセッションが汚染され、出力に隠された指示があるとエージェントは停止し(理由とともに decision: block)、トピックルールが出力に適用されます。
rules:
- { id: ask-destructive, match: { upstream: claude-code, label: destructive }, action: approve }
- { id: no-prod, match: { tool: Bash, args: { command: "*--context prod*" } }, action: deny }
- フックが「許可」と判断することはありません。異議がなければ、エージェント独自の許可プロンプトがそのまま表示されます。
denyはpermissionDecision: deny(Cursor ではpermission: deny)に、approveはaskに変換されます。 defaults.actionはエージェントツールには適用されません。適用すると、MCP ツール向けのdenyデフォルトによって、すべてのシェルコマンドがブロックされてしまいます。停止対象をルールに記述してください。- フックの呼び出しごとに、別個のプロセスが起動します(約 0.1~0.2 秒)。セッションの汚染状態は、各エージェントセッションごとに
~/.local/state/guardbee/hooks($XDG_STATE_HOME)に保存され、1 日後に期限切れになります。監査ログは、設定済みのファイルシンク、または~/.local/state/guardbee/hooks/audit.jsonlに出力されます(エージェントのターミナルには出力されません)。また、audit.dashboardが設定されている場合はダッシュボードにも出力されます。 - 設定の読み込み順は、
--config、GUARDBEE_PROXY_CONFIG、~/.guardbee/guardbee-proxy.yaml、組み込みのデフォルトの順です。フック専用のファイルにはhooks: {}セクションがあり、アップストリームはありません。 - 内部エラーが発生した場合、ツールは実行されます(フェイルオープン)。ただし、フックを
--fail-closedを付けてインストールした場合は実行されません。 - 組み込みツールの出力は書き換えられません。Claude が読み取るファイルをマスキングすると、後続の
Edit呼び出しでファイルと完全に一致させる必要があるため、問題が発生します。
MCP ツール
プロキシは独自のツールを登録しません。対象サーバーのツールを転送し、呼び出しごとにインターセプターチェーンを適用します。
インターセプター
プロンプトインジェクション検出器
@guardbee/guard-core の27個のルールを、ツール引数、ツールの結果、リソース、プロンプトに適用します。
- ブロック(精度の高い重大度「高」または「重大」のルール13個):英語とトルコ語の指示の上書き(「ignore all previous instructions」、「önceki talimatları yok say」)、
DAN mode、「bypass your safety guardrails」、チャットテンプレートのトークン(<|im_start|>、[INST])、<system>タグ、Unicodeタグ文字に隠されたテキスト、CSSで非表示にされた要素やHTMLコメント内の指示、Markdown画像を使ったデータ流出ビーコン、システムプロンプトの開示要求(英語とトルコ語)、「send the user's data to https://…」。 - 警告のみ(14個):通常のテキストにも現れる一般的なフレーズ(「act as」、「you are now」、「developer mode」、「jailbreak」、「override policy」、「sen artık」)と、ゼロ幅文字や双方向制御文字、行頭の
System:ラベルなど、中程度の重大度のシグナル。 - Base64: 読み取り可能なテキストにデコードされる文字列は、デコードしてブロックルールと照合するため、エンコードされたデータに指示を隠すことはできません。
1.1以降、一般的なフレーズだけではブロックされなくなりました。1.0では、ツールの結果に含まれる「use jailbreak mode」や「act as a reverse proxy」はブロックされていましたが、現在はログに記録され、結果に警告が付与されます。
PIIマスキング
| データの種類 | チェック | 入力例 | 出力 |
|---|---|---|---|
| 国民ID(TC Kimlik No) | 公式チェックサム | 10000000146 | [TC-KİMLİK] |
| IBAN(TR) | mod-97 | TR330006100519786457841326 | TR**[IBAN] |
| カード番号 | Luhn | 4111 1111 1111 1111 | ****-****-****-[KART] |
| メールアドレス | — | [email protected] | ***@[EMAIL] |
| 電話番号(TR) | 携帯電話/固定電話/850プレフィックス | 0532 123 45 67 | +90-***-***-**[TELEFON] |
| 納税番号(VKN) | チェックディジット。VKN/Vergi No のラベルが必要 | VKN: 1234567890 | VKN: [VKN] |
| プロバイダーのキー | secret-scanner のキーとトークン形式22種(AWS、GitHub、Stripe、OpenAI、Anthropic、Slack、…) | ghp_…、AKIA… | [API-KEY] |
| その他のAPIキー | sk_/api_/token_…のプレフィックスと英数字 | api_k3y9x8… | [API-KEY] |
| 秘密鍵 | BEGIN…ENDブロック全体 | -----BEGIN … PRIVATE KEY----- | [PRIVATE-KEY] |
| 接続文字列 | URL内のユーザー名とパスワード(localhostを除く) | postgres://app:pw@db/prod | [CONNECTION-STRING] |
| JWT | — | eyJhbGc... | [JWT-TOKEN] |
| パスポート | Pasaport/Passport のラベルが必要 | Pasaport No: U12345678 | Pasaport No: [PASAPORT] |
| SGK/SSK/Bağ-Kur番号 | ラベルが必要 | SGK sicil no: 1234567890 | SGK sicil no: [SGK-NO] |
| MERSİS番号 | ラベルが必要。16桁 | MERSİS No: 0123 4567 8901 2345 | MERSİS No: [MERSİS] |
| 生年月日 | Doğum tarihi/DOB/Date of birth が必要 | Doğum tarihi: 12.05.1990 | Doğum tarihi: [DOĞUM-TARİHİ] |
| 郵便住所 | Adres:/Address:、数字または道路を示す語が必要。IP/メール/ウェブ/ウォレットのアドレスは対象外 | Adres: Moda Cad. No 5 | Adres: [ADRES] |
| KEPアドレス | @….kep.tr | [email protected] | ***@[KEP] |
人名 ( ner: が必要) | トルコ語のローカルNERモデル(@guardbee/ner) | Ayşe Yılmaz aradı | [KİŞİ] aradı |
| IPアドレス (オプトイン) | 有効なIPv4オクテット。:: または8グループを含むIPv6 | 10.0.3.17 | [IP] |
| MACアドレス (オプトイン) | 区切り文字が1つの16進数ペア6組 | aa:bb:cc:dd:ee:ff | [MAC] |
| 車両ナンバープレート(TR) (オプトイン) | 州番号01~81、文字/数字の組み合わせ。単位は対象外(16 GB 2400) | 34 ABC 123 | [PLAKA] |
IPアドレス、MACアドレス、ナンバープレートはインフラの出力に頻出するため、リストに指定しない限り無効です:interceptors.piiMasking.include: [ip_address, mac_address, plate_tr]。各カテゴリの件数は piiHits とダッシュボードのKVKKレポートに記録されます。
マスク対象の選択。 piiMasking.preset でマスクする検出器を選択し、enable / disable で名前を指定して検出器を追加または除外します(名前は piiHits に表示される tc_kimlik、email、github_pat などです)。upstreams.<name> では、特定のアップストリームまたは LLM プロバイダーに対する選択を置き換えます。検出範囲は狭まりません。デフォルトの検出器はすべて引き続き piiHits にカウントされ、セッションに汚染フラグが付きます。選択に従うのはマスキングのみです。オプトイン検出器はどのプリセットにも含まれません。enable(または include)に指定してください。
| プリセット | マスク対象 |
|---|---|
ALL(デフォルト) | オプトイン検出器を除く上記のすべての検出器に加え、認証情報 |
KVKK | 個人データのみ。認証情報は含みません |
IDENTITY | 人名(ner: 使用時)、住所、生年月日 |
IDENTIFIERS | TC kimlik、VKN、MERSİS、SGK、パスポート、IBAN、カード、電話番号、KEP、メールアドレス |
SECRETS | API キー、トークン、秘密鍵、接続文字列、JWT |
interceptors:
piiMasking:
enabled: true
preset: KVKK
disable: [address]
upstreams:
ops: { preset: SECRETS, enable: [ip_address] } # an infrastructure server
不明なプリセット、検出器名、またはアップストリーム名がある場合、設定の検証に失敗します。
独自のパターン。 piiMasking.blockPatterns は、どの検出器にも認識されない値(ケース番号やクライアントコードなど)をマスクします。正規表現、または { pattern, category } を指定します。カテゴリは番号付きプレースホルダー(CLIENT_NO → <CLIENT_NO_1>、同じ値には同じ番号が付与されます)および piiHits のキーになります。重複する場合は組み込みの検出が優先され、ブロックパターンはプリセットにかかわらず適用されます。piiMasking.allowPatterns は、検出された値全体のテキストが一致する場合(公開サポートアドレスなど)、その値をマスクしません。一致するたびに、保護レベルを下げるため、ruleId: pii:allow を含む warn イベントが監査ログに記録されます。
interceptors:
piiMasking:
enabled: true
blockPatterns:
- { pattern: "CL-\\d{6}", category: CLIENT_NO }
- "DOSYA/\\d{4}" # category CUSTOM
allowPatterns: ["destek@acme\\.com\\.tr"]
各パターンには時間制限(マスキング呼び出しあたり 100 ms)が設けられているため、処理が極端に遅くなる正規表現でプロキシが停止することはありません。また、意図的に2種類のパターンは反対の動作をします。ブロックパターンが時間制限を超えると結果を返さず(pii:pattern-timeout)、LLM ゲートウェイは 502 を返します。許可パターンが時間制限を超えると、そのパターンはスキップされ、値はマスクされたままになります(pii:allow-skipped)。パターンは最大 25 個、各パターンは最大 200 文字です。不正な正規表現、または組み込みの検出器と同じ名前(EMAIL、IBAN、SECRET など)のカテゴリがある場合、設定の検証に失敗します。LLM ゲートウェイのリクエストでは、guardbee 本文フィールド({ "guardbee": { "blockPatterns": [...] } }。プロバイダーに本文を渡す前に削除されます)に独自のブロックパターンを追加できます。追加したパターンは設定済みのパターンに加えられます。allowPatterns を含むリクエストは 400 で拒否されます。リクエストで追加できるのはマスキングのみです。
チェックサムが一致しない番号(注文番号や追跡コードなど)は、そのまま残ります。
人名。 人名を正規表現で検出することはできないため、person_name の検出にはモデルを使用します。オフラインで動作するトルコ語 BERT NER モデル @guardbee/ner は、186 MB で、ノート PC の CPU ではテキストあたり約 8 ms かかります。設定の最上位に ner: セクションがない限り、無効です。このセクションはデプロイ設定であり、ポリシーではありません。ダッシュボードのポリシーで設定したり、移動したりすることはできません。
ner:
url: http://127.0.0.1:8765 # guardbee-ner serve (sidecar)
tokenEnv: GUARDBEE_NER_TOKEN # its bearer token, from the environment
timeoutMs: 5000 # default
# or in this process (install @guardbee/ner next to the proxy):
# ner:
# modelDir: /opt/guardbee/ner # default: guardbee-ner download's cache
サイドカーは npx @guardbee/ner download を実行してから npx @guardbee/ner serve を実行して起動します。ループバックでリッスンします。別のホストにあるサイドカーは https:// にする必要があります。そうしないと、テキストが暗号化されずにネットワークを通過します。モデルは読み込み前に、固定された SHA-256 ハッシュと照合してファイルを検証します。
マスキングの前に、値に含まれるすべての文字列がモデルに一度送られ、その後、正規表現の検出器と同じ処理でマスクされます。そのため person_name は、他の検出器と同様に preset / enable / disable、mode: tokenize(<pii:person_name:…>)、許可パターンに従います。人名はツールの結果、structuredContent、リソース、プロンプトテンプレート、サンプリングの回答、LLM ゲートウェイのリクエスト(埋め込みも含む)でマスクされ、llm.sensitive ルーティングの対象にもなります。モデルの回答(maskResponses)、コーディングエージェントのフック、またはマスキングが無効な場合は検出されません。
ner:が設定されていてモデルが回答できない場合(サイドカーの停止、タイムアウト、エラーなど)、何も送信されません。ツールの結果は保留され、LLM リクエストは502 guardbee_masking_failed、ruleId: pii:ner-failedで拒否されます。テキストにはモデルだけが検出できる名前が含まれている可能性があるため、マスキングが正規表現のみの処理にフォールバックすることはありません。
モデルは一部の名前を見逃し、一部の単語を誤検出します。文頭の大文字で始まる単語が名前でもある場合(Barış、Umut)、どちらの判定になることもあります。また、すべて小文字のチャット(elifle konuştum)は見逃されることがよくあります。測定値はpii-benchをご覧ください。
設定
環境変数で設定できます。
| Variable | Default | Description |
|---|---|---|
PROXY_MODE | block | blockまたはwarn |
PROXY_LOG | console | consoleまたはfile |
PROXY_LOG_PATH | ./proxy-audit.jsonl | ログファイルのパス |
PROXY_PII_MASK | true | PII マスキングを有効にします |
PROXY_INJECTION_CHECK | true | インジェクションチェックを有効にします |
ライセンス
MIT — GuardBee
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。