# 临时生产访问：随任务结束的授权

AI 编程智能体的生产访问权限，应当因为授权系统规定它到期而结束，而不是因为有人打算稍后回来处理。明确目的、截止时间和关闭检查，可以把一次高风险例外变成有边界的授权，让其他工程师也能检查。

我见过访问控制以一种乏味的方式失效：事件解决了，任务关闭了，但凭据仍然有效。当天不会发生什么戏剧性的事情。几个月后，同一个凭据出现在无关脚本里，旧的智能体进程重新运行，或者有人因为方便而继续使用它。原本的例外因为疏忽变成了永久生产访问权限。

临时生产访问至少需要三个事实，而且执行点必须能够检查它们：智能体为什么可以操作，它可以访问什么，以及什么时候必须停止。最后一点和前两点同样重要。如果你无法证明任务关闭后请求会被拒绝，就还没有完成任务。

## 截止时间必须拒绝请求，不能只是装饰工单

只有执行或授权操作的组件会在每次请求时检查截止时间，声明的结束时间才真正有效。项目跟踪器、日历条目或聊天提醒，都无法阻止一个旧进程在凌晨 2 点调用 API。

这个区别常常让团队措手不及，因为他们的变更流程看起来很规范：需要工单、审批者和关闭状态。然后，他们把一个在有人手动轮换前始终有效的凭据交给智能体。工作流记录了意图，但凭据仍然保留权限。

把过期时间放在请求进入生产环境的地方。根据设计，它可能是签发短期令牌的身份提供商、检查授权数据库的访问网关、SSH 证书验证器，或持有凭据并执行操作的代理。受保护服务必须收到一个在截止时间后能够拒绝的请求。

NIST SP 800-207 在介绍零信任架构时提出了一个有用观点：在建立通往企业资源的会话之前，应评估访问决定，而且访问应按会话授予。这并不意味着每个应用都必须照搬完整的零信任产品，但意味着只授予一次会话，然后假定它永远合适，与这一模型相矛盾。

对于智能体，至少要在每项操作开始时进行评估。长时间运行的工作还需要单独决定：允许已经开始的操作在有边界的租约内完成，或者在租约到期时取消它。应在事故发生前决定行为，因为数据库修复过程中意外中断，可能和不受限制的访问一样有害。

截止时间应使用统一时间基准的时间戳，通常是 UTC。不要写“当天结束前”，然后希望每个系统对“当天”的理解都一样。存储 `2025-04-18T16:30:00Z`，向审批人员显示本地时间，并让请求路径根据存储的时间戳进行比较。

## 目的必须限制授权范围

只有在目的能够限制智能体的操作时，目的才有用。“帮助处理生产问题”和“调查告警”描述的是一种状态，不是授权边界。

目的应该写到即使没有批准授权的工程师，也能判断某个请求是否属于范围。一个可用的声明应包含触发任务、目标、允许的操作以及预期完成条件。例如：

> 调查事件 INC-482 中结账失败率升高的问题。读取 checkout 服务的日志和部署状态。只有在事件指挥官批准重启时，才能重启 checkout worker。INC-482 解决后，或到 16:30 UTC 时，以较早者为准结束访问。

这段声明可以直接指导判断。读取账单数据库不属于范围。推送无关的应用变更也不属于范围。重启 worker 需要满足明确的审批条件。智能体仍然可以发挥作用，但不会得到一张空白支票。

范围不能只写一个“生产环境”标签。应将授权绑定到具体目标：

- 指定的 API 主机、代码库或 SSH 主机
- 指定的方法或命令，例如 `GET /health` 或部署状态查询
- 指定的环境和账户标识符
- 如果任务可能需要重复请求，设置最大操作次数或请求速率
- 能够停止工作的负责人

不要轻易接受“只读是安全的”，因此给智能体广泛只读权限的建议。只读访问经常会暴露客户记录、配置、拓扑、部署历史，或旧日志中嵌入的凭据。它也会让智能体收集远超任务所需的上下文。应只提供任务需要的具体可观测性端点和日志查询范围。

团队还经常混淆另一个边界：任务目的和提示词不是一回事。提示词告诉智能体你要求它尝试什么。授权目的告诉执行点，即使提示词发生变化、模型做出错误推断，或工具输出包含恶意指令，它仍然可以允许什么。应把提示词视为访问决定中的不可信输入。

## 凭据和权限的过期方式不同

凭据证明调用者持有某种东西。授权决定调用者现在是否可以执行这项请求。工作结束时，这两部分都必须结束。

如果接收 API 在每次请求时验证签名、受众和过期时间，带有 `exp` 声明的 bearer 令牌可以成为很好的时间限制。但它不是通用解决方案。有些服务会缓存身份验证，接受由其他系统验证的不透明令牌，或根据仍然有效的服务器端状态授权令牌。如果任务四分钟后就结束，却签发了一个 15 分钟后才过期的令牌，那么这个令牌同样没有满足任务边界。

SSH 也有同样的问题。OpenSSH 支持带有效期的签名用户证书，其 `ssh-keygen` 手册将 `-V` 记录为有效期选项。短期证书远好于把长期私钥放进智能体工作区，但它只回答了时间问题。你仍然需要限制主体、目标主机和命令行为。

下面的命令展示了一个有效期为 20 分钟的证书：

```sh
ssh-keygen -s ./user_ca -I agent-run-7f3a \\
  -n deploy-readonly -V +20m ./agent-run-7f3a.pub
```

输出通常会列出签名后的公钥文件，例如：

```text
Signed user key ./agent-run-7f3a-cert.pub: id "agent-run-7f3a" serial 0 for deploy-readonly valid from 2025-04-18T16:00:00 to 2025-04-18T16:21:00
```

不要把这张证书误认为完整的任务授权。如果远程账户可以运行任意命令，那么证书在 16:21 前仍然授权任意命令。根据工作需要，使用受限账户、强制命令或主机侧命令允许列表。

撤销和过期也不一样。过期是可预测的，即使中央撤销服务不可用也能生效。撤销则在事件解决、智能体行为异常或审批者收回同意时提前结束权限。好的设计应同时支持两者：较短的最长有效期，加上立即撤销能力。

## 任务关闭需要机器可读的事件

关闭任务必须产生访问组件能够消费的事件。人工修改工单状态，却没有连接到撤销路径，只会制造完成的假象。

使用两个终止条件。第一个是创建授权时设置的硬截止时间。第二个是工作系统或负责人发出的提前关闭信号。实际结束时间取两者中较早的时间。工单重新打开时，不应悄悄恢复旧授权，而必须重新审批并设置新的结束时间。

这可以防止一种常见故障。智能体因某个事件获得访问权限。智能体报告问题已经修复，于是操作员关闭事件。但另一个智能体进程仍然持有经过身份验证的连接，继续收集诊断信息。团队后来重新打开事件，却没有发现旧进程从未停止。访问决定绑定的是工单中的叙述，而不是实际进程的生命周期。

除了任务标识符，还要把授权绑定到不可变的运行标识符。一个任务可能在数天内拥有多次运行。每次运行都需要自己的开始时间、结束时间和负责人。运行退出时撤销授权。任务关闭时，撤销与它关联的所有仍然活动的运行。

关闭事件需要幂等接收器。系统会重试 webhook，操作员也可能按两次按钮。撤销请求应接受重复投递，并保留第一次生效的时间戳，而不是把重复请求当成错误，最后被人忽略。

记录关闭来源。工单系统的状态变更、事件指挥官手动停止、进程退出和硬过期，在调查中代表不同含义。对访问而言结果相同，但审计记录应保留它结束的原因。

不要因为智能体在截止时间附近发出更多工具调用，就自动延长授权。这种模式会奖励忙碌的进程，让它获得更多权限。应要求人工做出新的决定；如果工作已经变化，还要更新目的。

## 授权记录可以避免模糊审批

结构化授权记录能让审批变成系统可以执行、工程师可以审查的内容。它也会在智能体接触生产环境前暴露尚未决定的事项。

下面的示例刻意保持简单。它是记录格式，不是策略语言。这些字段可以防止一次智能体运行继承模糊且可重复使用的权限。

```yaml
grant_id: grant_01JQ7P8V6R
agent_run_id: run_7f3a2c
purpose: "INC-482: inspect checkout worker failures"
owner: "on-call engineer"
created_at: "2025-04-18T16:00:00Z"
ends_at: "2025-04-18T16:30:00Z"
ends_on_task_close: "INC-482"
targets:
  - "https://ops.example.internal/checkout/status"
  - "ssh://checkout-worker-03.internal"
allowed_actions:
  - "GET /checkout/status"
  - "journalctl -u checkout-worker --since 20m"
closure_required: true
verification_action: "GET /checkout/status"
```

记录不会写“生产访问：是”。这个字段会隐藏真正重要的选择。它会写明谁负责决定、授权给哪次运行，以及你要用什么请求证明授权已经消失。

范围要足够清晰，让审批者能够拒绝。巨大的通配符路径列表会让人在压力下无法理解授权，只能机械批准。如果智能体需要二十个互不相关的目标，就把工作拆成多个授权，或者承认目的范围过宽。

不要把秘密放进这份记录。授权标识符和目标描述就足够了。执行器可以在检查授权后，再解析受保护的凭据。这样既能保留有用的审计数据，也不必保留可以重新创建生产访问权限的材料。

从运行智能体的人的角度看，记录应能跨重启保留，并且只能追加。如果智能体可以修改 `ends_at`、改变目标或删除关闭信号，你就把安全决定交给了原本要限制的进程。

要明确处理已经在执行中的操作。日志查询在过期后完成，风险可能不大。部署、迁移或重启可能需要取消规则、事务边界或人工接管流程。最糟糕的做法是让行为保持未定义，然后在恢复期间才发现问题。

## 用否定请求证明授权已经过期

要证明访问已经结束，应通过智能体使用过的同一条路径发送一个现在应该失败的请求。关闭的工单、管理控制台中已过期的徽章，以及数据库中已撤销的记录，都只是辅助证据，不是证明本身。

在任务关闭或授权过期两者中较早的时间之后，立即安排检查。使用仍然需要该授权、但没有破坏性的端点，例如服务状态请求。不要使用公共健康检查，因为无论访问是否结束，公共端点都会成功。

简单的测试流程如下：

```sh
# 授权有效时，这个调用成功。
agent-action --grant grant_01JQ7P8V6R \\
  GET https://ops.example.internal/checkout/status

# 任务关闭后，重复执行完全相同的受保护操作。
agent-action --grant grant_01JQ7P8V6R \\
  GET https://ops.example.internal/checkout/status
```

第二条命令应产生类似下面的输出：

```text
request_id=req_92a1
status=403
reason=grant_ended
ended_at=2025-04-18T16:12:09Z
```

你的实现可能返回 `401`、`403` 或连接被拒绝。选择一种含义并写清楚。审计条目必须说明访问层因为授权已结束而拒绝请求，而不是因为 DNS 失败或目标服务碰巧不可用。

测试人们容易忘记的路径。如果智能体既能使用 HTTPS，也能使用 SSH，就两者都验证。如果服务在 API 交换后签发 cookie，就验证 cookie 的有效期不会超过授权。如果 worker 在本地排队操作，就验证执行器是在发送排队操作时检查授权，而不只是接受任务时检查。

在依赖生产设计之前，先在非生产环境中运行定期过期测试。创建一个有效期很短的授权，执行一次允许的请求，等待过期，然后再次执行相同请求。接着提前撤销第二个活动授权，再重复请求。这两项检查会发现不同问题：错误的时钟处理和失效的提前撤销。

时钟同步值得特别关注。评估截止时间的组件应使用可信系统时钟，并记录它进行评估的时间。如果笔记本可以把时钟调回去，从而延长访问权限，那么截止时间就不可执行。中央代理可以降低风险，因为由一个持续运行的组件做出决定，但仍然要监控它的时间来源。

## 智能体进程需要自己的边界

任务可能跨越重试、终端和多轮模型调用，但智能体进程仍然是访问控制的实际单位。按运行授予权限，可以提供清晰的位置来请求审批、观察行为并立即撤销。

不要把对话和进程混为一谈。模型重启后，或工具启动子进程后，用户仍然可能告诉智能体“继续调查”。如果访问权限随着对话延续，却没有新的授权事件，操作员就无法判断哪个可执行程序持有权限。

记录启动运行的进程身份。在 macOS 上，代码签名权限比用户提供的进程名称更有助于审批者判断。名为 `agent` 的进程几乎没有信息价值。记录签名身份、可执行文件路径、父进程和启动时间，才能支持后续审查。

Sallyport 默认会为新的智能体进程使用会话审批，其审批卡片会首先显示进程的代码签名权限。这是合理的进程边界，但会话审批仍应放在带有结束时间的任务授权之内。

正常退出时终止或撤销进程权限，但绝不能只依赖退出。进程可能崩溃，机器可能休眠，父子进程关系也可能变得复杂。硬截止时间和任务关闭事件仍然是最后的保障。如果进程在任务关闭后继续存在，请求路径必须拒绝它，即使操作系统还没有清理它。

将人工操作员的终端访问和智能体访问分开。让智能体继承操作员已经认证的 shell 很方便，但这会让归因失去意义，也可能把操作员当天积累的全部权限交给智能体。应给智能体独立身份，并要求它通过自己的路径发起请求。

## 审批提示无法替代范围控制

人工点击批准可以发现明显错误的请求，但当审批出现过于频繁，或请求描述过于简略时，审批就会变得薄弱。只写“允许 API 调用”的提示，会让人猜测它可能带来的后果。

让审批者看到目的、目标、方法或命令、授权结束时间以及智能体身份。如果系统无法用这些信息解释请求，就还没有收集足够的信息来负责任地请求审批。

对于删除数据、轮换凭据、触发付款任务或部署未经审查的变更等不可逆操作，可以使用逐次调用审批。但它不应成为每次读取日志的常规控制。人们在事件期间会机械地批准重复提示。

如果调用带有逐次审批标志，Sallyport 会在每次使用时请求批准，但这一设置应作为结束时间的补充，而不是替代。人工可能在 16:29 批准一个有效请求，但访问路径仍必须在授权结束后拒绝任何请求。

保留一个不需要寻找原审批者的快速紧急停止机制。当智能体陷入循环、误读输出或开始接触错误目标时，值班工程师需要能够终止运行。记录谁执行了停止以及原因。没有问责机制的紧急控制，可能变成另一条未经审查的访问路径。

## 审计轨迹必须回答那些不舒服的问题

生产事件之后，人们会问访问何时开始、谁允许了访问、哪个进程使用了访问权限、哪些请求成功，以及它是否真的停止。只记录成功的日志无法回答最后一个问题。

也要记录被拒绝的调用，尤其是过期或撤销后的拒绝。拒绝证明执行点收到了请求，并应用了边界。它还可以暴露任务关闭后仍不断重试的卡住智能体，即使控制措施已经生效，这种情况也值得处理。

通过标识符将授权决定和各项操作关联起来。审查者应该能够从 `grant_01JQ7P8V6R` 找到审批负责人、关联任务、运行身份、允许范围、关闭来源以及每个请求。如果这些记录分散在没有共享标识符的系统中，重建经过就只能靠猜测。

因为访问记录在有人有动机修改它们之后，往往会变得重要，所以防篡改能力很关键。Sallyport 通过加密、哈希链式审计日志生成会话和活动日志，`sp audit verify` 可以在离线状态下对密文验证这条链。这种能力无法判断请求是否合适，但会让悄悄改写记录顺序变得更加困难。

不要收集所有提示词和模型思考，把它们当成操作日志的替代品。这些记录会带来隐私和保留期限问题，而且仍然可能无法确定实际网络请求。先记录授权决定和操作结果。只有在运营和隐私规则允许时，再添加提示词上下文。

为审查者提供固定格式的关闭记录。它应说明授权标识符、实际结束时间戳、终止来源、成功操作数、结束后被拒绝的操作数，以及验证请求的结果。这样可以把“访问已移除”这种模糊说法，变成变更审查者可以质疑的记录。

## 按照小而可测试的流程构建控制措施

你不需要通用策略引擎来执行临时访问。你需要一个所有智能体操作都必须经过的狭窄机制，以及一条可以测试的拒绝路径。

先列出智能体目前在生产环境中执行的操作。将读取操作、可逆写入，以及可能造成不可逆或大范围影响的操作分开。针对每项操作，找出实际持有凭据的主体和执行点。这个过程经常会发现凭据存在于环境变量、 shell 配置文件、CI 日志或复制的配置文件中。

然后构建最小生命周期：

1. 创建独立的运行身份和结构化授权，写明目的及硬截止时间。
2. 让每个受保护的 HTTP 或 SSH 操作都通过执行器，由执行器在使用前检查授权。
3. 接收提前关闭或撤销事件，并记录其生效时间戳。
4. 终止后发送一个无害的受保护请求，并保留拒绝结果。
5. 定期审查已过期和已撤销的授权，检查访问结束后是否仍有请求尝试。

不要一开始就制定能够分类所有可能命令的复杂规则。团队花数周争论语法时，智能体可能仍然持有长期凭据。先实现指定目标、短有效期、按运行身份授权，以及拒绝证明。等操作历史显示确实有人请求更宽范围时，再增加更细的限制。

标准很简单，却要求很高：任务关闭后，旧的智能体进程必须在生产边界处失败，而且你必须能够说明它为什么失败。在能够运行这项检查之前，访问权限只是在纸面上临时存在。
