mcpField
S
security-proxy

security-proxy

ローカル
github.com/GuardBeeセキュリティ22 日前に更新

security-proxy について

security-proxy は MCP サーバーの前段に置くセキュリティゲートウェイで、プロンプトインジェクション検査、PII マスキング、監査を行います。複数サーバーへの共通ポリシー適用、ツール定義の変更検出、サーバー間の有害なデータフロー遮断にも対応します。

主な機能

  1. 01プロンプトとツール結果の注入を検出
  2. 02応答内の PII と秘密情報をマスキング
  3. 03設定可能な監査ログを記録
  4. 04複数の MCP サーバーに共通ポリシーを適用
  5. 05サーバー間の有害なデータフローを遮断
  6. 06ツール定義の変更を検出

活用シーン

  1. 01既存 MCP サーバーへのセキュリティ層の追加
  2. 02複数サーバーのアクセス方針の一元管理
  3. 03機密データが外部ツールへ渡るのを防止
  4. 04エージェントのツール呼び出しの監査

MCP 対応

MCP ゲートウェイとしてクライアントと 1 つ以上の MCP サーバーの間に入り、ツールやプロンプトを中継しながら呼び出しと結果をポリシーに照らして検査します。

設定

ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。

mcp.json · 共通設定
{
"mcpServers": {
"guardbee-security-proxy": {
"command": "npx",
"args": [
"-y",
"@guardbee/mcp-security-proxy"
]
}
}
}
起動コマンドnpx -y @guardbee/mcp-security-proxy

ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。

パッケージ
npm@guardbee/mcp-security-proxy1.37.1

ツール

ツール一覧はまだ取得されていません

ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。

詳細

翻訳後に原文が更新されています。最新の内容は原文をご確認ください。

@guardbee/mcp-security-proxy

🇬🇧 英語 | 🇹🇷 トルコ語 | 🇨🇳 中国語 | 🇯🇵 日本語 | 🇰🇷 韓国語 | 🇧🇷 ポルトガル語 | 🇪🇸 スペイン語 | 🇩🇪 ドイツ語

npm version npm downloads License: MIT

あらゆる 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 10MITRE ATLAS
引数とプロンプトへのプロンプトインジェクションLLM01AML.T0051.000, AML.T0054
ツールの結果、リソース、プロンプトに隠された指示LLM01, LLM05AML.T0051.001
システムプロンプトの抽出の試みLLM07, LLM01AML.T0056
汚染されたツール定義LLM01, LLM03AML.T0051.001, AML.T0053
承認後に変更されたツール定義(ラグプル)LLM03AML.T0010, AML.T0053
外部に送信される機密データや認証情報(有害なフロー、認証情報の送信、ドキュメント)LLM02, LLM06AML.T0057
ポリシーによって拒否または保留された呼び出しLLM06AML.T0053
急増、クォータ、予算LLM10AML.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 it
    

    SAML はプロキシの設定ではありません。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 });

すべてのリクエストは、次の順に処理されます。

  1. キー。 Authorization: Bearer(OpenAI SDK)または x-api-key(Anthropic SDK)を使用します。llm.keys のゲートウェイキーからユーザーとグループが特定されます。それ以外に、listen.apiKeys と listen.oidc のトークンも受け付けます。llm.keys が設定されていて、listen にキーがない場合、不明なキーはループバック上であっても必ず拒否されます。
  2. ルーティングとアクセス。 モデル名によってプロバイダーが選択されます。ルートのないモデルには 404 が返されます。呼び出し元がリクエストできるモデルは llm.access で決まります。ユーザー(glob)またはグループに一致する各エントリーによって、そのモデルの glob が追加されます。一致するエントリーがない場合は accessDefault が適用されます。モデルへのアクセスが拒否されると 403(ruleId: llm:access)が返され、GET /models には呼び出し元が利用できるモデルのみが表示されます。
  3. ユーザーメッセージとツールメッセージ内のプロンプトインジェクション(interceptors.promptInjection)を検査します。アプリ独自のシステムプロンプトと、モデルのそれ以前のターンは検査されません。
  4. プロンプト内のあらゆる場所にある認証情報(interceptors.egressSecrets)を検査します。プロバイダーキー、秘密鍵、接続文字列は、プロバイダーに届く前にブロックされます(403)。
  5. 機密ルーティング(llm.sensitive)。プロンプトに個人データ(必要に応じて一部の categories のみ)または指定された topics のいずれかが含まれる場合、リクエストは指定されたモデルではなく、実行環境で動作する Ollama、vLLM、または llama.cpp モデルである sensitive.model に送られます。監査ログには、両方のモデル名と理由を示す ruleId: llm:route:sensitive の warn イベントが記録されるため、ルーティングされたリクエストはダッシュボードに表示され、使用状況イベントには llm.routedFrom が含まれます。ローカルモデルにリクエストの API 用のルートがない場合(OpenAI 互換のローカルモデルしかないのに Anthropic リクエストが届いた場合など)、避けるべきプロバイダーには送らず、リクエストを拒否します。予算はルーティング先のモデルに対して計上されます。
  6. 予算。 固定期間における、ユーザー(またはキー)単位、あるいはゲートウェイ全体のリクエスト数、トークン数、USD を管理します。一致するすべての予算に余裕がある場合にリクエストが許可されます。トークン数とコストが判明するのは応答後のため、上限を超えるリクエストは完了し、次のリクエストには Retry-After 付きの 429 が返されます。
  7. メッセージ本文、ツール呼び出しの引数、ツールの結果に含まれる個人情報(PII)(interceptors.piiMasking)はマスキングされます。または mode: tokenize の場合、プロキシが応答内で実際の値に戻すトークンに置き換えられます。プロバイダーには実際の値が渡らず、ユーザーには表示されます。テキストまたはコンテンツをソースとする Anthropic の document ブロックのテキストもマスキングされます。画像データ、ID、ツール名はそのまま残されます。プロキシが読み取れないコンテンツは拒否されます(失敗時の動作を参照)。
  8. 監査。 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
502guardbee_masking_failed検出器またはスキャンで例外が発生した場合、ブロックパターンの実行が時間制限を超えた場合、または名前モデル(ner:)が応答しなかった場合です。呼び出し前のすべてのチェック(インジェクション、認証情報、トピック、機密情報のルーティング、マスキング)が対象です。pii:masking-failed、pii:pattern-timeout、pii:ner-failed
415guardbee_unmaskable_contentリクエストに、プロキシが読み取れないコンテンツが含まれている場合です。OpenAI の input_audio または file パーツ、PDF(base64、url、または file)ソースを持つ Anthropic の document、不明なパーツまたはブロックのタイプ、テキストでもリストでもないコンテンツが該当します。検出された内容はメッセージに記載されます。pii:unmaskable
413guardbee_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/Globsensitive
これらのファイルを読み取る Bash、printenv、キーチェーンやクラウドトークン関連のコマンドsensitive
curl、wget、nc、scp、ssh、git push、npm publish、docker push、gh api、aws s3 cp などを使う Bashegress(curl/wget/git clone には untrusted も付与)
rm -rf、git push --force、git reset --hard、DROP TABLE、terraform destroy、kubectl delete などを使う Bashdestructive
WebFetch、WebSearchuntrusted + 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-97TR330006100519786457841326TR**[IBAN]
カード番号Luhn4111 1111 1111 1111****-****-****-[KART]
メールアドレス—[email protected]***@[EMAIL]
電話番号(TR)携帯電話/固定電話/850プレフィックス0532 123 45 67+90-***-***-**[TELEFON]
納税番号(VKN)チェックディジット。VKN/Vergi No のラベルが必要VKN: 1234567890VKN: [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: U12345678Pasaport No: [PASAPORT]
SGK/SSK/Bağ-Kur番号ラベルが必要SGK sicil no: 1234567890SGK sicil no: [SGK-NO]
MERSİS番号ラベルが必要。16桁MERSİS No: 0123 4567 8901 2345MERSİS No: [MERSİS]
生年月日Doğum tarihi/DOB/Date of birth が必要Doğum tarihi: 12.05.1990Doğum tarihi: [DOĞUM-TARİHİ]
郵便住所Adres:/Address:、数字または道路を示す語が必要。IP/メール/ウェブ/ウォレットのアドレスは対象外Adres: Moda Cad. No 5Adres: [ADRES]
KEPアドレス@….kep.tr[email protected]***@[KEP]
人名 ( ner: が必要)トルコ語のローカルNERモデル(@guardbee/ner)Ayşe Yılmaz aradı[KİŞİ] aradı
IPアドレス (オプトイン)有効なIPv4オクテット。:: または8グループを含むIPv610.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: 使用時)、住所、生年月日
IDENTIFIERSTC kimlik、VKN、MERSİS、SGK、パスポート、IBAN、カード、電話番号、KEP、メールアドレス
SECRETSAPI キー、トークン、秘密鍵、接続文字列、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をご覧ください。


設定

環境変数で設定できます。

VariableDefaultDescription
PROXY_MODEblockblockまたはwarn
PROXY_LOGconsoleconsoleまたはfile
PROXY_LOG_PATH./proxy-audit.jsonlログファイルのパス
PROXY_PII_MASKtruePII マスキングを有効にします
PROXY_INJECTION_CHECKtrueインジェクションチェックを有効にします

ライセンス

MIT — GuardBee

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。