mcpField
I
integrate-arcjet-guard-agent-framework-go

integrate-arcjet-guard-agent-framework-go

github.com/arcjet安全480 次安装3 周前更新
代码仓库

关于 integrate-arcjet-guard-agent-framework-go

此技能介绍如何在 Go 应用中为 Microsoft Agent Framework 接入 Arcjet Guard,涵盖函数、具名工具、Agent 调用和输入文本的防护,并说明默认拒绝策略与实现要点。

主要功能

  1. 01使用 GuardAction 防护 Go 函数
  2. 02使用 GuardTool 或 GuardTools 包装工具
  3. 03通过 GuardMiddleware 防护 Agent
  4. 04筛查输入文本和工具调用
  5. 05将拒绝作为成功的工具结果返回

适用场景

  1. 01为退款等高风险工具设置速率限制
  2. 02筛查用户提示词中的注入内容
  3. 03防护 mcptool.ListTools 返回的 MCP 工具

MCP 相关

该技能说明如何用 GuardTools 包装 mcptool.ListTools 返回的 MCP 工具,并按工具名称选择策略。

安装

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

$npx skills add https://github.com/arcjet/skills --skill integrate-arcjet-guard-agent-framework-go

安装后在 Claude Code 中输入/integrate-arcjet-guard-agent-framework-go确认已加载。

详细介绍

将 Arcjet Guard 集成到 Microsoft Agent Framework for Go

github.com/arcjet/arcjet-go/agentframework 封装了应用现有的 arcjet.GuardClient。Guard 的通用基础知识(客户端、规则、标签、 判定、捕获)见 ../arcjet/references/guards_go.md。有关非框架特定的内容,请加载该参考文档。

三个使用场景,一条判定规则:

  • 任意 Go 函数 → 根模块中的 arcjet.GuardAction。不依赖 任何框架。返回 *arcjet.GuardDeniedError 或 *arcjet.GuardUnavailableError。
  • 可在接线时命名的 tool.FuncTool(来自 functool.New、 mcptool.ListTools 或 agenttool.New)→ agentframework.GuardTool,或 用于列表的 GuardTools。结果仍是 tool.FuncTool。
  • 由模型选择工具的 Agent,或要筛查的用户文本 → agent.Config.Middlewares 中的 agentframework.GuardMiddleware。

这是 Go 框架。Microsoft Agent Framework 的 .NET 和 Python 实现没有 Arcjet 适配器。请勿在此处导入 @arcjet/guard 或 arcjet.guard。

拒绝是工具结果,而不是错误

框架会将工具错误替换为 Error: Function failed.,并允许连续三轮失败的工具调用,第四轮才会结束运行。因此,GuardTool 会将 arcjet.GuardDenialResult 作为成功结果返回。请勿通过返回错误来“修复”这一行为,也不要设置 IncludeDetailedErrors 来让错误携带拒绝信息。

默认采用失败关闭策略

当无法评估策略时,GuardTool、GuardTools、GuardMiddleware 和 arcjet.GuardAction 都会拒绝调用。这与策略 DENY 是不同的结果,并且两者会向模型返回不同的结果。

  • 无法评估策略:arcjet.NewGuardUnavailableResult(),原因 为 ERROR,重试提示为 5 秒。
  • 策略拒绝调用,例如速率限制已耗尽或检测到提示词注入:arcjet.NewGuardDenialResult(decision),或者返回 ToolPolicy.OnDeny 设置的替代结果。

入站拒绝会以其消息结束运行。仅在可用性比强制执行更重要的场景(例如只读查询)中设置 OnGuardError: arcjet.OnGuardErrorAllow;它只会改变不可用时的处理路径,而 DENY 始终会阻止调用。

人工审批不属于策略

tool.ApprovalRequiredFunc 和 toolapproval 中间件会请求人工审批。 GuardTool 会保留被包装工具的审批状态,并且没有适配器能将 Arcjet 判定传递给自动审批规则。托管工具 (hostedtool.*)由提供商运行,无法进行 Guard 保护。

工作流无需单独的辅助工具

workflow/ 不是第三种使用场景。通过 agentworkflow 托管的 Agent 会经由 agent.Agent.Run 运行,因此 GuardMiddleware 和 GuardTool 在工作流中同样适用,无需更改。裸执行器属于普通 Go 代码:在其处理程序中调用 arcjet.GuardAction,并在其中决定拒绝时如何处理运行,因为下游不会替你做决定。

关联信息不会在默认执行环境中保留。inproc.Default 是 inproc.OffThread,其运行循环会构建自己的上下文,因此在调用 Run 前使用 arcjet.ContextWithCorrelationID 设置的 ID 永远不会传递到执行器,也无法关联任何内容。只有 inproc.Lockstep 会传递调用方的上下文。请改为在执行器中显式设置 GuardActionPolicy.CorrelationID;无论如何,它的优先级都高于上下文中的 ID。

首先向用户询问的问题

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

  1. 哪些工具有风险(会产生副作用、不可逆、花费资金、发送消息)?这些工具应使用默认拒绝的 ToolPolicy。只读工具可以使用 OnGuardErrorAllow。
  2. 有哪些限制?(“每位用户每小时最多退款 5 次” → 使用硬编码 Bucket 的 GuardTokenBucket。)
  3. Actor 和 SecurityMetadata.User 中的用户是谁:使用经过身份验证的请求中的不透明 ID,绝不使用 PII。
  4. 关联 ID是什么:应用已有的会话 ID 或请求 ID。在运行 Run 前,使用 arcjet.ContextWithCorrelationID 将其放到上下文中。绝不要生成新的 ID。
  5. 是否应筛查输入文本?如果是,使用带有 InboundPolicy 的 GuardMiddleware。此处默认拒绝会导致 Agent 在服务中断期间停止工作,因此在这个位置使用 OnGuardErrorAllow 是合理的选择。

读者容易犯错的地方

  1. 标签是硬编码的。 使用 Action: "refund.issued",绝不要使用 fmt.Sprintf。在 GuardTools 策略函数中,使用 switch t.Name()。
  2. 使用 Action,而不是 Label。 包装器接收 Action;原始 GuardRequest 接收 Label。它们使用相同的标识。在构建辅助函数时会验证 ToolPolicy.Action 和 InboundPolicy.Action,而不是等到首次调用时才验证——拼写错误会在启动时导致 GuardTool / GuardTools / GuardMiddleware 返回错误。对于自行构建的标识,请使用 arcjet.ValidateGuardLabel 检查。
  3. 拒绝是一种结果。 参见上文。
  4. 关联 ID 由调用方负责。 将其放到上下文中,或存储在 agentframework.CorrelationIDStateKey 对应的会话状态中。绝不要使用 agent.Session.ServiceID:提供方会在运行过程中改写它。没有任何机制会生成 ID。
  5. Args 会解码工具的类型化输入。 对于结构体输入,参数对象就是该值;对于标量,框架会将其包装起来。
  6. 只包装一次。 GuardTools 和 GuardMiddleware 会跳过已由 GuardTool 包装的工具,因此组合使用时每次调用只会评估一次。
  7. 通常从参数中解析规则,因此 ToolPolicy.Rules 是一个函数,而不是切片。
  8. 捕获中的 success 表示策略已判定该操作;degraded 表示它在没有完整判定的情况下通过 OnGuardErrorAllow 运行。

步骤 1:安装并查找 Guard 客户端

go get github.com/arcjet/arcjet-go@latest
go get github.com/arcjet/arcjet-go/agentframework@latest

agentframework 模块要求 Go 1.26;根 github.com/arcjet/arcjet-go 模块仍支持 Go 1.25 及更高版本。如果项目使用的 Go 版本低于 1.26,请告知用户并停止。将一个 arcjet.NewGuardClient 创建在包级作用域;如果 Key 为空,它会读取 ARCJET_KEY。

步骤 2:为可命名的工具添加门控:GuardTool

guarded := agentframework.MustGuardTool(guard, issueRefund, agentframework.ToolPolicy{
	Action: "refund.issued",
	Actor: func(ctx context.Context, _ json.RawMessage) (string, error) {
		return userIDFromContext(ctx)
	},
	Rules: agentframework.Args(func(ctx context.Context, in refundArgs) ([]arcjet.GuardRuleInput, error) {
		userID, err := userIDFromContext(ctx)
		if err != nil {
			return nil, err
		}
		return []arcjet.GuardRuleInput{refundLimit.Key(userID, 1)}, nil
	}),
	Metadata: arcjet.SecurityMetadata{Reversibility: "irreversible"}.Metadata(),
})

Actor 和 Rules 会在每次调用时运行,并接收该次调用的上下文,因此应从中读取调用方身份。包级变量或闭包捕获的变量会让整个进程生命周期内的用户保持不变:所有调用方都会共享同一个速率限制桶,且每次判定都会使用相同的 Actor。包装工具时只会读取一次 Metadata,因此不应在其中放置每次调用各异的身份信息;Actor 已包含用户信息。

GuardTool 返回 (tool.FuncTool, error);MustGuardTool 会在配置错误时触发 panic,适合用于包级初始化。对于 MCP 工具,请使用 GuardTools 包装 mcptool.ListTools 的输出,并传入一个按工具名称进行分支处理的策略函数。

返回 false 的策略函数会让工具处于未保护状态

GuardTools 会在策略函数返回 false 时原样追加工具;对于不是 tool.FuncTool 的工具也会如此。 不会发生错误,不会记录日志,工具会在未受保护的情况下运行。因此, 按工具名称进行的 switch 只会保护其中列出的名称:以后添加工具时, 在有人添加对应的 case 之前,该工具都不会受到保护。

请决定要采用哪种方式,并明确写下来:

  • 默认拒绝。 为每个工具返回一个策略;对于 switch 无法识别的名称,返回限制性策略。这样就不会有新工具处于未保护状态。
  • 默认允许。 对无法识别的名称返回 false,并在测试中断言受保护工具的集合,以便发现新增工具。

无论选择哪种方式,都要进行测试。GuardTools 在决定不处理时会返回传入的同一个值,因此,身份比较可以检测出未受保护的工具。

对于默认拒绝,将一个策略未覆盖的工具传入策略,并断言该工具仍会受到保护。测试的重点正是这个未列出的工具:如果默认分支不再覆盖它,测试就会失败。

tools := []tool.Tool{lookupOrder, issueRefund, unlistedTool}
guarded, err := agentframework.GuardTools(client, tools, toolPolicy)
if err != nil {
	t.Fatal(err)
}
for i, g := range guarded {
	if g == tools[i] {
		t.Errorf("tool %q came back unguarded", g.Name())
	}
}

对于默认允许,列出你打算不予保护的工具,并断言只有这些工具不受保护。不要将无法识别的工具传入上面的循环:策略本就应该拒绝处理它,因此断言每次运行都会报告该工具。如果向 Agent 添加了没有对应策略 case 的工具,它就会作为无人列出的条目出现在这里。

guarded, err := agentframework.GuardTools(client, agentTools, toolPolicy)
if err != nil {
	t.Fatal(err)
}
exempt := []string{"health_check"} // deliberately unguarded
var unguarded []string
for i, g := range guarded {
	if g == agentTools[i] {
		unguarded = append(unguarded, g.Name())
	}
}
if !slices.Equal(unguarded, exempt) {
	t.Errorf("unguarded %v, want %v", unguarded, exempt)
}

这里使用 arcjet.GuardConfig{Key: "ajkey_test"} 构建客户端就足够了。 测试不会调用 Arcjet API,因此这里用的是测试中的字面值,而不是应用代码中的密钥。

第 3 步:保护 Agent:GuardMiddleware

mw, err := agentframework.GuardMiddleware(guard, agentframework.MiddlewareConfig{
	Tools: func(t tool.Tool) (agentframework.ToolPolicy, bool) {
		switch t.Name() {
		case "issue_refund":
			return agentframework.ToolPolicy{Action: "refund.issued", /* ... */}, true
		}
		return agentframework.ToolPolicy{}, false
	},
	Inbound: &agentframework.InboundPolicy{
		Action: "message.received",
		Rules: func(_ context.Context, text string) ([]arcjet.GuardRuleInput, error) {
			return []arcjet.GuardRuleInput{promptScan.Text(text)}, nil
		},
	},
})
a := anthropicprovider.NewAgent(client, anthropicprovider.AgentConfig{
	Config: agent.Config{Tools: tools, Middlewares: []agent.Middleware{mw}},
})

中间件会处理来自 agent.Config.Tools 和单次运行中通过 agent.WithTool 传入的工具。

第 4 步:关联

ctx = arcjet.ContextWithCorrelationID(ctx, conversationID)
resp, err := a.RunText(ctx, prompt).Collect()

验证集成

  1. go vet ./... 通过且项目可构建。
  2. 进行测试:允许的工具调用、受速率限制的调用(模型收到 arcjetDenied: true 并对此作出解释)、入站提示词注入被拒绝(运行在调用提供方之前结束),以及故障关闭(无法访问 Arcjet 时返回 reason: "ERROR")。
  3. 在 Arcjet Console 或 CLI 中确认各项决策使用相同的调用方自有关联 ID。
  4. 使用真实 ARCJET_KEY 进行的手动端到端测试仍待验证,须在运行后确认。

示例: examples/agentframework。 不要另造第二个示例名称。不要在此 skills 仓库中添加示例。

评论

支持 Markdown