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 版本范围的开发者,说明拒绝行为、调用者身份和关联标识的处理方式。

主要功能

  1. 01通过 `beforeToolCall` 控制服务端工具
  2. 02支持阻止或替代式拒绝决策
  3. 03在对话轮次开始前筛查入站消息
  4. 04传递已验证的调用者和策略输入
  5. 05读取由调用方提供的关联标识

适用场景

  1. 01拒绝或替代有风险的工具调用
  2. 02在 Agent 轮次前筛查用户消息
  3. 03使用调用者和输入应用远程规则
  4. 04将 Guard 决策关联到会话上下文

安装

复制下面的命令,在终端中运行。

$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 会包装 Agent 现有的 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 mix-in 包装其工具。这不是 TanStack Start HTTP protect()。也不是 Cloudflare Workers HTTP protect()(@arcjet/node / 基于请求)。

导出项:guardHooks、cloudflareThinkContext。没有 guardTool,也没有 guardThink mixin。跳过工具调用要通过 beforeToolCall 的返回值实现,而不是从 execute 抛出异常。没有 guardInbound,也没有 guardApproval。没有未带版本号的 @arcjet/guard/cloudflare-think 别名。

两个接口,一个决策规则:

  • 工具调用 → guardHooks。返回一个 { beforeToolCall } 对象,Think 子类会将其委托给该对象。交付结果是 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/ 不要另设第二个 slug,也不要覆盖其他任何 /guards/... 页面。示例(main 树中的链接返回 404;目前没有开放的示例 PR,因此在 examples/cloudflare-think-agent 合并到 main 之前,请引用此分支): examples/cloudflare-think-agent (没有验证 fixture)。不要另设第二个示例名称。

闸门是 beforeToolCall 的 block / substitute

Think 会包装每个服务端工具的 execute,并先调用 beforeToolCall(先运行子类钩子,再运行扩展)。返回 block 或 substitute 会跳过 execute。必须失败时关闭:出错时始终返回 其中之一,不要返回 void(这会执行工具),也不要抛出异常。默认 DENY 为 substitute,并附带 ArcjetDenialResult,让模型看到 { arcjetDenied: true, … }。 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 用于调整模型调用;它们不是此策略门控。在轮次开始前,在应用程序中调用 arcjet.guard()(或在 beforeTurn 开始时调用并根据决策采取措施)。核心 guard() 会默认放行失败的情况:ALLOW 并不能证明规则已运行。如果此调用点必须采用失败时拒绝策略,请根据 decision.hasFailedOpen() 进行门控;guardHooks 已默认采用此策略。

actor / inputs——省略后,远程规则永远不会触发

只有当此次调用传入 actor 或类型化的 inputs 时,声明了这些值的远程策略才会评估它们。在 guardHooks(以及入站 arcjet.guard())中传入 actor 和 inputs。从 @arcjet/guard 导入 policyInput,不要从适配器路径导入。检查已安装的类型——不要传入辅助函数未声明的字段。

  • 省略 actor / inputs 后,要求这些值的远程策略永远不会触发。守卫调用仍会执行;只是远程规则没有可读取的内容。
  • 解析器抛出异常时会失败关闭,这是默认的 onGuardError: "deny" 行为(不可用时的替代方案——绝不能是 void)。
  • actor 是经过身份验证的调用方,绝不是由模型生成的工具参数。Think 信封中的 input 由模型控制。应从经过身份验证的服务器端上下文(已签名的会话、已验证的令牌,或你已经信任的 Durable Object 状态)派生 actor,而不是从 ctx.input 派生。

rules / action / metadata 可以访问 { toolName, input }。actor / inputs 解析器还会将 Think 的 beforeToolCall 上下文作为第二个参数接收。

先询问用户的问题

只询问无法从代码中推断的信息;同时建议默认值。

  1. 哪些工具有风险(会产生外部副作用、不可逆、花费金钱或发送消息)?这些工具由 guardHooks 门控。不带 execute 的客户端工具不在范围内。
  2. 有哪些限制?(例如:“每位用户每分钟查询 10 次” → tokenBucket。)
  3. 元数据中的用户是谁——不透明的用户/租户 ID(绝不能是 PII)?默认值:无。同一个经过身份验证的 ID 就是 actor。将你已有的会话/会话 ID 传入 guardHooks({ sessionId })。该 ID 是关联 ID,不是用户 ID。不要使用 Durable Object ID 或 toolCallId。
  4. Arcjet 停机是否不可接受?所有辅助函数的默认值都是 onGuardError: "deny"。务必明确询问轮次开始前的入站筛查:在那里失败关闭意味着 Agent 不会回答,因此在那个调用点选择 "allow" 是常见且合理的做法。

读者常犯的错误

  1. 这不是 Vercel AI SDK。 不要导入 @arcjet/guard/vercel-ai/v7,也不要用该命名空间中的 guardTool 包装 Think 工具。Think 已经管理 streamText。
  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 后,远程规则永远不会触发。 解析器抛出异常时会失败关闭。请从 @arcjet/guard 导入 policyInput。
  11. 不要用原始 guard() 手动包装每个 Think 工具。

步骤 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 路径则不行。如果 Agent 尚无 guard 客户端,请在模块作用域中仅启动一次:

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

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

步骤 2:拦截工具调用——委托给 beforeToolCall

将当前对话经过身份验证的调用方传入 guardHooks。Durable Object 通常是每个对话对应一个——但仍 不要将 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())、替代式拒绝(模型会看到 ArcjetDenialResult)、阻止式拒绝(原因字符串;仅限真正的 DENY)、 服务不可用 + onDeny: "block" 时仍采用替代式拒绝、无返回值的 execute、不抛出异常、绝不生成 ID、省略 actor / inputs (远程规则不执行)、解析器抛出异常(故障关闭并采用替代式拒绝),以及故障关闭(guard 不可达时 → 替代式拒绝,绝不无返回值)。确认 needsApproval 从未被 当作拦截机制,并且工具没有再用 @arcjet/guard/vercel-ai/v7 包装。
  3. 在 Arcjet Console / CLI 中确认,决策共用调用方持有的 session / conversation ID——而不是 Durable Object ID 或 toolCallId——且 actor 是经过身份验证的调用方。
  4. 使用真实 ARCJET_KEY 进行的手动 E2E 测试,在实际运行之前仍属待验证。

示例: examples/cloudflare-think-agent (无 verify fixture;main 中尚无此目录树)。不要 另行编造示例名称。不要在此 skills 仓库中添加示例。

评论

支持 Markdown