第7章 工具代理连接协议与业务边界
代理必须知道自己被允许代理什么
工具代理的价值在于把不稳定的 Agent 输入转换成稳定的业务请求,并在进入后端前执行清晰的约束。它可以适配 MCP、HTTP 或其他工具协议,但不能成为一个接受任意 URL、任意凭据和任意 HTTP 方法的通用转发器。通用转发一旦与受信服务身份结合,就容易形成混淆代理:低权限调用方借代理的高权限访问自己到不了的资源。
本书代理只接受 Operation 结构,目标身份服务和业务服务由配置固定。客户端不能指定 upstream、headers、Authorization、HTTP method 或回调地址。未知 JSON 字段被拒绝,输入体最大八千一百九十二字节,说明字段有长度上限,对象 ID 不允许路径分隔符。网络层还设置连接与读取超时,并拒绝自动重定向,避免一个合法目标把凭据带向另一个位置。
这些约束并不解决所有注入问题。说明字段可以包含“忽略策略”或一个恶意 URL;系统把它当作无执行语义的文本,不根据其中内容发起请求。但如果后续版本把说明交给一个会主动取 URL 的组件,这个边界就变化了,必须新增校验和实验。安全结论应与数据流绑定,而不是与某段正则表达式绑定。
代理预检与业务终检
代理检查身份、当前状态、租户、动作结构和 scope,减少无效请求进入业务服务。业务服务重复关键校验,并承担对象状态、审批和事务修改。两层判断有重叠,但职责不同:代理看工具契约,业务看权威对象。不要为减少重复代码而把业务授权全部搬到网关,因为网关往往不知道对象所属、最新状态和事务条件。
失败案例是代理允许访问 https://orders.internal,但跟随 302 重定向到攻击者域名,并携带原 Authorization。另一个案例是代理将 id=../../admin 拼进 URL,最终访问了管理接口。实验使用固定 /execute,将对象作为 JSON 数据提交,不通过模型字段拼接路由,同时让 HTTP 客户端不自动跟随重定向。生产系统仍需要出口限制与 DNS、TLS 和代理设置的审阅。
错误处理也属于边界
身份依赖不可用时,代理返回拒绝或服务不可用,不应为了“提高可用性”跳过验证。业务错误应被结构化映射,不能把下游异常中的令牌、数据库连接串或内部堆栈直接返回给模型。返回给 Agent 的拒绝信息应足够它纠正输入,但不暴露其他租户对象是否存在。
实验中的错误码简化了产品体验:credential、policy、delegation_unavailable 等只表达决策类别。生产平台可以增加稳定请求 ID,让支持人员在受控审计中找到细节。应分别统计认证失败、授权拒绝、审批失效、业务冲突与依赖超时,否则一次身份平台故障可能被误看成“模型不会用工具”。
实验与扩展
运行 go test -race -run TestTenantScopeAndUntrustedToolInput -v ./lab,确认多余 url 字段返回 400,跨租户和非法对象路径被拒绝,而恶意说明作为普通文本仍可伴随一个合法只读请求执行。再运行 TestKeyRotationAndDependencyOutage,确认身份服务关闭后代理没有降级放行。
本书没有引入通用代理框架,也没有实现流式工具返回、文件上传和长连接。接入新工具时,应重新评估输入大小、目标控制、重定向、超时、返回数据敏感性和副作用。练习是为“上传客户附件”工具写一个契约:谁决定存储目标、文件属于哪个租户、病毒扫描之前能否被读取、重试是否产生重复对象,以及审计中能保存哪些元数据。
代理返回值也需要最小化
订单服务可能返回客户电话、地址、支付渠道引用和内部风控字段,而 Agent 当前只需要确认订单状态。代理可以采用明确的结果模型,减少发送给模型的字段。但这种裁剪不能替代业务端字段授权;如果代理被绕过,业务服务仍应只向相应身份返回允许的数据。
返回内容越多,工具结果里的不可信文本越容易影响后续模型行为,也更容易被写进对话记录。应区分业务所需事实、调试信息和敏感数据。不要把完整下游响应放进一个 debug 字段供模型自行挑选。错误中同样不需要包含下游请求头和原始堆栈。
本书为便于观察返回简化 Object,字段数量有限,尚未实现多角色字段权限。扩展到真实订单系统时,可先定义一个只读 DTO,明确每个字段是否需要、是否可能含用户内容、能否进入模型上下文和审计。这样的接口设计通常比事后增加“不要泄漏隐私”的提示词更可控。