# 通用 HTTP 工具会抹掉权限边界

对于智能体宿主来说，一次通用 HTTP 调用看起来只是一个工具。但在实际运行中，它可能包含数千种能力。只要更改 URL、凭据、方法、请求头或请求体，同一个函数就能读取状态页、导出客户记录、轮换签名密钥，甚至删除生产资源。

正因为存在这种错位，工具级权限不是可靠的安全边界。批准智能体使用 `http_request`，并没有说明智能体获准做什么。真正有用的决策要再下沉一层，在那里把特定凭据与解析后的目标地址、允许的方法和受约束的请求细节绑定起来。

即使智能体本身表现正常，这种区别也很重要。提示词里会有错误，检索到的文本可能包含恶意指令，而宽泛的工具会把一个小小的规划错误变成真实的 API 操作。给工具换一个更友好的名字解决不了问题。你必须对即将通过网络发出的操作本身授权。

## 一次调用包含许多能力

只有当工具的输入无法彻底改变其权限时，工具才算得上能力边界。上游地址固定、只能用 GET、响应格式也固定的天气查询，接近单一能力。一个允许传入任意 URL、动词、请求头和请求体的函数，则是一台可编程的网络客户端。

设想一个只有五个常见字段的模式：`url`、`method`、`headers`、`body` 和 `credential_name`。智能体宿主可能只把它作为工具列表中的一项展示出来。但使用读取令牌执行 `GET /projects/42`，和使用管理员令牌执行 `DELETE /projects/42`，不该得到相同的授权判断。请求公共 API 和请求解析到私有网络服务的地址，也不该得到相同判断。

Model Context Protocol 的工具注解无法弥补这种权限坍缩。MCP 工具规范把只读、破坏性行为等注解称为提示，并要求客户端在注解并非来自可信服务器时把它们视为不可信。通用调用器也无法为 `readOnlyHint` 设置一个始终真实的值，因为它的行为取决于尚未收到的参数。

团队经常混淆这里的两个概念：工具选择回答要运行哪段实现，操作授权回答允许产生哪种外部效果。如果宿主只批准实现名称，那么所有实质上不同的请求都会继承这次批准。界面整洁的工具选择器背后，可能藏着一条几乎不受限制的外连通道。

把每个 API 操作拆成独立工具，有助于描述和规划，但这不是完整的安全答案。生成出来的工具仍需要执行点，凭据仍带有自身权限，重定向也可能把看似狭窄的操作带往别处。具体工具用于改善可用性，最终请求才是执行权限的地方。

## 权限需要四个坐标

可防御的 HTTP 决策需要四个坐标：凭据、解析后的目标地址、方法和请求细节。缺少任何一个，请求都可能保留已经获批的外观，同时改变真实效果。

凭据说明正在行使哪种权限。部署令牌和账单令牌可能指向相同主机和路径，却开放完全无关的能力。目标地址说明这份权限可以提交到哪里。方法表达宽泛的 HTTP 意图。其余请求细节包括路径、查询参数、选定的请求头、内容类型和请求体，它们共同决定实际操作。

一条紧凑的授权记录可以写成这样：

```yaml
credential: issue-tracker-read
origin: https://api.example.test:443
path_prefix: /v2/issues/
methods:
  - GET
redirects: deny
headers:
  allow:
    - Accept
query:
  deny:
    - include_deleted
body: forbidden
```

这个片段能同时防止几种故障。智能体不能换用权限更大的凭据，不能把凭据发送到另一个源，不能把读取改成 POST，不能跟随重定向，不能添加类似授权信息的请求头，不能通过查询开关索取已删除记录，也不能向预期只读的操作里夹带请求体。

这个策略对象不是通用模板。有些 API 需要精确路径，有些需要路径中的租户标识，还有些把一项操作放进 POST 请求体。重点在决策的形状：完成解析和规范化后，把四个坐标一起评估，只有请求通过时才注入凭据。

不要让智能体通过 `headers` 提供真正的秘密。让它用不透明名称引用凭据，然后由可信执行器在授权后加入持有者令牌、基本认证信息或自定义请求头。否则，智能体可以把秘密复制到另一个字段、日志或第二个目标地址，使精心制定的目标规则沦为摆设。

## 权限由凭据决定，而不是便利性

凭据应当是彼此分开的授权，并且只拥有上游 API 能支持的最小权限。执行器从多个有名称、有范围的凭据中选择，而不是持有一个覆盖整个组织的令牌，通用 HTTP 工具的风险就会小很多。

OAuth 范围有帮助，但范围和受众回答的是不同问题。范围可能表示令牌可以读取联系人，受众则说明哪个资源服务器应当接受它。RFC 8707 定义了 OAuth 的 `resource` 参数，让客户端能为特定受保护资源请求令牌，并建议授权服务器限制已签发令牌的受众。它的安全理由很实际：提交给一个资源的令牌，不应当在另一个资源上也能使用。

当前 MCP 授权规范也采用同样的分离方式。它要求 MCP 服务器只接受为自身签发的令牌，并禁止把入站 MCP 令牌原样传给上游 API。当 MCP 服务器调用另一个 API 时，它要作为独立 OAuth 客户端，使用单独的上游令牌。这个边界很清楚，即使智能体看不到 OAuth 流程，通用 HTTP 执行器也应保留它。

API 密钥原生提供的控制通常更弱。即便如此，也要把每一把密钥视为独立权限。记录哪些源可以接收它、执行器要用哪种请求头格式注入、何时过期，以及由谁负责轮换。不要只因为保险库条目的名字叫 `staging`，就认定它无害。必须核实它在上游真正拥有的权限。

凭据选择也应该出现在批准界面里。只显示 `POST api.example.test` 的提示，没有说明调用使用的是沙箱密钥还是账户所有者令牌。界面应显示便于理解的凭据标签及其对应的账户或租户，但绝不能显示秘密内容。如果执行器无法确定这些上下文，这项授权就太模糊，不应静默复用。

把秘密留在模型之外可以减少意外泄露，但仅仅保密并不能限制使用。如果宽泛的执行器愿意把秘密附加到任意请求上，智能体无需看到秘密字节也能误用它。不披露和最小权限解决的是两个不同问题，两者都需要。

## 目标地址指解析后的端点

只检查一次 URL 字符串不算目标控制。执行器必须解析 URL、进行规范化、解析主机、执行网络规则，并在每次重定向时重新作出判断，然后才能附加凭据。

先确定准确的协议、主机和有效端口。`https://api.example.test` 与 `https://api.example.test:8443` 是不同的源。拒绝 URL 中的用户信息、含糊编码、不支持的协议，以及只是看起来带有获批后缀的主机名。若后缀检查会接受 `api.example.test.attacker.invalid`，那就不是允许列表。

接着解析 DNS 并检查返回的每一个地址。看起来公开的主机名可能解析到回环地址、本地链路、私有网段或云实例元数据。解析结果还可能在校验和连接之间发生变化。负责校验的组件应当控制连接并核实实际使用的地址，而不是把 URL 交给另一个再次解析的客户端。

OWASP 的服务器端请求伪造防护速查表建议，在应用能够识别目标时，把已知可信目标加入允许列表。它还建议禁用自动跟随重定向，因为重定向可以绕过输入校验。这个建议尤其适合智能体工具：URL 往往由模型提供，请求还可能携带只允许第一个目标接收的凭据。

重定向需要一次新的授权判断。RFC 9110 指出，对不安全方法自动重定向时必须谨慎，并建议跟随重定向时移除 Authorization 和 Cookie 等资源特定字段。安全执行器可以更简单：默认拒绝已认证调用的重定向，或者把新目标作为新操作展示出来，只有再次通过策略后才重新注入凭据。

即使源已经获批，路径控制仍然重要。多租户网关、共享 SaaS 主机和管理路由可能位于同一个主机名之后。RFC 8707 指出，在多租户系统里，标识租户的路径可能需要纳入资源标识。只有源允许列表而没有路径或租户约束，其范围可能远超审阅者的预期。

## HTTP 方法只是信号，不是裁决

限制方法能消除大量错误，但方法名称无法证明请求无害。RFC 9110 把 GET、HEAD、OPTIONS 和 TRACE 定义为安全方法，因为它们规定的语义基本上只读。它把 PUT、DELETE 以及安全方法定义为幂等方法，也就是重复执行预期操作，与只执行一次具有相同的预期效果。

安全和幂等不是同义词。DELETE 可以是幂等的，同时仍会销毁资源。POST 通常既不安全也不幂等，但某个 API 可能因为查询内容太长而用 POST 执行只读搜索。把风险简化为 GET 对 POST 的批准系统，会把这两种情况都分错。

更糟的是，现实中的 API 有时会违反方法语义。RFC 9110 明确警告，有些资源把删除等操作放在 GET 查询参数中，并要求资源所有者禁止通过安全方法执行不安全行为。执行器不能假设所有上游都遵守这条规则。如果 `GET /jobs?id=7&action=cancel` 会改变状态，允许所有 GET 并没有形成只读授权。

把方法作为一个输入，并结合路径，必要时再加上特定操作的请求约束。对于描述完善的 API，OpenAPI 操作可以提供有用的映射：OpenAPI 规范允许操作声明安全要求，OAuth 条目则列出所需范围。可以把这些信息导入配置，但随后要对照上游实际签发和接受的内容进行核实。描述文件本身不是执行点。

重试行为也属于这项决策。POST 之后发生网络超时，并不能告诉调用方服务器是否已经应用操作。除非 API 提供幂等机制，或调用方能确定第一个请求没有生效，否则不要自动重试不安全、非幂等的请求。幂等可以降低重试的运行风险，但不能让原始操作自动获得授权。

## 请求细节决定真实效果

即使凭据、源、路径和方法都相同，两个请求仍可能产生相反结果。请求体、查询参数和部分请求头往往包含真正重要的操作。

某个账单端点可能同时用 `POST /v1/subscriptions/update` 来减少席位，也用它把账户升级到昂贵方案。代码仓库端点可能在同一个变更路由中通过 `operation` 字段表示归档、转移或删除。搜索端点可能在出现 `include_deleted=true` 时返回隐藏或已删除记录。批准路由但不约束这些字段，就等于批准该路由实现的全部模式。

请求头同样需要警惕。执行器应当控制 `Authorization`、`Proxy-Authorization`、`Host` 以及所有自定义凭据请求头，通常还应拒绝智能体设置这些字段。用于选择账户、模拟用户、覆盖方法、请求异步执行或携带条件写入控制的请求头，都可能改变权限或效果。转发任意请求头，相当于在第一个通用工具里又藏了一个通用工具。

内容类型决定如何解析。如果策略检查 JSON，但客户端可以发送表单数据、多部分内容或压缩字节，智能体就能把敏感字段移到解析器之外。执行声明的类型，在缓冲前设置大小限制，拒绝重复或含糊的字段，并对执行器最终发送的同一份字节表示进行授权。先验证一个对象，再把另一个对象重新序列化，会留下缺口。

响应处理也属于边界，尽管它不是决定外发效果的权限坐标。限制响应大小，对内容类型分类，并把返回的指令视为不可信数据。获准的 GET 也可能取回包含提示注入、秘密或超大负载的页面。外连授权不会让响应自动变得适合送回智能体。

维护精确的请求体模式成本很高，因此应把精力用在权限集中的路由上。对于低风险读取，禁止请求体并限制查询参数名称或许就够了。对于账户变更、部署、秘密轮换、资金移动或破坏性操作，应校验标识对象、租户、金额、环境和请求状态转换的字段。如果 API 提供范围更窄的端点或凭据，应优先使用它，而不是编写复杂的本地规则。

## 批准界面应描述解析后的操作

有用的批准提示要展示规范化之后可信执行器即将发送的内容，而不是模型最初提出的工具调用。审阅者需要看到凭据标签、账户或租户、解析后的目标、方法、路径、有意义的查询字段、影响重大的请求体字段以及重定向行为。

这并不意味着要把原始 JSON 整块塞进对话框。原始负载会把危险字段埋在时间戳和默认值中。先呈现操作摘要，再允许检查规范请求。例如部署批准可以先说明 `production-deployer` 凭据将在生产租户中创建版本 `2026.07.24`，随后展示确切主机、POST 路径和请求体差异。

把批准绑定到规范操作的摘要。如果点击之后主机、方法、路径、受保护请求头或请求体发生变化，就计算出不同摘要并要求重新决定。这样可以堵住常见的检查时机缺口：界面批准一个请求对象，之后中间件又跟随重定向、添加默认值或修改请求体。

要有意识地选择复用边界。对于使用单一窄权限凭据和目标的重复读取，会话批准可能合理。能删除资源的凭据应要求逐次批准，或者只预先批准范围小得多的操作。不要用反复弹窗代替权限收窄。每张卡片看起来都一样时，人很快就会一路点过。

缺少必要上下文时，批准结果应当默认拒绝。未解析的主机名、未知内容类型、无法识别的方法覆盖，或策略无法解析的请求体，都不是低风险请求。系统根本无法准确描述这些操作。

## 看似无害的计划仍可能变成危险调用

故障通常从普通任务开始，在请求穿过多个层次时改变含义。假设智能体需要读取一个问题单并发布简短状态说明。两个操作都使用同一个项目服务，于是宿主为整个会话批准了通用调用器。

读取凭据无法执行 POST，规划器便选择另一个描述里提到项目自动化的保险库条目。这个令牌同时还能管理 Webhook。检索到的问题单评论要求智能体通知外部回调地址，规划器于是把该回调 URL 作为状态目标提交。通用工具仍有会话批准，凭据名称仍听起来相关，方法也仍是 POST。工具级权限看不到任何越界。

初始回调返回 307 重定向，指向私有地址。方便的 HTTP 库会在这个状态下保留 POST 请求体。它可能移除自动生成的 Authorization 请求头，但由调用代码提供的自定义凭据请求头仍可能保留，除非客户端明确处理。请求现在带着项目权限和问题单内容，正前往一个无人审阅的目标。即使私有服务拒绝凭据，请求体也可能泄露数据或触发无需认证的操作。

四项协调检查会在不同位置阻止这个过程。读取凭据不能授权 POST，自动化凭据不能到达未登记的回调主机，重定向必须重新判断目标，请求规则会拒绝包含任意回调目标的请求体。没有任何单项检查能承担全部防御，工具和凭据的友好名称则完全不提供防御。

因此，我反对每个会话只批准一次通用调用器。这种模式受欢迎，是因为反复批准会打断工作，而稳定的工具名称看起来像是在描述稳定的风险。事实并非如此。应把会话授权限制到凭据和目标，并约束具体操作，任何坐标变化时都要重新请求批准。

即使没有恶意评论，同一过程也可能失败。智能体可能根据过期文档猜测端点，从错误响应中复制 URL，或在预期凭据返回 403 后选择名称相近的凭据。这些都是规划器常见的恢复行为。安全设计应当预期这些行为，并把恢复尝试限制在原始授权内。

比较之前必须先规范化。按照一套明确规则解码百分号编码的路径段，拒绝逃出允许前缀的点段，规范有效端口，并确定 API 如何处理重复查询名称。如果策略把 `/v2/issues/%2e%2e/admin` 当成问题单路径，而服务器把它解析成 `/v2/admin`，策略和服务器授权的就不是同一资源。面对含糊形式应直接拒绝，不要猜测每个中间组件会怎样解释。

凭据注入必须在上述工作之后进行，并尽量靠近发送时刻。构造规范请求，完成授权，绑定批准摘要，打开已批准连接，然后由可信执行器加入秘密。如果中间件可在注入后重写主机、方法或请求体，就要把中间件输出纳入授权步骤，或者取消它的重写自由。

失败也必须走封闭路径。如果选中的凭据收到 401 或 403，就把结果返回智能体，不要自动尝试保险库中的每个条目。凭据回退会把一次窄范围失败变成权限探测。下一次尝试必须明确指定另一个凭据，并根据它自身的权限和账户上下文接受新的判断。

上游错误消息同样只是数据。API 经常回显 URL、操作名称或建议的重试动作。规划器用这些文本形成下一次调用很合理，但文本不会因为来自获批主机就自动获得权限。下一次请求仍要接受同样的目标和请求检查。

要按序列测试这种行为，而不能只测孤立请求。先执行一次允许的读取，再返回特制的重定向或错误建议，让规划器形成后续调用，并确认会话复用没有扩大权限。然后让预期凭据失败，验证执行器拒绝静默回退。这些测试能发现单次策略匹配的普通单元测试会漏掉的权限继承。

## 日志必须记录决策和调用

工具调用日志过于粗略，不足以用于事件调查。只记录智能体调用了 `http_request`，会漏掉最核心的问题：使用了哪项凭据，请求去了哪里，允许了什么操作，以及发出的请求是否与获批请求一致。

记录会话或进程身份、工具调用标识、凭据标识、规范目标、解析后的地址、方法、脱敏请求摘要、策略版本、批准者身份、操作摘要、响应状态、耗时和最终结果。普通日志不应保存秘密字节和敏感响应体。必须作为证据保留的字段，可以保存摘要，或使用单独的访问控制进行加密。

拒绝和成功都要记录。连续尝试私有地址或替代主机却被拒绝，可能在任何外部效果发生之前暴露提示注入或错误规划器。区分策略拒绝、用户拒绝、网络故障、上游拒绝和本地取消，避免响应人员把连接失败误认为操作已被阻止。

证据应当难以被执行操作的智能体改写。仅追加存储、受限写入者和完整性检查，能让工作站或进程受到质疑后，记录仍然有用。Sallyport 从一份写入方无法读取的加密哈希链审计日志中记录智能体会话和单次调用，`sp audit verify` 无需密钥即可离线验证密文链。它还把 API 和 SSH 凭据放在加密保险库中，由自身执行操作，不向智能体暴露秘密内容。

## 在执行器中替换宽泛授权

你可以保留通用 HTTP 界面，而不保留通用权限。把凭据注入和执行控制移到可信执行器，让模型只提交无凭据请求和一个不透明的凭据引用。

实际迁移应从盘点真实调用开始，而不是设想中的调用。按凭据、源、方法和操作整理近期请求。从未一起出现的宽泛凭据和目标，很适合拆分。一个请求体里承载多种破坏性模式的路由，需要自己的请求约束或上游凭据。

接下来让现有流量进入仅报告评估。规范每个请求，解析目标，并展示拟议授权会允许还是拒绝它，但暂时不改变执行。检查意外匹配。看起来只允许读取问题单的规则，也可能通过共享主机放行导出、已删除记录或另一个租户。

然后先执行最容易建立的边界：凭据归执行器所有、只允许精确 HTTPS 源、不自动重定向、明确方法，并默认拒绝私有地址，除非某项特定集成确实需要。围绕高影响操作添加路径、查询、请求头和请求体约束。保留一个需要明确逐次批准并生成醒目审计记录的例外通道，不要静默退回旧的无限制工具。

最后，用已知正常请求的变体测试边界。改变主机后缀、端口、DNS 答案、重定向目标、凭据引用、方法、内容类型、租户字段、操作字段和编码路径。每个变体都应匹配有意设置的授权，或者在附加凭据前失败。还要验证日志、批准界面和实际发送的字节使用同一个操作摘要。

工具列表仍有助于智能体选择合理操作，但它不该是授权的终点。如果开发者喜欢，可以保留便利的调用界面，但每项网络效果都必须在凭据、目标、方法和请求细节全部已知时取得权限。
