# AI 代理操作目录：找出不需要的访问权限

AI 代理操作目录是找出那些没人真正有意选择的权限的最快方法。它列出代理在自身工作区之外可以执行的操作，然后要求逐项做出决定：谁负责这项操作，出错后会发生什么，以及是否必须由人审批。

大多数团队一开始就走错了方向。他们先盘点 API 密钥、集成或软件账户，然后以为自己已经了解访问权限。其实并没有。凭据只是一个容器。真正的安全决策发生在操作层面：“创建发票草稿”和“发起退款”有着截然不同的含义，即使两个调用使用的是同一个令牌。

我见过权限审查失败，因为团队问的是：“代理需要访问计费系统吗？”这个问题太宽泛，无法诚实回答。应该分别问它是否需要读取一张发票、创建草稿、提交退款、更新收款信息，或导出客户列表。答案通常各不相同。不必要的访问权限，就藏在这些差异中。

## 集成清单会掩盖真正重要的权限

系统清单告诉你代理连接到了哪里。操作目录告诉你它可能造成什么后果。两者都要保留，但不要把其中一个误当成另一个。

考虑一个连接到源代码管理服务的代理。“仓库访问”可能包括读取代码、创建拉取请求、修改分支保护、创建部署密钥、发布版本或删除仓库。把这些内容当作一个权限，会把一组独立的风险决策简化成敷衍的“是”或“否”。

SSH 也存在同样的问题。“代理可以通过 SSH 连接到 staging”几乎没有提供有用信息。只能获取服务状态的受限命令，与一个可以重启服务、读取部署密钥或修改防火墙规则的账户下的 shell 访问，后果完全不同。应记录命令类别或端点，而不是只记录传输方式。

这个区别很重要，因为团队经常把暴露的两个维度混在一起：

- **可达性**是指代理能否联系某个系统。
- **权限**是指系统在建立连接后允许它做什么。
- **后果**是指代理发出错误或被操纵的请求后可能发生什么。

一个内部端点可能很难访问，却拥有极高权限。一个公开 API 可能很容易访问，但权限有限。只根据服务是否“内部”来设计审批，会同时漏掉这两种情况。

NIST Special Publication 800-53 的 AC-6 控制项将最小权限描述为：只授予完成指定任务所需的访问权限。这句话听起来显而易见，但应用到代理身上就不一样了。“指定任务”不能只是“协助工程工作”。它需要具体到一个具有目标、方法、边界和预期结果的操作。如果无法把这些写下来，就不能声称实现了最小权限。

从审查者无需打开代码仓库就能理解的操作名称开始。“POST /v1/issues”是有用的证据，但“在工程跟踪系统中创建问题”能让负责人明白自己正在审批什么。两者都保留在记录中。

## 一行记录一个外部可见操作

目录中的每一行，都应代表一个可以单独做出访问或审批决定的最小操作。如果两项操作合理地拥有不同负责人、不同影响或不同审批要求，就应该分成两行。

一行实用的记录需要足够详细，让工程师能够实现控制，也要足够通俗，让系统负责人能够拒绝它。可以使用以下字段：

| 字段 | 记录内容 | 存在的原因 |
|---|---|---|
| 操作 ID | 稳定标识符，例如 `deploy.production.restart-service` | 即使名称变化，也能保留原有决策 |
| 系统 | 目标系统和环境 | 区分生产环境与测试环境的访问 |
| 操作 | 人类可读的动词和对象 | 让权限可以被审查 |
| 技术路径 | API 方法和路径、命令模式或工具调用 | 让工程师能够执行边界控制 |
| 身份 | 凭据类型、账户、作用域和委派模型 | 暴露共享权限或过大的权限 |
| 处理的数据 | 发送的输入和返回的输出 | 暴露数据泄露风险 |
| 影响 | 后果类别和可逆性 | 为审批选择提供依据 |
| 负责人 | 明确的业务或技术决策人 | 让某个人对访问权限负责 |
| 审批 | 无、每次会话或每次调用 | 定义人的控制点 |
| 证据 | 测试、日志引用或实现位置 | 证明记录符合实际情况 |
| 审查日期 | 日期和审查人 | 防止旧例外变成永久权限 |

不要在操作字段中写“各种”“管理员任务”“完整 API”或“按需”。这些说法意味着目录在工作真正开始前就停止了。继续拆分，直到某个人无需猜测就能回答“是”或“否”。

下面是一个开发代理的简要示例：

```yaml
action_id: issue-tracker.create-bug
system: issue tracker, production tenant
operation: Create a bug report in the Engineering project
technical_route: POST /api/projects/engineering/issues
identity: service account agent-issues, scope issues:write
inputs: title, body, labels, repository reference
outputs: issue ID and URL
impact: internal write, reversible by project members
owner: Engineering operations manager
approval: per-session
review_date: 2026-09-30
```

即使这项操作只创建工单，“production tenant”这个词也应该保留在这一行中。许多团队在同一个生产 SaaS 租户中存放真实客户、员工和事件信息。环境标签能告诉审查者，他们正在跨越哪一道边界。

避免虚假的精确。只要目标、负责人、身份、审批和后果确实相同，就不需要为每个无害的字段更新单独建立一行。但当某个特殊字段会改变结果时，就必须拆分。“更新事件状态”和“更换事件指挥官”可能经过同一个端点，但不应获得相同的审批决定。

## 沿着凭据和工作流发现操作

只阅读代理提示词，不可能找到完整的操作集合。提示词描述意图，代码、配置、凭据和实际流量才会显示代理真正能够请求什么。

先从人们希望代理执行的工作流开始。请工程师用动词描述他们最近交给代理，或希望交给代理的十项任务。“调查构建失败”可能会扩展为读取日志、查询部署服务、创建问题、重启测试环境和发送消息。记录每一个跨越的外部边界。

然后从每个凭据反向检查。查看 API 作用域、OAuth 授权、服务账户角色、SSH authorized keys、命令包装器、CI 变量和本地密钥存储。凭据经常会暴露工作流讨论中没人提到的操作。带有用户管理作用域的 API 令牌就是一个操作候选，即使团队坚持说代理只负责创建工单。

最后，将意图与证据进行比较。网络跟踪、API 网关日志、命令审计记录和代理工具定义，会显示工作流文档遗漏的调用。可以时，先在安全的测试环境中完成这一步。生产日志依然重要，因为在时间压力下，代理和人都倾向于寻找捷径。

使用以下五轮收集流程：

1. 列出每个会超出本地任务范围的代理工作流。
2. 从代理配置和代码中提取每个工具调用、端点和 shell 命令模式。
3. 列出每个凭据允许的操作，包括继承的角色和通配符作用域。
4. 审查近期操作日志，寻找清单中没有的目标或动词。
5. 与目标系统负责人核对差异。

第四轮通常会出现令人不舒服的发现。你可能会找到一个拥有广泛仓库管理权限的旧令牌、一把被生产主机接受的 staging SSH 密钥，或一个可以触发发布的“内部” webhook。不要为了符合原计划而悄悄缩小记录。把实际有效的操作放入目录，并让某个人决定它是否应该保留。

一个有用的测试是把某一行记录交给没有参与构建该集成的同事。他们应该能够解释代理发送了什么、收到了什么，以及最严重的合理错误是什么。如果做不到，这一行只是技术残留，不是控制记录。

## 影响要描述后果，而不是一个笼统的风险分数

应根据错误操作会改变、暴露、花费或承诺什么来分类影响。单一的“高、中、低”评级会失败，因为它隐藏了操作为什么需要人的关注。

我在目录中使用五类后果：披露、完整性、可用性、外部承诺和权限变更。一行可以同时属于多个类别。读取客户导出文件会产生披露后果。删除部署会影响可用性。添加管理员会改变权限。发送合同会产生外部承诺，即使从技术上说该操作可以撤销。

再加入两个修饰因素：可逆性和影响范围。可逆性要问的是，是否能由有能力的人在不造成损失或混乱的情况下撤销操作。影响范围要问的是，错误会影响一份草稿、一个项目、许多用户，还是整个环境。

这样得出的决定才经得起解释。“删除临时测试分支”可能会改变完整性，但可以撤销，影响也有限。“轮换生产数据库凭据”可能提高安全性，却也可能影响许多服务的可用性。两者都是写操作，但应属于不同的审批类别。

不要让“API 支持撤销”决定可逆性。账本中的退款可以撤销，但客户可能已经收到令人困惑的邮件。已发布的软件包可以撤回，但下游系统可能已经获取它。已删除的账户可以恢复，但原所有者可能在事件处理期间失去访问权限。应考虑运营后果，而不只是数据库操作。

操作返回的数据同样需要关注。一个看似无害的状态查询，如果返回完整环境变量、支持记录或私钥，也属于披露操作。团队往往过度关注代理能否写入，因为写入看起来更主动。一个能够读取所有密钥、然后调用外部端点的代理，已经拥有制造严重事件的足够权限。

保持影响描述具体。不要写“高影响”，改为“可以修改工作区所有成员的生产授权”或“可以将客户标识符发送到第三方 API”。前一种描述能告诉负责人要决定什么，单独的标签做不到。

## 负责人必须是有权说“不”的人

每项操作都需要一名明确的负责人，这个人能够拒绝、缩小或移除该操作。负责人对权限决定负责，而不是对代理产生的每个运营结果负责。

最合适的负责人通常属于目标系统，或承担业务后果。工程负责人可以负责生产部署操作。财务团队负责提交退款。安全团队可以定义访问管理的条件，但不应因为自己是安全团队，就成为每一行记录的默认负责人。

共享负责人会让目录逐渐失效。“安全和工程”意味着双方都会以为对方会审查。应记录一名承担责任的负责人；如有需要，可以在备注中列出需要咨询的团队。如果负责人更换岗位或团队，应在交接中一并转移目录记录。

负责人需要一份一屏就能看完的决策材料。提供操作、系统、实际身份、涉及的数据、后果、建议的审批方式，以及代理需要该权限的简短原因。不要把一份原始 OAuth 作用域列表交给他们，然后称之为治理。

负责人应该回答四个问题：

- 代理确实需要这个结果，还是工作流只是觉得这样方便？
- 能否通过更窄的端点、角色、账户、目标或命令取得同样结果？
- 哪种错误会造成无法接受的后果？
- 谁应该批准使用，且这项决定什么时候到期？

通常最难回答的是第一个问题。团队经常授予宽泛操作，只因为这样可以避免未来再次讨论。但这不是需求，而是一次被推迟的访问审查，并且还附带了凭据。

让负责人在执行路径中清晰可见。如果发生事件时没人能找到负责人，目录就没有发挥作用。明确的负责人也让定期审查变得可行，因为审查可以直接问：“你是否仍然授权这个代理以这个身份执行操作 X？”

## 审批应该对应承诺发生的时刻

审批只有在代理造成后果之前出现，并且审查者能够理解自己允许的内容时才有用。如果代理已经把十项无关操作捆在一起，弹出一个写着“允许使用工具”的按钮，那只是安全表演。

在目录中使用三种审批状态。“无”表示代理获得会话权限后即可执行操作。“每次会话”表示人在特定代理进程开始调用任何获授权操作前，批准该进程。“每次调用”表示人需要审查该操作的每一次使用。

对于后果适中、范围狭窄且较为常规的操作，适合使用每次会话审批，例如读取构建状态或创建内部草稿问题。它能确认预期的代理进程正在运行，然后让代理完成普通工作，而不必让人反复点击。

对于会产生承诺或跨越难以恢复边界的操作，适合使用每次调用审批。应将其用于修改生产访问权限、删除重要记录、触发影响客户的部署、向公司外发送消息、处理资金和导出敏感数据。在这些情况下，让人检查每个请求是合理的摩擦。

审批记录必须包含上下文。至少要显示调用进程身份、操作、目标、目标环境和重要参数。“代理请求 POST”会迫使审查者在压力下重新推断操作。“在生产环境重启 payments 服务，由已签名进程 X 发起”才是可以做出决定的信息。

不要用审批提示来弥补一项本不应存在的权限。对“在生产环境执行任意 shell 命令”设置每次调用提示，仍然意味着审查者要一次次批准空白支票。应将宽泛的 shell 能力替换为受限命令或独立操作，然后在必要时审批这些具体操作。

操作网关在这里很重要，因为它可以让凭据远离代理，同时把人的决定放在接近执行的位置。Sallyport 使用固定的决策阶梯：锁定的保管库拒绝所有操作，新代理进程默认需要每次会话授权，选定的密钥则可以要求每次使用都审批。

这种结构能避免一个常见错误：在还没有很好地清点操作、无法安全编写规则之前，就先发明一套庞大的规则语言。复杂策略在设计文档中看起来很成熟，例外不断增加后却会变得无法审查。从清晰的操作、范围狭窄的身份，以及与影响相匹配的审批开始。

## 目录能在生产环境之前暴露失败路径

当目录捕捉到一连串单独看来都合理的选择时，它才真正有价值。考虑一个被要求调查订单工作流为何失败的编程代理。

工程师因为某个服务令牌可以读取日志，就把它交给代理。同一个令牌也有权查询订单 API。代理发现一份格式错误的订单，并被告知“清理测试数据”。由于集成没有清晰区分租户名称，它对生产租户调用了删除端点。端点接受了请求。之后人发现这份订单是真实订单，但恢复它需要在支付、库存和客户支持系统之间进行协调。

每句话单独看都无害：读取日志、查询订单、删除测试数据。操作目录会把这条链拆成几行：

| 操作 | 隐藏问题 | 更好的决定 |
|---|---|---|
| 读取订单工作流日志 | 日志包含客户标识符 | 限制返回字段，并记录披露影响 |
| 按 ID 查询订单 | 令牌拥有广泛订单访问权限 | 使用限定到所需租户的只读身份 |
| 删除测试订单 | 生产与测试共用一组端点 | 分离目标环境，并要求每次调用审批 |
| 修改客户订单 | 这不是清理工作 | 将负责人指定给运营人员，并从代理范围中移除 |

有用的发现不是“代理会犯错”。当访问边界含糊时，人也会犯同样的一连串错误。代理执行得更快，更容易重试，也可能遵循听起来在局部合理的指令，却没有注意到人类会结合上下文推断出的业务含义。

对于高后果记录，在备注中写出失败路径。使用简单格式：触发条件、错误目标或错误理解、采取的操作、即时影响、恢复负担。这样能让审批选择不再抽象，也能让审查者有理由拒绝宽泛访问。

目录还可以发现危险组合。读取事件记录可能是可以接受的。经过审查后向公共状态页面发布信息也可能可以接受。但如果同一个代理同时执行这两项操作，却没有内容边界，就可能暴露内部事件细节。应审查这样的组合：一个操作提供敏感输入，另一个操作把数据发送到组织外部。

## 让目录可执行，然后证明它符合实际

只存在于电子表格中的目录，最终会变成一份权限愿望清单。将每条获批记录连接到技术边界：独立凭据、受限 API 作用域、目标允许列表、受限 SSH 命令或审批配置。

在配置和日志中使用操作 ID。这样可以进行简单测试：每个观察到的外部操作都应映射到一个目录 ID，每个有效的目录 ID 都应对应当前的执行控制点。调查两种不匹配。没有记录的观察操作是影子访问。没有执行控制的记录可能是过时计划，也可能是未受控制的路径。

对于 HTTP 调用，验证方法、主机、路径模式、目标环境和凭据作用域。对于 SSH，验证账户、主机组、命令限制，以及辅助工具能否传递任意参数。“SSH 访问已获批”不是可执行的声明。

例如，这条目录记录声称代理只能获取部署状态：

```text
Action ID: deploy.status.read
Host: deploy.internal.example
Command: deployment-status --service \u003capproved-service\u003e
Identity: agent-deploy-read
Approval: per-session
```

但下面的实现破坏了这一声明：

```sh
command=\"/usr/local/bin/deployment-status $SSH_ORIGINAL_COMMAND\" ssh-ed25519 AAAA... agent
```

这个包装器把任意原始命令作为参数传入。如果 `deployment-status` 会调用 shell，或接受未经检查的选项，那么目录边界就是虚构的。更安全的设计是把获批的服务名称映射到固定命令，并拒绝所有其他输入。要有意测试拒绝路径。

```sh
case \"$1\" in
  checkout) exec /usr/local/bin/deployment-status --service checkout ;;
  search) exec /usr/local/bin/deployment-status --service search ;;
  *) echo \"service not permitted\" \u003e\u00262; exit 1 ;;
esac
```

不要把这段代码直接复制到 authorized-keys 配置中。它展示的是你需要的特性：代理只能从定义好的操作中选择，而不是使用任意命令语言。你的环境仍然需要输入验证、账户限制，以及由了解命令行为的人进行测试。

对于操作证据，应记录能够证明边界两侧的测试。正向测试证明获批调用可以正常工作。负向测试证明相邻的禁止调用会失败。团队经常只保留正向测试，因此宽泛凭据才会悄悄存活。

Sallyport 会从一个只能写入、无法读取的加密哈希链审计日志中，记录代理会话和单独调用。它的 `sp audit verify` 命令可以在离线状态下对密文验证哈希链，无需访问保管库密钥。这些证据有助于将目录与实际行为进行核对，但不能代替移除不必要操作的决定。

## 工作变化时审查访问，不要等到季度审查才发现问题

当你添加工具、修改代理工作流、签发或轮换凭据、更换系统负责人，或发现险些出错的事件时，都应审查目录。这些事件会改变实际有效的访问权限。等待日历上的审查，会让旧假设持续存在，同时集成不断增加。

无论如何，都要为每一行设置审查日期。高后果操作的间隔应短于无害的内部读取。审查不应只是确认电子表格还存在，而应确认代理是否仍需要这项确切操作、身份是否仍然足够狭窄、负责人是否仍然正确，以及实际使用情况是否支持保留它。

积极移除未使用的操作。团队不愿删除权限，是因为担心代理以后会需要它。如果真的需要，就按照同一个负责人和审批流程重新添加。重新授予一项已知操作，比清理一项本来不该留存的操作更省力。

第一份有用的目录不必覆盖一切。选择一个代理，列出它今天能够执行的每项外部操作，并要求负责人逐行做出决定。你会发现，有些访问之所以存在，只是因为此前没人需要给它命名。这正是应该在最糟糕的时刻到来之前移除的访问权限。
