mcpField
I
integrate-arcjet-guard-cloudflare-think

integrate-arcjet-guard-cloudflare-think

github.com/arcjetセキュリティ469 回インストール3 週間前に更新
リポジトリ

integrate-arcjet-guard-cloudflare-think について

このスキルは、Arcjet GuardをCloudflare Thinkに統合し、サーバー側ツール呼び出しの制御と受信メッセージの検査を行う方法を案内します。指定されたThinkのバージョン範囲を使う開発者向けに、拒否動作、呼び出し元の識別、相関IDの扱いを説明します。

主な機能

  1. 01`beforeToolCall`でサーバー側ツールを制御
  2. 02ブロックと代替応答による拒否に対応
  3. 03ターン開始前に受信メッセージを検査
  4. 04認証済みの呼び出し元とポリシー入力を渡す
  5. 05呼び出し元が用意した相関IDを参照

活用シーン

  1. 01リスクのあるツール呼び出しを拒否または代替
  2. 02エージェントのターン前にユーザーメッセージを検査
  3. 03呼び出し元と入力を使ってリモートルールを適用
  4. 04Guardの判定をセッションコンテキストに関連付け

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/arcjet/skills --skill integrate-arcjet-guard-cloudflare-think

インストール後、Claude Code で/integrate-arcjet-guard-cloudflare-thinkを入力して読み込まれたか確認します。

詳細

Cloudflare Think に Arcjet Guard を統合する

@arcjet/guard/cloudflare-think/v0 は、エージェントが既に使用している Arcjet クライアントをラップします。Arcjet API と直接通信することはありません。共有の Guard の基本機能(クライアント、ルール、ラベル、判定、キャプチャ、登録)については、 ../arcjet/references/guards_javascript.md を参照してください。Think 固有ではない内容については、このリファレンスを読み込んでください。

公式の @cloudflare/think >=0.3.0 <1 のみが対象です。これは、beforeToolCall が機能する ToolCallDecision(allow / block / substitute)を返し、すべてのサーバー側ツールの execute をラップしてフックを先に実行する最初のリリースです。Think が 1.x をリリースするまでは /v1 はありません。これは Vercel AI SDK(ai / @arcjet/guard/vercel-ai/v7)ではありません。Think は内部で streamText を管理するため、AI SDK のミックスインでツールを重ねてラップしないでください。TanStack Start の HTTP protect() でもありません。Cloudflare Workers の HTTP protect()(@arcjet/node/リクエストベース)でもありません。

エクスポート:guardHooks、cloudflareThinkContext。guardTool も guardThink ミックスインもありません。スキップするには beforeToolCall の戻り値を使います。execute から例外をスローする方法ではありません。guardInbound も guardApproval もありません。バージョン指定のない @arcjet/guard/cloudflare-think エイリアスもありません。

2 つの用途、1 つの判定ルール:

  • ツール呼び出し → guardHooks。Think のサブクラスが委譲する { beforeToolCall } オブジェクトを返します。Think の ToolCallDecision を返します。モデルにペイロードを見せるには substitute({ action: "substitute", output: ArcjetDenialResult })、モデルに理由文字列を見せるには block({ action: "block", reason })を使います。void/{ action: "allow" } の場合は実行されます。例外をスローしないでください。スローすると拒否ではなく、未加工の例外/onChatError になります。
  • 相関付け → cloudflareThinkContext は呼び出し元が所有する ID を読み取ります。ID を生成することはありません。Durable Object の ID、toolCallId、requestId、traceId は読み取りません。

ドキュメント:https://docs.arcjet.com/guards/cloudflare-think/ 2 つ目のスラッグを作成したり、他の /guards/... ページを上書きしたりしないでください。例(main ツリーは 404 になり、例の PR もまだないため、examples/cloudflare-think-agent が main に存在するまでは、このブランチを引用してください): examples/cloudflare-think-agent (検証用フィクスチャはありません)。2 つ目の例の名前を作らないでください。

ゲートは beforeToolCall の block/substitute

Think はすべてのサーバー側ツールの execute をラップし、最初に beforeToolCall(サブクラスのフック、その後に拡張機能)を呼び出します。block または substitute を返すと、execute はスキップされます。フェイルクローズにしてください。エラー時には必ずいずれかを返してください。void(ツールが実行されます)を返したり、例外をスローしたりしないでください。デフォルトの DENY は、モデルに { arcjetDenied: true, … } を見せる ArcjetDenialResult を使った substitute です。onDeny: "block" では代わりに { action: "block", reason } を使い、構造化フィールドは破棄されます。onDeny: "block" が適用されるのは 実際の DENY のみです。利用不可の場合もフェイルクローズし、substitute を使います。コアの guard() は引き続きフェイルオープンします(hasFailedOpen())。

クライアントツール(サーバー側の execute がないツール)は、このフックに入りません。Think が自動マージするワークスペースツールは、execute があればこのフックに入ります。

needsApproval はポリシーゲートではありません

AI SDK の needsApproval、確認形式のツール、人間による再開は、ヒューマン・イン・ザ・ループの仕組みです。人間が承認した後も、Guard はツール呼び出しに対して実行されます。TanStack の needsApproval、Mastra の requireApproval、Claude の canUseTool、LangGraph の interrupt()、OpenAI Agents の needsApproval、Genkit の interrupt()、Google ADK の requireConfirmation と同じ落とし穴があります。guardApproval はありません。

chat() / submitMessages() の前に受信内容をスクリーニングする

guardInbound はありません。beforeTurn / beforeStep はモデル呼び出しを調整するものであり、このポリシーゲートではありません。ターン開始前に、アプリケーション内で(または beforeTurn の冒頭で判定に基づいて対応したうえで)arcjet.guard() を呼び出してください。コアの guard() はフェイルオープンです。ALLOW はルールが実行された証拠ではありません。この呼び出し箇所でフェイルクローズが必要な場合は decision.hasFailedOpen() に基づいてゲートしてください。guardHooks はすでにこれをデフォルトにしています。

actor / inputs を省略すると、リモートルールは一切実行されない

actor または型付き inputs を宣言するリモートポリシーは、この呼び出しでそれらの値を送信した場合にのみ評価されます。guardHooks(および受信時の arcjet.guard())に actor と inputs を渡してください。policyInput はアダプターパスではなく、@arcjet/guard からインポートしてください。インストール済みの型を確認し、ヘルパーで宣言されていないフィールドは渡さないでください。

  • actor / inputs を省略すると、それらを必要とするリモートポリシーは一切実行されません。ガード呼び出し自体は行われますが、リモートルールが読み取るものがありません。
  • リゾルバーが例外をスローすると、デフォルトの onGuardError: "deny" ではフェイルクローズします(代替値を使ってください。決して void にしないでください)。
  • actor は認証済みの呼び出し元であり、モデルが生成したツール引数ではありません。Think エンベロープの input はモデルが制御します。actor は、すでに信頼しているサーバー側の認証済みコンテキスト(署名付きセッション、検証済みトークン、または Durable Object の状態など)から導出してください。ctx.input から導出してはいけません。

rules / action / metadata には { toolName, input } が渡されます。actor / inputs リゾルバーには、2 番目の引数として Think の beforeToolCall コンテキストも渡されます。

まず人に確認する質問

コードから推測できないことだけを質問し、デフォルト値を提案してください。

  1. リスクのあるツールはどれですか(外部への副作用、取り消し不能、金銭の消費、メッセージの送信など)?これらは guardHooks でゲートします。execute のないクライアントツールは対象外です。
  2. 制限は何ですか?(例:「ユーザーあたり毎分 10 回の検索」→ tokenBucket。)
  3. メタデータに使用するユーザーは誰ですか。不透明なユーザー ID/テナント ID(個人を特定できる情報は使わない)ですか?デフォルトはなしです。その同じ認証済み ID が actor になります。すでにある会話 ID/セッション ID を guardHooks({ sessionId }) に渡してください。この ID は相関 ID であり、ユーザーではありません。Durable Object ID や toolCallId は使わないでください。
  4. Arcjet の停止は許容できませんか?すべてのヘルパーはデフォルトで onGuardError: "deny" です。ターン前の受信内容のスクリーニングについては、明示的に確認してください。そこでフェイルクローズするとエージェントは応答しなくなるため、その呼び出し箇所だけ "allow" を選ぶのは一般的で妥当な選択です。

読者が間違えやすい点

  1. これは Vercel AI SDK ではありません。 @arcjet/guard/vercel-ai/v7 をインポートしたり、その名前空間の guardTool で Think ツールをラップしたりしないでください。streamText は Think がすでに所有しています。
  2. guardInbound はありません。 chat() / submitMessages() の前にコアの guard() でスクリーニングしてください。
  3. needsApproval は HITL であり、ポリシーではありません。
  4. フェイルクローズとは、エラー時に必ず block または substitute を返すことです。 void はツールを実行します。例外をスローしても拒否にはなりません。利用不可の場合は、onDeny: "block" であっても substitute でフェイルクローズします。
  5. デフォルトの DENY は substitute(構造化された ArcjetDenialResult)です。block は理由文字列の経路で、フィールドが失われます。これは実際の DENY にのみ適用されます。
  6. guardHooks はミックスインではありません。 beforeToolCall を hooks.beforeToolCall(ctx) に委譲してください。guardThink(Think, …) はありません。
  7. 相関 ID は読み取るものであり、新たに作るものではありません。 Durable Object ID、toolCallId、requestId、traceId は決して使わないでください。
  8. インポートパスにはバージョンが含まれます。 @arcjet/guard/cloudflare-think/v0 は解決されますが、@arcjet/guard/cloudflare-think は解決されません。
  9. レート制限のキーには、モデルが指定した注文 ID ではなく、認証済みの呼び出し元を使ってください。 actor も同様です。
  10. actor / inputs を省略すると、リモートルールは一切実行されません。 リゾルバーが例外をスローするとフェイルクローズします。policyInput は @arcjet/guard からインポートしてください。
  11. Think の各ツールを、素の guard() で個別にラップしないでください。

手順 1:guard クライアントをインストールして取得する

公開済みの @arcjet/guard と、ピア依存関係の @cloudflare/think をインストールします。 プロジェクトですでに使用しているパッケージマネージャーを使ってください。git SHA は固定しないでください。

npm install @arcjet/guard @cloudflare/think
pnpm add @arcjet/guard @cloudflare/think

ピア依存関係の範囲は @cloudflare/think >=0.3.0 <1 です。バージョン付きのパス @arcjet/guard/cloudflare-think/v0 は解決されますが、バージョンなしの @arcjet/guard/cloudflare-think パスは解決されません。エージェントに guard クライアントがまだない場合は、モジュールスコープで 一度だけ 起動します。

import { launchArcjet } from "@arcjet/guard";

export const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });

手順 2:ツール呼び出しをゲートする — beforeToolCall に委譲する

guardHooks に、現在の会話の 認証済み呼び出し元を渡します。 Durable Object は会話ごとに 1 つであることが多いですが、それでも this.ctx.id / this.name を actor や相関情報として使用しないでください。

import { Think } from "@cloudflare/think";
import { launchArcjet, policyInput, tokenBucket } from "@arcjet/guard";
import { guardHooks } from "@arcjet/guard/cloudflare-think/v0";

const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
const lookupLimit = tokenBucket({
  bucket: "lookups",
  refillRate: 10,
  intervalSeconds: 60,
  maxTokens: 10,
});
// From auth middleware or Durable Object state you already trust.
// Not this.ctx.id / this.name, and not a model-produced argument.
const userId = authenticatedUserId;
const conversationId = authenticatedConversationId;

const hooks = guardHooks(arcjet, {
  action: ({ toolName }) => `${toolName}.invoked`,
  // Authenticated caller — never a model-produced order id / ctx.input.
  actor: userId,
  inputs: ({ toolName }) => ({
    tool: policyInput.server.string(toolName),
  }),
  // Keyed on the authenticated caller, not the model-supplied order id.
  rules: () => [lookupLimit({ key: userId, requested: 1 })],
  sessionId: conversationId,
  onGuardError: "deny",
  // default DENY is substitute (ArcjetDenialResult). onDeny: "block"
  // uses { action: "block", reason } on real DENY only. Unavailable
  // still substitutes.
});

export class SupportAgent extends Think<Env> {
  // needsApproval on a tool is HITL — not this policy gate
  override beforeToolCall = hooks.beforeToolCall;
}

サブクラスがすでに beforeToolCall を実装している場合は、 hooks.beforeToolCall(ctx) を呼び出し、その ToolCallDecision を返します。 DENY を無視して処理を続けたり、@arcjet/guard/vercel-ai/v7 でもラップしたりしないでください。

rules を省略すると、ルールは送信されません。それでも guard の呼び出しは行われます。ポリシーによってツールの実行が許可されたときに ALLOW は outcome: "success" を記録します。execute の完了時ではありません。

手順 3:ターンの開始前に受信内容をスクリーニングする

import { detectPromptInjection, policyInput } from "@arcjet/guard";
import { cloudflareThinkContext } from "@arcjet/guard/cloudflare-think/v0";

const inbound = detectPromptInjection();
const decision = await arcjet.guard({
  label: "message.received",
  actor: userId,
  inputs: { content: policyInput.server.string(userText) },
  rules: [inbound(userText)],
  ...cloudflareThinkContext({ context: { sessionId: conversationId } }),
});
if (decision.conclusion === "DENY") {
  throw new Error("message blocked");
}
if (decision.hasFailedOpen()) {
  throw new Error("inbound guard unavailable");
}

// Then chat() / submitMessages() / the WebSocket turn.

guardInbound はありません。guard() はフェイルオープンするため、必ず hasFailedOpen() を確認してください。

手順 4:相関情報

cloudflareThinkContext は、呼び出し元が所有する ID を読み取ります。優先順位は次のとおりです。 correlationId、次に sessionId、次に呼び出し元が所有するラッパー上の conversationId(cloudflareThinkContext({ context: appContext }))、 次にベアなアプリオブジェクト上のコピー、次に guardHooks({ sessionId }) / init.correlationId です。ID を生成することはありません。Durable Object の ID / this.ctx.id / this.name は読み取りません。Think や AI SDK が生成する toolCallId、requestId、traceId、ストリーム ID も読み取りません。toolCallId と toolName を持つ beforeToolCall コンテキストは、Think のエンベロープです。そのオブジェクトのトップレベルの sessionId は無視されます。 ターンごとに相関 ID を作らないでください。有効な ID が何も残らない場合、その呼び出しは相関なしとなり、生成された ID に結び付けられることはありません。

統合を検証する

  1. npm run typecheck(またはプロジェクトの型チェック)が成功することを確認します。
  2. 受信 PI(ターン開始前に実行し、hasFailedOpen() も確認)、代替 DENY(モデルが ArcjetDenialResult を受け取る)、ブロック DENY(理由文字列。実際の DENY のみ)、利用不可時に onDeny: "block" でも代替されること、execute の void、 例外をスローしないこと、ID を決して生成しないこと、省略された actor / inputs (リモートルールは無音)、リゾルバーの例外(フェイルクローズの代替処理)、フェイルクローズ(到達不能な guard → 代替処理、決して void にしない)をテストします。 needsApproval がゲートとして扱われないこと、ツールが @arcjet/guard/vercel-ai/v7 でもラップされていないことを確認します。
  3. Arcjet Console / CLI で、判定に呼び出し元が所有するセッション ID / 会話 ID が使われていること(Durable Object の ID や toolCallId ではないこと)、および actor が認証済み呼び出し元であることを確認します。
  4. 実際の ARCJET_KEY を使用する手動 E2E は、実行するまで未検証です。

実装例: examples/cloudflare-think-agent (検証用フィクスチャはありません。main にはまだこのツリーがありません)。別の例の名前を作らないでください。この skills リポジトリに例を追加しないでください。

コメント