# 生产上线前如何测试代理 API 访问

代理能否获得生产 API 访问权限，取决于它在请求被拒绝、格式错误、响应缓慢或具有危险性时能否正确行动，而不是它能否在沙盒里返回一次成功响应。一次友好的演示会掩盖真正重要的失败：审批针对了错误的进程，令牌被复制进代理记录，重试循环不断撞击速率限制，或者日志无法说明是谁批准了破坏性调用。

在把生产能力交给代理之前，先让它针对非生产目标走完整的操作路径。这条路径包括代理发起请求、授权、凭据注入、远程 API 响应、代理对失败的理解，以及一份能在会话结束后保留的调用记录。只要缺少其中一环，你测试的就是 API 客户端，而不是自主行动者。

## 非生产端点必须在关键隔离上真正分开

一个有用的测试端点应有独立的凭据、独立的数据，以及一句话就能解释清楚的损害边界。如果只是给生产主机名加一个据称能选择测试模式的查询参数，却仍使用同一个令牌读取客户记录或花钱，就达不到要求。

如果供应商提供了隔离账户和测试凭据，请使用供应商沙盒。如果服务没有沙盒，就使用专用租户。如果两者都没有，可以在自己控制的不同主机名后面部署一个小型服务，并使用可丢弃的数据。重点不在于它是否被标成 staging，而在于一次误操作不能影响生产用户、生产余额或生产密钥。

让端点证明自己属于非生产环境。每个成功响应都返回明显的环境字段，并让破坏性路由只写入测试账本。类似下面这样的响应，可以防止操作员把绿色测试误认为生产变更：

```json
{
  "environment": "test",
  "request_id": "req_7f1a",
  "status": "accepted",
  "resource_id": "demo-order-184"
}
```

测试数据要足够真实，能够覆盖分页、字段缺失、权限和冲突。只有一条完美记录，会教会代理形成坏习惯。准备几条它可以读取的记录、一条它可以更新的记录，以及一条它绝不能访问的记录。不需要准备庞大的测试数据集，但要有足够变化，才能抓住那些假设每个响应都干净完整的代码。

不要为了方便而在测试环境复用生产 bearer 令牌。我见过团队把这称为临时捷径，后来因为每项后续任务看起来都更紧急，就一直没有撤掉。测试身份应有明确名称、负责人、过期日期，以及与具体测试用例匹配的权限。如果没人说得清某项权限对应哪个测试，就删掉它。

## 首次运行应测试授权边界，而不是 API 本身

在验证业务行为之前，先证明一个不受识别的代理进程无法悄悄行动。启动一个全新的代理进程，让它针对测试端点尝试一次无害的读取。预期结果是在 API 请求发出之前，先出现授权事件。

这能抓住一个经常被团队混淆的区别：用户审批不等于进程审批。操作员可能信任自己的终端，却不信任插件、复制来的脚本，或由其他应用启动的代理。审批界面应告诉操作员，是哪个可执行程序获得了行动授权。一个只写着「允许」的按钮，却没有这些上下文，相当于让人批准一个身份不明的进程。

在这项测试中，记录四个观察结果：

- 新进程会在外部调用之前收到审批请求。
- 审批会以操作员能够识别的方式标明进程。
- 审批只持续预期的这次运行，不会延续到所有未来进程。
- 结束进程后，会话授权随之失效。

先拒绝一次请求，再批准它。拒绝必须让代理获得可用的失败信号，而不是自行编造成功。好的代理指令应明确拒绝后的处理方式：停止操作，报告审批被拒绝，并且不要寻找同一端点的第二条路径。

然后重启代理，再次执行这次无害读取。如果第二个进程继承了第一个进程的权限，就查清原因。缓存授权在演示中看似高效，但当代理更新后重启，或另一个启动器调用同一命令时，它就会变成一个悄无声息的权限授予。

Sallyport 默认开启会话审批，并会在新的代理进程首次调用时显示该进程的代码签名权限。这正是测试人工判断的时机，应在带凭据的请求到达外部服务之前完成。

## 凭据注入必须证明代理从未持有密钥

只有当代理能够请求执行操作，却无法取回用于该操作的凭据时，凭据注入才算通过。把令牌遮挡在控制台输出中不算保护。只要令牌进入过环境变量、工具响应、提示词、Shell 历史记录或本地文件，代理就曾经能够访问它，即使碰巧没人把它打印出来。

设置一个测试凭据，让远程服务能够识别它，但不暴露其值。许多 API 提供令牌标签、客户端标识符或审计字段。如果你的 API 没有这些功能，就创建一条测试路由，返回它看到的凭据身份，而不是凭据本身。结果应能证明是哪一个测试身份完成了认证调用。

对于 bearer 令牌 API，线上的请求通常是这样的：

```http
GET /v1/test/projects/demo HTTP/1.1
Host: api.test.example
Authorization: Bearer [injected outside the agent]
Accept: application/json
```

方括号中的文字只是说明，不是让代理填写的值。代理应提供方法、目标地址和获准的请求参数。只有在审批决定作出后，凭据处理器才添加授权请求头。Basic authentication 和自定义请求头方案也要进行同样的测试，因为配置错误时，它们会在不同位置失败。

检查代理记录、工具请求历史、代理能够看到的 Shell 环境，以及运行期间创建的所有文件。既要查找完整的令牌材料，也要查找间接泄露，例如带有授权请求头的请求对象。事后脱敏无法修复一个曾经把密钥交给进程的设计。

然后轮换测试凭据，再执行同一个请求。第二次运行成功，说明操作路径读取的是当前存储的凭据，而不是嵌在代理配置中的旧值。即使运行失败，也可能有价值，只要错误说明了认证失败，却没有打印被拒绝的密钥。

测试时不要使用权限超出场景要求的令牌。只读凭据已经足以证明注入机制正常。之后再为修改测试添加范围很窄、可以逆转的写入权限。审核测试的人不应需要接触原始令牌，才能判断测试是否通过。

## 审批阻力应与调用造成的损害相匹配

会话审批和逐次使用审批解决的是不同问题。会话审批确认某次特定的代理运行可以使用范围受限的能力。逐次使用审批则要求人对敏感凭据发起的每个请求进行审核。把两者当成替代关系，最终要么得到无用的提示风暴，要么留下无人看管、可能造成昂贵错误的路径。

用低风险凭据测试会话边界。对能够创建、删除、转移、发布或修改访问权限的凭据，启用逐次使用审批。让代理使用该凭据发起两次不同的测试调用。你应看到两次决定，第二次请求不能沿用第一次获得的审批。

测试必须包含拒绝场景。批准第一个测试操作，拒绝第二个。确认代理报告和操作记录中都有以下事实：

1. 第一个操作到达测试服务，并返回了请求标识符。
2. 被拒绝的操作没有到达测试服务。
3. 代理没有声称自己完成了更改。
4. 会话仍可用于不需要被拒绝凭据的操作。

第四点可以抓住一种棘手的失败模式。有些集成会把一次敏感请求被拒绝，视为终止所有后续操作的理由。另一些则忽略拒绝并持续重试，直到某人意外批准。两种行为都会让人工控制变得比应有的更困难。

审批疲劳是设计失败，但取消审批通常不是解决办法。可以通过将工作分组到短时会话中、减少敏感调用的数量，或给代理提供更安全的批量操作来降低疲劳。不要因为提示太多，就给一个任务执行到一半可能改变计划的进程授予范围很广的永久令牌。

## HTTP 状态码应驱动代理行为

代理需要针对不同失败类别制定明确行为，因为 HTTP 成功和任务成功不是一回事。RFC 9110 定义了 HTTP 响应状态码的语义，其中 401 表示缺少或无效的认证凭据，403 表示服务器理解请求，但拒绝履行。要区别处理这两种响应。通常，带着同一个请求重试它们只会增加噪音，不会带来进展。

在上线前建立失败处理表，并针对测试端点逐行执行。要求动作足够明确，让审核人员能够判断代理是否遵守了规则。

| 测试响应 | 代理动作 | 记录应显示的内容 |
| --- | --- | --- |
| 401 认证失败 | 停止并报告凭据问题 | 目标地址、状态、凭据引用，不含密钥 |
| 403 授权失败 | 停止并报告权限不足 | 目标地址、方法、状态、尝试执行的操作 |
| 404 资源不存在 | 询问资源标识符是否有误 | 提供的标识符和状态 |
| 409 冲突 | 先读取当前状态，再提出其他写入方案 | 资源标识符、状态，不得盲目重试 |
| 429 速率限制 | 按服务器提示等待，或停止 | 状态，以及服务器提供的重试时间 |
| 500 或 503 | 只在规定限制内重试，然后报告结果 | 尝试次数、状态、最终结果 |

400 响应值得比通常更多的关注。它往往暴露出代理工具架构与远程 API 实际契约之间的不匹配。让测试服务器返回字段级校验错误，然后检查代理是否会报告错误参数，而不是自行编造替代值。会猜测字段的代理，可能把一次无害的校验错误变成针对错误账户的请求。

将传输失败与 HTTP 503 分开测试。断开测试服务，或让受控测试请求指向无法访问的地址。代理应区分没有收到响应和服务器返回了响应。当操作可能已经到达服务、只是响应丢失时，这一区别非常重要。对创建操作的模糊超时进行重试，可能会产生重复对象。

在 API 支持的情况下使用幂等标识符。如果 API 不支持，就让代理在重复执行可能修改数据的调用前，先读取是否已有结果。「重试三次」不是恢复方案，因为每次重试都可能扣款、创建用户或发送消息。

## 一次失败的上线，往往始于看似无害的重试循环

一种常见的失败场景是：代理被要求创建测试资源，然后验证它。创建请求在服务端成功了，但网络中断导致响应没有返回。代理看到错误，重试创建调用，并收到第二个成功结果。接着它按猜测的名称获取一个资源，然后报告成功。此时操作员已经产生了重复变更，却无法清楚判断哪个请求造成了哪个结果。

你可以在不影响生产的情况下重现这一场景。让测试路由接受创建请求并保存测试对象，然后在返回响应前故意关闭连接。使用固定的请求标识符运行代理。安全的预期行为是：代理在重复创建前，先向测试服务查询该标识符。如果代理做不到，就应停止并报告结果不明确。

一个最小化的测试服务契约可以让检查变得具体：

```json
POST /v1/test/jobs
{
  "request_id": "rollout-042",
  "name": "reconcile-demo"
}

GET /v1/test/jobs?request_id=rollout-042
{
  "items": [
    {"id": "job_128", "request_id": "rollout-042", "state": "queued"}
  ]
}
```

这里也能发现那些要求代理「一直尝试直到成功」的提示。对观察人工操作的人来说，这句话听起来合理，但对于能够以人们察觉不到的速度发起调用的行动者来说，它并不安全。应改成有上限的重试规则、重复检查，以及需要人工审核的条件。

OWASP 的 API Security Top 10 提到了不受限制的资源消耗和对象级授权缺失。在代理上线中，这两项往往表现为普通的行为错误：忽略限制的循环，以及请求的对象失败后，代理换用了一个相近的对象标识符。测试必须包含一个禁止访问的对象和一条受速率限制的路由，因为只有成功路径的测试数据永远暴露不了这两种习惯。

## 记录必须说明决定和外部影响

调用记录应让人能够重建发生了什么，而不必重建代理的全部私有推理过程。保存能够证明授权和影响的事实：哪个会话执行了操作、哪个进程发起了请求、使用了什么目标地址和方法、应用了哪个凭据引用、是否有人批准、何时发生，以及返回了什么结果。

不要不加判断地把原始请求体放进每条记录。有些负载包含客户数据、第三方令牌，或代理被指示处理的内容。如果安全摘要或选定字段已经满足调查需要，就保存这些内容。远程服务的请求标识符尤其有用，因为它能把本地记录与服务自己的审计轨迹连接起来。

将运行记录与调用记录分开。运行记录回答某个代理进程是否获准运行，以及之后是否有人撤销了权限。调用记录回答每次外部操作发生了什么。把两者都塞进聊天记录，会在一次运行发出多次请求时丢失所需结构。

Sallyport 会将会话日志和活动日志投射到一份加密、哈希链式的审计日志中。它的 `sp audit verify` 命令无需保险库密钥，即可离线检查密文上的链，因此可以将记录完整性测试与密钥访问分开进行。

正常测试后运行验证，然后将加密审计文件复制到测试位置，并修改副本中的字节。命令应报告副本已失败，而原文件仍能通过验证。只对可丢弃的副本执行此操作。这项练习能让审核团队在生产调查真正需要时，提前了解有效报告的样子。

防篡改证据不代表每个操作员都能读取所有细节，也不能替代远程 API 日志。它回答的是一个更窄的问题：本地记录的顺序是否保持完整？保留测试端点的请求 ID 和时间戳，让调查人员能够比较两边的记录。

## 撤销必须阻止下一次调用，而不只是关闭一个窗口

在代理仍然运行时测试撤销。批准一个会话，执行一次无害请求，撤销会话，然后要求同一进程再执行一次无害请求。第二个请求必须在到达非生产端点之前失败。如果它因为现有连接或缓存凭据仍然有效而成功，这就是生产上线阻断项。

然后单独测试保险库闸门。锁定凭据存储，再次尝试请求。保险库锁定后应拒绝所有操作，包括操作员此前已为该会话批准的请求。这比撤销单次运行更强，因为它会停止所有依赖保险库的操作路径。

在两项测试期间观察端点。不要把代理侧显示「访问被拒绝」当成证据。服务器端请求日志必须显示没有收到第二个请求。这个简单的交叉检查可以抓住这样的集成问题：集成在已经发出 HTTP 请求之后，才报告审批失败。

如果代理除了 HTTP 调用还可以执行 SSH 操作，就针对一台可丢弃的主机重复测试。使用非特权账户和结果明确的命令，例如在临时目录中创建文件。撤销必须像阻止 API 请求一样阻止新的 SSH 命令。把两种通道区别对待的网关，会制造一个盲区，让操作员误以为控制仍然存在。

## 上线需要证据包，而不是演示带来的自信

只有当审核人员能够检查一份简洁的测试记录，并判断代理是否始终处于预期授权范围内时，才进入生产环境。证据包应包含测试身份和权限、端点边界、预期审批行为、有代表性的成功与失败结果、记录验证结果，以及实际观察到的撤销结果。

不要把每一项测试权限都带进生产。单独创建生产凭据，并从支持第一个真实任务的最小操作集合开始。指定一名能够批准或撤销运行的操作员，并写清楚哪些响应状态要求代理停止。如果团队说不出这个人的名字，就等于把运行控制交给了运气。

首次生产任务应在审批开启的情况下运行，并在完成后立即检查记录。将实际请求数量、目标地址和结果与测试运行进行比较。如果代理联系了未计划的端点、要求更宽泛的凭据，或在生产数据下采用了不同的重试方式，就停止上线，把这种行为带回非生产端点重新测试。

正确的首次生产上线应当刻意保持平淡。代理只发出少量预期调用，有人能够停止它，凭据从未进入代理上下文，每个外部影响都有一条与服务自身请求 ID 相匹配的记录。这足以支持谨慎扩大范围。精心包装的演示并不能证明这一点。
