# AI 智能体的凭据别名：更安全的本地操作

智能体应该按名称请求连接，而不是接收让连接真正生效的凭据。这听起来像是一个很小的 API 设计选择。实际上，它决定了智能体是在边界内执行操作，还是变成一个进程，手里揣着一把可以反复使用的密钥。

我见过团队把密钥叫作 `GITHUB_TOKEN`，放进智能体的环境变量，然后因为工具接口看起来整洁就放心了。令牌仍然会进入进程内存、Shell 历史记录、调试输出、子进程，有时还会进入智能体自己的工作笔记。换个暴露方式，并不会降低暴露风险。

**AI 智能体的凭据别名**只有在可信的本地执行器负责解析简短、易读的连接名称，并亲自执行认证请求时才真正有效。智能体请求 `repo-release-prod`，执行器在使用点注入凭据，智能体得到状态码、经过密钥过滤的响应正文，或命令输出。密钥不会成为智能体循环的输入或输出。

这个边界需要比大多数第一版方案更仔细地设计。别名可以提供清晰的操作入口、方便的轮换和有用的审计记录。如果不定义名称允许做什么、谁可以调用它，以及如何停止它，别名也可能变成宽泛且永久的权限，只是外面套了一层好看的标签。

## 别名指向的是权限，不是隐藏字符串

有用的凭据别名应该指向一个有明确用途的服务连接。它不应该代表一个智能体可以随意浏览的密钥数据库，也不应该暗示所有携带该别名的请求都可以接受。

以 `payments-prod` 为例。对于自主智能体来说，这个名称太模糊了。它允许读取发票、发起退款、编辑账户数据，还是下载报表？一个 bearer token 在技术上可能同时支持这些操作，但别名不应该抹去它们之间的区别。更好的名称会直接表达操作意图：`payments-invoice-read-prod` 和 `payments-refund-review-prod` 能让操作员清楚自己准备授权什么，也能让六个月后的审计记录更容易理解。

人们经常把三种不同的东西混在一起：

- **密钥引用**用于标识存储的材料，例如保险库路径或密钥 ID。
- **凭据别名**用于标识一个可以实际进行认证的连接。
- **能力名称**用于标识允许执行的操作，例如发布版本或读取构建日志。

在小型系统中，它们可能一一对应，但不应该在无意中变成同一个概念。密钥引用可能指向一个可以调用多个端点的令牌。一个连接可能需要令牌和客户端证书。一个能力在不同环境中可能需要不同的连接。如果把它们合并，轮换、审查和事故响应都会变得更困难，因为没人能说清楚到底是哪一部分发生了变化。

我倾向于让别名包含目标服务、环境和任务，同时省略凭据类型。`artifact-publish-prod` 就比 `artifact-api-token-2` 更好。前一个名称可以在从静态令牌迁移到短期交换机制后继续使用。后一个名称却把实现细节写进了每个智能体提示、工具调用、测试和运行手册。

默认不要让智能体枚举全部别名。连接名称列表经常会暴露内部服务名、环境和高权限工作流。更重要的是，枚举会把受约束的智能体变成一个好奇的智能体。给任务提供它需要的少量名称，或者提供一个由内部逻辑选择连接的面向操作的工具。

## 密钥离开上下文后，必须留在每条路径之外

让令牌不出现在主模型提示中是必要条件，但远远不够。如果智能体可触达的进程树中有任何组件可以读取密钥，或诱使其他组件把密钥打印出来，凭据就可能泄露。

考虑一种常见的失败设计。编程智能体通过 `connection=deploy-prod` 调用本地 helper。helper 查找 API 令牌，然后把令牌放进环境变量，启动一个命令行客户端。命令失败后，诊断包装器把环境变量打印到日志文件。智能体可以访问工作区的日志目录，于是为了说明失败原因，它读取了日志中的令牌。

没人打算泄露密钥。架构本身造成了泄露，因为 helper 把环境变量当成了私密通道。它并不私密，子进程、崩溃报告、诊断工具以及任何能够检查自身环境的命令都可以看到它。把凭据放进命令参数更糟，因为进程检查和 Shell 历史记录都可能暴露它们。

本地执行器应该把凭据保存在自己的受保护存储中，在内存中构造经过认证的请求，然后返回经过明确限制的结果。对于 HTTP，通常由执行器自己添加授权请求头。对于 SSH，则由执行器选择私钥，或调用一个能够使用私钥而不把密钥文件路径和内容交给智能体的 helper。

响应路径同样需要关注。有些 API 会在创建令牌时返回凭据，webhook 可能回显授权数据，错误报告也可能包含请求头。执行器需要响应策略，删除已知的密钥承载请求头，并避免反映自己的请求构造过程。这不是无声改写业务数据的理由，而是要明确智能体有权看到请求和响应的哪些部分。

一个干净的测试比设计图更有用。让智能体尝试获取别名值、导出工具设置、触发认证错误、在能力允许时检查运行中的子进程，并读取所有它能够访问的日志位置。预期结果应该很无聊：它只能找到名称和操作结果，找不到令牌、密码、私钥或可以重复使用的签名请求。

## 连接范围必须匹配操作范围

别名有多窄，取决于它背后的权限有多窄。一个隐藏得再好的管理员令牌，仍然是管理员令牌。

对于 HTTP 服务，范围首先取决于服务账户或令牌权限。给发布智能体创建版本和上传所需制品的权限。不要因为未来可能有用，就同时授予仓库管理、组织成员管理、计费访问或用户模拟权限。将来的需求应该触发新的审查，通常也应该使用不同的连接。

当服务自身的授权模型无法表达端点限制时，执行器应该补上这层限制。团队往往会因此选择一个庞大的策略语言。演示时它看起来很灵活，但当操作员必须判断一个陌生请求是否会通过时，就会变成维护负担。如果需要限制端点，就让规则清晰而精简：允许的方法、主机、路径族和预期凭据。异常请求正文和重定向应该作为需要明确设计的情况，而不是偶然的例外。

SSH 也需要同样的纪律，只是控制方式看起来不同。能够打开交互式 Shell 的密钥会给智能体带来非常大的操作范围。优先使用只能在受限目录中完成所需任务的账户，或使用只接受指定操作的强制命令。不要以为 `~/.ssh/config` 中的主机别名能解决问题。它只是为客户端选择目标，并不会限制认证成功后发生什么。

OpenSSH 的 `authorized_keys` 手册记录了 `command=`、`no-pty`、`no-port-forwarding` 和 `restrict` 等选项。这些都是机器连接的实用工具，但也有明显的边界：强制命令必须验证参数，禁止端口转发也不会让一个权限宽泛的 Shell 账户变得安全。先查看有效的账户权限，再使用 SSH 选项减少不需要的路径。

不要因为生产环境和预发布环境只是在 URL 参数上有所不同，就让它们共用一个别名。智能体在压力下也会像人一样犯错。分开的名称让你可以分别设置凭据、批准方式和审计预期，也能防止预发布任务通过字符串替换错误获得生产权限。

## 执行器必须先认证调用方，再接受别名

如果任何本地进程都能调用凭据别名，别名就保护不了任何东西。执行器需要清楚回答一个简单问题：哪个进程请求了这项操作？

进程 ID 本身不是身份。它们生命周期短，而且可能被重新使用。进程名称更不可靠，因为其他程序可以使用同一个名称。在 macOS 上，代码签名权威能让审批者更好地了解请求背后的可执行文件，但它不会让请求的操作自动变得安全。操作员仍然需要判断，这个经过签名的进程是否应该在这个会话中获得这个连接。

对于需要发起许多相关调用的智能体，按会话授权很合适。新智能体进程第一次请求时，提交调用方身份和请求的运行任务。批准在进程退出前对该进程有效。这样可以避免让人类批准一连串例行的小调用，同时保留不同运行之间的边界。

有些别名应该每次使用都做决定。生产部署、破坏性 API 操作和客户数据访问都属于合理候选。批准界面应该用清晰的语言显示别名、目标、操作和调用方身份。要求人们批准一个不透明的事件，只会训练他们无脑点击确认。

RFC 6749 做了一个智能体工具构建者应该保留的有用区分：授权服务器颁发访问令牌，资源服务器接受令牌。智能体没有必要充当其中任何一个。在本地执行器设计中，执行器可以负责持有或获取凭据，并发送经过认证的请求。智能体则只负责请求定义清晰、范围狭窄的操作。与其因为智能体能发 HTTP 请求，就把它当作通用 OAuth 客户端，这种分离更干净。

这不意味着每个智能体都需要交互式批准。受控的构建工作器可以使用范围狭窄且预先批准的连接。关键在于，你能否识别调用方，以及它的权限是否符合分配的任务。如果这两个问题都没有可靠答案，别名只是用更友好的界面掩盖了本地权限提升。

## 轮换应该更换材料，而不是破坏名称

别名的运营价值会在轮换时体现出来。你替换 `artifact-publish-prod` 背后的令牌或 SSH 密钥，智能体继续请求同一个连接名称，不需要更新提示、源文件或智能体配置中的新密钥。

这并不意味着别名应该永久存在。只有在含义保持不变时，才保持名称稳定。如果令牌从只读权限变成写入权限，旧名称就不再诚实。如果所有权转移给另一个团队，原有的批准预期可能不再合适。如果凭据从测试租户转移到生产租户，即使 API 完全相同，也应该创建新的别名。

轮换流程应该验证变更的两面：

1. 将替代凭据放到现有别名后面，通过执行器发起一次范围很窄的健康检查调用。
2. 在服务提供商处撤销或禁用旧凭据，然后再次执行预期的智能体操作。
3. 检查审计记录中是否有失败重试或异常调用方，因为过时的进程经常会在轮换后暴露自己。
4. 删除复制的凭据文件、旧环境设置，以及绕过执行器的应急脚本。

最后一项是实际轮换最容易失败的地方。有人为了让发布继续进行而创建临时令牌，把它留在 CI 变量或本地脚本里，然后忘记清理。经过设计的别名路径顺利完成轮换，但被遗弃的旁路仍然可以使用。找到并关闭所有替代路径之前，轮换就还没有完成。

OWASP Secrets Management Cheat Sheet 建议团队轮换密钥并应用最小权限。这两条建议都正确，但通常被当作存储卫生问题来介绍。对于智能体来说，轮换还保护了指令层。稳定的别名意味着模型上下文不包含不断变化的密钥，也不需要执行可能出现在对话记录、提交信息或工具输出中的密钥更新任务。

## 审计记录应该描述决策，但不能记录密钥

日志应该帮助你回答谁请求了操作、请求了哪个连接、执行器做了什么，以及是否获得批准。日志绝不能变成密钥保险库的便利副本。

实用的事件记录包括时间戳、会话标识符、调用方身份、别名、通道、目标、方法或命令类别、授权决策和结果类别。对于 HTTP 调用，记录 `POST /releases` 通常已经足够解释操作。记录完整的 `Authorization` 请求头毫无用处，还会制造严重的清理问题。对于 SSH，记录主机别名、远程账户或命令类别以及退出状态，不要记录可能泄露敏感资产清单的私钥指纹。

将会话记录与单次操作记录分开。会话记录说明智能体运行何时开始、哪个本地进程拥有它，以及你是否撤销了它。单次记录说明运行过程中发生了什么。如果把它们合并成一条模糊的活动流，撤销可疑运行并查找其操作会比应有的时间更长。

防篡改能力会改变审计轨迹的质量。任何能够修改文件的人都可以编辑只追加文件。哈希链则让后续修改变得可检测，因为每条记录都会包含上一条记录的摘要。它无法阻止删除，也不能证明恶意执行器记录了真实内容，但会让悄悄改写历史更难伪装成功。

一个有用的验证命令应该有非常简单的契约：

```
$ sp audit verify
verified 184 records
chain: valid
first record: 2025-04-03T09:14:22Z
last record:  2025-04-03T16:48:01Z
```

实际数量和时间戳会有所不同。重要的是，验证过程会读取存储的加密记录流，检查哈希链，并在有人修改记录时报告无效位置。对密文进行离线验证尤其有用，因为调查人员无需打开保险库或暴露存储的操作详情，就能检查记录的连续性。

不要把防篡改能力和监控混为一谈。你仍然需要查看可疑活动，在事故开始时保存副本，并确定谁有权撤销会话。密码学可以告诉你记录序列发生了变化，却无法告诉你 `delete-production-data` 是否是一个合理请求。

## 批准疲劳说明别名设计有问题

如果一个人必须批准每个无害操作，他们最终会不看内容就批准。这不是人的问题，而是系统没有区分例行权限和高影响权限。

对于允许使用普通运营风险连接的受限智能体运行，使用会话批准。批准界面应该标出进程的代码签名权威，因为 `agent` 这样的标签几乎没有信息。如果该进程退出，下一个进程就需要重新批准。相比无限期有效的一次性批准，这种方式更能限制被复制的工具调用。

对于每次调用都值得审查的别名，使用逐次调用批准。执行器应该让决策事件足够清晰，使人类能够快速拒绝。`Use payments-refund-review-prod to submit refund request for order 4821` 是可以执行的描述。`Tool call requires approval` 则是懒惰的设计。

Sallyport 因此采用固定的决策阶梯：锁定的保险库拒绝所有操作；新智能体进程默认需要会话授权；单个凭据可以设置为每次使用都需要批准。它没有采用策略语言，因为本地操作员不应该在停止智能体之前，先去调试一个授权解释器。

不要把每次确认都变成与智能体的辩论。批准是控制点，不是对话提示。如果运行偏离方向，就拒绝操作、撤销会话，并检查之前的记录。如果任务经常需要例外批准，就重新划定任务边界，或创建一个权限与重复工作相匹配的更窄连接。

## 别名无法解决提示注入或糟糕的任务设计

凭据隔离可以限制密钥被盗造成的损害，但它不会判断智能体是否应该调用 API、上传文件或运行远程命令。

藏在仓库 issue 中的提示注入可能告诉智能体发布版本、通过允许的端点窃取报表，或使用 SSH 连接执行一个处于宽泛账户权限范围内的命令。如果攻击者始终没有拿到令牌，别名就完成了自己的任务。但智能体仍然可能执行有害的、被允许的操作。

这就是为什么宽泛别名即使不泄露密钥也很危险。`cloud-admin-prod` 给注入指令留下了太多空间。`artifact-publish-prod` 配合受限服务账户和预期目标，留下的空间更小。你仍然需要限制智能体可以读取的文件，将不可信内容与操作指令分开，并在操作后果超出任务范围时要求批准。

另一个薄弱设计是提供一个通用的 `http_request` 工具，再加上别名参数。这样智能体就能把强大的凭据指向任意路径、重定向后的主机，或操作员从未考虑过的端点。像 `create_release` 这样的专用工具灵活性更低，但这往往是安全优势。当智能体会遵循不可信文本时，通用性并不是中性的。

把每个别名都当作一句可以写下来的话：“这个已识别的进程，可以在这样的批准方式下，使用这个连接，对这个目标执行这一类操作。”如果你必须使用“任何事情”或“按需”之类的词才能写出这句话，那么在把连接交给智能体之前，还需要进一步设计。

## 本地执行器应该保持简单且易于检查

执行器拥有比智能体更多的权限，所以它应该做得比智能体更少。它需要加密凭据存储、少量操作通道、调用方识别、决策处理和审计记录。它不需要变成远程访问网关、万能代理，或拥有数百个例外的自定义规则语言。

在本地开发环境中，菜单栏应用可以让保险库状态和待处理批准清晰可见，同时避免增加一个操作员可能忘记存在的独立后台服务。这并不意味着可以降低工程质量，但能把敏感操作集中在一个可以锁定、拒绝请求并维护自身记录的位置。

Sallyport 将 API 密钥和 SSH 密钥保存在加密的本地保险库中，并代表支持 MCP 的智能体执行 HTTP 和 SSH 操作，而不是把凭据传入智能体上下文。它的 `sp mcp` shim 为智能体提供普通的 stdio 连接，同时由应用担任执行器。

易检查意味着操作员在事故处理中无需阅读源代码，就能回答一些基本问题：保险库是否锁定？哪个运行正在进行？它请求了哪个连接？我能撤销这次运行吗？记录序列是否发生过变化？如果必须把五个不同组件的日志拼在一起才能回答这些问题，系统也许很聪明，但凌晨两点并不好管理。

保持工具协议简洁。返回结构化的成功和错误结果。避免错误信息包含内部请求对象或密钥存储路径。让取消操作明确可用，以便用户停止长时间运行的操作，而不会杀掉无关工作。这些选择看起来很普通，却决定了你拥有的是一个可以运营的边界，还是一个只能寄希望于存在的边界。

## 像能编写提示的攻击者一样测试边界

顺利完成的演示只能证明别名能够解析。在信任这套设计之前，应测试智能体或受感染插件可能如何把一个连接名称变成更广泛的访问权限。

先从指向非生产服务账户的别名开始。运行一个执行预期调用的智能体任务，然后给它对抗性指令，让它检查工具架构、请求配置转储、触发格式错误的请求、跟随重定向、启动子命令并读取可访问的日志。检查它对外显示的对话记录和执行器记录，寻找密钥材料、意外目标，以及绕过预期操作边界的调用。

然后有意测试失败场景。在请求进行时锁定保险库。在第二次调用前撤销会话。轮换底层凭据。在副本中修改一条审计记录并运行验证。每个预期失败都应该让操作员清楚，同时不给攻击者提供帮助。“访问被拒绝，因为保险库已锁定”是合理的。包含保险库记录或请求头值的堆栈跟踪则不可接受。

最后测试一个棘手的运营场景：智能体运行正在正常工作时，行为突然变得可疑。操作员能否立即识别并撤销这次运行，确定哪些调用已经成功，并验证记录序列没有被编辑？如果这些操作需要在终端和云控制台之间进行寻宝式排查，就应该先修复问题，再添加更多别名。

命名连接是一个很小的接口，却有很大的影响。让它的名称诚实、权限狭窄、调用方可识别、记录有用。然后让智能体请求操作，却永远不给它一份值得窃取的密钥。
