阅读需 8 分钟

如何控制 AI 代理的刷新令牌

AI 代理刷新令牌需要由人负责的操作层、明确的刷新权限、可用的撤销机制,以及能够解释每次使用的记录。

如何控制 AI 代理的刷新令牌

AI 代理不应持有刷新令牌。这个规则听起来很严格,但只要看看令牌的作用就能理解:它允许某个进程持续获取访问权限,即使最初的人类批准已经淡出视线。访问令牌可能很快过期,刷新令牌却会让这段关系继续存在。

更合理的设计,不是让代理更擅长保护 bearer 凭据,而是把凭据放进由人工控制的进程负责的操作层。代理请求执行某项具体的外部操作,操作层判断这次运行是否可以执行,只有在需要时才刷新令牌,然后返回结果。这样,你就有了一个可以审批、撤销和调查使用情况的位置。

单靠密码保管库解决不了问题。保管库负责保护存储,代理操作层负责管理使用。团队经常把这两项工作混在一起,直到发现:即使令牌放在加密存储中,只要某个进程能向存储提出正确的问题,它仍然可以拿到令牌。

AI 代理刷新令牌会改变信任边界

AI 代理刷新令牌之所以危险,是因为它会把权限延伸到最初需要令牌的代理进程之外。如果编程代理能从环境变量、配置文件、浏览器配置文件或密钥管理器响应中读取刷新令牌,它就能通过自己可以调用的任何 HTTP 客户端使用该令牌。令牌不再属于某个边界明确的任务,而属于任何取得该进程控制权的对象。

OAuth 2.0 的 RFC 6749 将刷新令牌描述为一种凭据,用于在当前令牌过期或失效后获取访问令牌。规范允许不使用刷新令牌,但这不代表它无害。提供商签发刷新令牌,是因为每次续期访问令牌都进行交互式授权会很麻烦。正因为这种便利性,无人值守的代理才不应持有刷新令牌。

请把下面三件事分开:

  • 账户所有者,是授予访问权限的个人或服务身份。
  • 操作请求者,是此刻请求调用 API 的代理进程。
  • 凭据保管者,是保存刷新令牌并与令牌端点通信的组件。

在小型环境中,同一个人或程序可以承担多个角色,但这些角色仍然必须存在。如果同一个代理同时承担三者,它就能连接新账户、扩大作用域、无限刷新,并把操作藏在普通请求中。这不是授权设计,而是给聊天机器人接上了 bearer 令牌。

我见过团队说“代理只需要读取权限”,然后把拥有仓库、电子邮件或云服务广泛权限的刷新令牌放进本地 .env 文件。访问令牌的有效期可能很短,但刷新令牌会让这个错误持续存在。提示注入不需要说服模型泄露密码,只要说服模型利用仍然有效的授权,发出一个在当前上下文中看似合理的请求即可。

边界应当位于凭据进入代理进程之前。代理既不应收到令牌值,也不应收到一个可以在其他地方兑换的假占位符。它得到的应是操作接口,例如获取这条 issue、创建这份草稿、读取这项部署状态,或打开到指定获批主机的 SSH 会话。操作层负责这些操作背后的协议细节。

必须由人拥有授权,而不只是批准一次提示

在 OAuth 连接建立前,就应明确谁可以批准刷新。“任何开发者都能点击允许”这种做法,在账户所有者离职、共享邮箱易手,或代理以他人身份重新连接服务时就会失效。

对于个人 SaaS 账户,账户所有者应完成初始授权,并在撤销或过期后批准重新连接。对于共享运维账户,应指定一名负责所有者和一名可以撤销授权的备份人员。对于机器身份,服务所有者应授权客户端注册和作用域。不要因为人和机器身份都能调用同一个 API,就把它们当成同一种身份。

把经常被一次浏览器点击合并的四个决定分开:

  1. 谁可以创建最初的授权。
  2. 哪个操作层可以保留由此产生的刷新令牌。
  3. 哪些代理会话可以使用这项授权执行操作。
  4. 谁可以撤销授权或批准新的连接。

最初的 OAuth 同意页面只回答第一个问题,有时回答得还不完整。它告诉提供商,账户持有人授权了一个具有指定作用域的客户端,却不会告诉你的本地系统:不受信任的仓库、新的代理子进程或夜间任务是否可以使用这项授权。

实用的所有权记录不应只有提供商账户邮箱。应保存一条本地授权记录,包含不透明的授权 ID、提供商名称、账户引用、作用域集合、所有者、备用撤销人、连接日期,以及允许请求该授权的操作。不要把刷新令牌复制到这条记录中。记录的作用是解释令牌,而不是成为第二个秘密存储。

共享访问是最棘手的情况。团队常常因为方便而连接一个管理员账户,然后让每个开发者的代理都使用它。这会破坏责任追踪。如果 API 支持服务账户、应用安装、委派身份或作用域更窄的项目令牌,应优先使用它们。如果不支持,就把操作层限制为少量获批操作,并记录共享账户的实际所有者。

不要让代理自行发起新的 OAuth 浏览器流程。代理可以展示合法的提供商页面,也可能把人引向权限更广的账户、更宽的作用域选择或不同的租户。发起连接属于管理操作。应要求人从操作层发起连接,并在同意前检查账户和作用域列表。

操作层只应为完成获批操作而刷新令牌

受控的操作层只有在收到经授权且需要当前访问令牌的请求时,才应兑换刷新令牌。不要让它运行一个“以防万一”定期刷新所有凭据的后台循环。预先刷新在代码中看起来整洁,却会让事件响应更困难,因为它会在没有对应的人类或代理操作时持续维持授权。

请求路径可以很简单:

  1. 代理会话请求一项命名操作,并提供普通的操作参数。
  2. 操作层找到与该操作关联的授权,然后检查该会话是否可以使用它。
  3. 如果缓存的访问令牌不存在或即将过期,操作层就把刷新令牌发送到提供商的令牌端点。
  4. 操作层使用访问令牌调用目标 API,并把经过筛选的结果返回给代理。
  5. 操作层记录操作和刷新事件,但不记录凭据内容。

代理永远不应选择令牌端点、客户端标识符、回调 URL 或作用域字符串。这些值属于经过人工批准的连接定义。允许代理提供这些值,会把你的网关变成开放的令牌中继。

假设代理被要求向项目跟踪系统发布一份版本说明。它请求操作层在指定项目中创建一条 issue。操作层发现该操作需要这个项目的跟踪系统授权,于是检查请求会话,在必要时续期访问令牌,并发布说明。返回给代理的可以是新 issue 的 ID,以及提供商返回的类似 URL 的引用,而不是用于创建它的 bearer 令牌。

现在改变提示内容。恶意仓库指令要求代理通过列出组织中的所有项目,并在每个项目中创建测试 issue 来“验证访问权限”。如果代理持有刷新令牌,这条指令就可能变成一连串直接的 API 调用。如果代理只有命名操作接口,操作层可以拒绝超出获批项目范围的请求,或要求再次获得人工授权后才继续会话。

这不需要复杂的策略语言,只需要一组小而易懂的选择:哪个进程在请求、它可以使用哪项授权,以及这次操作是否需要人工批准。选项更多并不会自动让设计更安全,反而常常让操作员无法判断最终生效的是哪条规则。

Sallyport 采用了这种分离方式:API 凭据保存在加密保管库中,HTTP 或 SSH 操作通过 MCP 连接执行,而不是把凭据返回给代理。

授权类型决定了哪些自动化方式是安全的

如果要在桌面或本地应用中连接人的账户,应使用带 PKCE 的授权码流程。用户在提供商处登录,检查同意请求,然后通过已注册的重定向路径返回本地应用。PKCE 会把授权响应绑定到发起流程的客户端,降低被截获的授权码的价值。

RFC 9700《OAuth 2.0 安全最佳实践》规定,公共客户端必须使用 PKCE。它还规定,公共客户端的刷新令牌必须使用发送者约束或刷新令牌轮换。这项指导对代理集成很重要,因为本地应用通常属于公共客户端。在桌面应用中内置客户端密钥,并不会让它变成机密客户端。任何拿到应用的人都可以提取该密钥。

根据要连接的身份选择流程:

  • 人的提供商账户使用带 PKCE 的授权码流程。
  • 如果提供商支持,且不需要委派人的账户,则为服务身份使用客户端凭据。
  • 如果提供商提供更细粒度的项目或组织访问,应使用其专用的安装或应用模型。
  • 只有在提供商和运行环境确实要求时才使用设备授权,并清楚展示用户正在批准的身份和作用域集合。

客户端凭据通常不会产生刷新令牌,因为客户端可以再次验证自身身份来请求新的访问令牌。如果服务身份权限很窄,且客户端认证材料一直留在操作层中,这种方式对自治任务可能更安全。但不要借客户端凭据之名,把权限广泛的客户端密钥交给编程代理。

离线访问需要特别留意。一些 OpenID Connect 提供商要求 offline_access 作用域,才会签发刷新令牌。只有当操作确实需要在交互式会话结束后继续运行时,才请求它。如果每次操作都有人在场,短时访问令牌配合新的授权可能更合适。团队常常默认请求离线访问,因为这样不用处理过期问题,却把一次麻烦换成了长期凭据。

完全不要使用资源所有者密码凭据。RFC 9700 已将这种授权方式标记为过时,因为它会把用户密码交给客户端。操作层并不能让这种做法变得可以接受,只是多了一个丢失密码的位置。

只有在存储能正确处理替换时,轮换才有用

锁定每个操作
保管库锁定后,Sallyport 会拒绝所有操作,直到你使用 Touch ID 解锁。

刷新令牌轮换会在每次成功刷新后用新令牌替换旧令牌,从而减少令牌被复制后的损害。提供商可以检测旧令牌的重复使用,并使受影响的授权令牌族失效。这种检测很有帮助,但如果自身的刷新逻辑不严谨,也可能把合法集成锁在门外。

最常见的问题是竞争条件。两个代理会话几乎同时需要访问令牌,并且都读到了同一个旧刷新令牌。第一个会话成功刷新并收到新值,第二个会话片刻后提交旧值。根据提供商的行为,第二个请求可能失败,也可能触发重复使用检测,使包括新令牌在内的整个令牌族失效。

每项授权只设置一个刷新所有者,就能避免这种竞争。操作层应按授权 ID 串行处理刷新任务。第二个调用者等待第一次刷新完成,然后使用新缓存的访问令牌,而不是再次发送令牌请求。这是正确性要求,不是性能优化。

在认为刷新成功、允许后续工作前,先保存替代令牌。安全的顺序如下:

  1. 通过 TLS 将旧刷新令牌发送到令牌端点。
  2. 验证令牌响应,并确认它对应预期的提供商和授权。
  3. 在加密存储中通过一次持久化更新写入新刷新令牌和元数据。
  4. 在本地状态中标记旧令牌不可用。
  5. 使用新的访问令牌释放等待中的调用,或让它们重新发起请求。

如果进程在提供商轮换令牌后、而本地存储记录替代令牌前崩溃,你可能会失去这项授权。重试无法解决这个问题。恢复路径是由人主导的重新连接,这正是所有者和撤销人记录重要的原因。

有些提供商只在部分情况下签发新的刷新令牌,另一些会返回同一个令牌。代码必须同时兼容这两种行为,不能假定其中一种一定成立。在确认提供商响应和持久化写入成功前,只保留旧值。调试时绝不要记录任一令牌值。很多令牌泄露都始于临时调试语句,最后却跟着版本一起发布了。

发送者约束令牌可以把令牌绑定到客户端持有的加密密钥,从而降低重放风险。RFC 9449 定义的 DPoP 就是一种方法。但它并不能消除保管责任。如果代理同时可以使用刷新令牌和私有签名密钥,它仍然拥有持久权限。应把两类材料都放在操作层之后,并在依赖这种约束前测试提供商的实际行为。

撤销需要指定操作员和经过测试的路径

撤销不是一次启用后就不用管的设置。它是有人必须在压力下执行的操作,因为那时提供商控制台可能很慢,也没人记得到底哪个账户批准了这项集成。

为账户所有者和指定的备份人员提供直接的撤销路径。撤销授权时,操作层必须删除本地刷新令牌,使缓存的访问令牌失效,并停止仍能请求该授权的会话。只禁用代理界面、却把凭据留在存储中,不算完整撤销。

RFC 7009 定义了 OAuth 令牌撤销请求。提供商会公布自己的端点,但请求通常类似下面这样:

POST /revoke HTTP/1.1
Host: authorization.example
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <client authentication>

token=<refresh-token>&token_type_hint=refresh_token

RFC 7009 要求服务器即使在提交的令牌已经失效或未知时,也返回成功响应。这可以防止攻击者利用该端点充当令牌有效性探针。这也意味着,操作员不能仅凭 HTTP 成功响应就认定授权仍然拥有活动访问权限。应记录已发送撤销请求,删除本地凭据,然后通过一次无害的提供商调用或提供商审计记录进行确认,如果服务提供此类记录的话。

应为以下事件准备撤销流程:账户所有者离职、怀疑代理会话遭到入侵、仓库指令导致意外外部调用、集成退役,或提供商报告令牌被重复使用。不要等到发生泄露后,才决定谁有权按下按钮。

已经签发的访问令牌可能会一直可用到过期。有些提供商会立即撤销它,有些不会。本地层可以立即停止签发新的操作,这是你真正控制的部分。除非提供商明确记录并且你已经测试过,否则不要承诺全局即时失效。

把提供商撤销和本地禁用分开。 本地禁用会阻止操作层使用授权,提供商撤销则要求提供商也拒绝它。事件处理中应按这个顺序执行:先切断自己的执行路径,再发送提供商请求。第一步由你控制,不应依赖外部网络调用。

审计记录必须解释意图,而不只是记录流量

批准每次代理运行
新的代理进程必须先获得一次会话批准,才能使用已配置的 API 密钥。

HTTP 调用清单无法告诉你一次刷新是否合理。你需要一条记录,把人工决定、发起请求的代理会话、授权引用和最终的外部操作连接起来。

不要把刷新令牌、访问令牌、授权码、客户端断言或完整 API 正文写入审计日志。令牌字符串本身就是秘密。完整响应正文可能包含客户数据、仓库内容或个人信息。为方便而记录这些内容,会制造出第二个更混乱的凭据和数据存储。

有用的事件记录应包含事件 ID、时间、会话 ID、请求进程身份、授权 ID、连接账户引用、操作名称、提供商主机、请求资源、连接时记录的作用域集合、批准引用、结果类别,以及发生错误时的错误代码。对于刷新,记录发生过刷新以及是否成功即可,不需要令牌值就能调查。

操作记录和凭据记录的区别很重要。操作记录说明某个代理会话请求获取指定环境的部署状态,操作层允许了该操作。凭据记录说明哪个授权支持这次请求,以及谁拥有它。两者可以通过不透明的授权 ID 关联,但不要让所有能查看操作历史的操作员都能看到账户连接详情。

防篡改机制会改变调查质量。如果遭到入侵的本地进程可以修改自己写入的同一份日志,攻击者就能删除最重要的记录。应使用追加写入的事件处理和完整性检查,并由生成日志的进程之外的组件独立验证日志。

Sallyport 从加密、哈希链式的审计日志生成 Sessions 和 Activity 日志,sp audit verify 无需保管库密钥即可离线检查这条链。

应根据授权的权限安排相应频率的审查。个人 issue 跟踪器授权可能偶尔审查即可。能够修改生产基础设施的授权,则应在每次新连接、每次作用域变更和任何异常代理行为后审查。操作层应让记录足够易读,使所有者能回答:“哪个代理以什么目的使用了我的账户,又是在谁的批准下进行的?”

浏览器配置文件和通用令牌代理会形成隐蔽的绕过路径

让令牌处理留在本地
在执行时注入 bearer、basic 或自定义标头凭据,不必把它们传给代理。

浏览器配置文件不适合作为代理的凭据存储。它可能包含会话 Cookie、缓存的访问令牌、刷新令牌、账户选择器以及无关的浏览状态。让代理访问这个配置文件,比委派一项 API 操作宽泛得多,也更难清理,因为提供商状态和浏览器状态混在了一起。

如果通用令牌代理接受调用者任意提供的令牌端点参数,也会造成同样的问题。团队经常构建一个名为 getToken(scope) 的 API,因为令牌不再位于代理进程中,就觉得更安全了。如果任意会话都能请求任意已连接账户或任意作用域,这个代理仍然是令牌自动售货机。

让调用者请求操作,而不是请求令牌。“在项目 A 中创建版本说明”有所有者、目标和可检查的作用域要求。“给我一个 tracker.write 令牌”则把太多权限留给了调用者。

不要在还不了解操作之前,就用庞大的规则引擎解决问题。一份与指定授权和人工审批点绑定的简短获批操作目录,更容易审查,也更难绕过。只有真实的运行需求要求时,才增加复杂度。

还要避免在开发、预发布和生产环境之间共享同一个刷新令牌。分开的授权能让撤销影响更小,审计记录也更清楚。预发布代理不应因为两个环境碰巧使用同一个身份提供商,就保留通往生产环境的路径。

从所有权表和一次撤销演练开始

第一份有用的成果是授权清单,而不是代码。为每个代理可能触发使用的刷新令牌建立一行记录。包括提供商、账户引用、授权 ID、作用域、操作层位置、账户所有者、备用撤销人、创建方式、最近确认使用时间和本地禁用步骤。如果有一列填不出来,就说明你还没有真正控制这项授权。

然后使用低风险集成进行一次撤销演练。让所有者在本地禁用授权,在提供商处撤销它,再尝试执行一项普通代理操作。确认操作层会阻止请求,重新连接需要明确的人工操作,审计轨迹能识别之前的会话。这项练习会迅速暴露各种假设:缺失的提供商端点、未知的账户所有权、存放在旧开发者电脑上的令牌,以及超出本地预期仍然有效的缓存访问令牌。

为没有提供商强制期限的授权设置到期审查。无人值守工作有时确实需要长期访问,但无限期访问应当是经过明确决定的例外,并指定所有者。如果团队说不出所有者是谁,这项授权就不应继续可用。

应坚持一个简单标准:代理可以请求工作,但不能继承永久续期账户权限的能力。把刷新凭据放在人的控制范围内,让撤销成为经过练习的操作,而不是出事后在浏览器标签页中紧急寻找。

常见问题

OAuth 中的刷新令牌是什么?

刷新令牌允许客户端在不要求用户再次交互式登录的情况下获取新的访问令牌。它通常比访问令牌有效得久,因此拿到它的代理可能在最初的提示消失很久后继续执行操作。应当像管理凭据一样,为它安排明确的所有者和撤销方案。

AI 代理可以安全使用 OAuth 刷新令牌吗?

只有在代理永远拿不到令牌值,也不能自行选择作用域或刷新策略时,代理才可能安全地使用刷新令牌。受控的操作层应持有授权,仅在获批准的操作需要令牌时请求刷新,并把 API 结果返回给代理。把令牌交给代理进程,会让每次提示注入或本地进程入侵都变成凭据事件。

谁应该授权代理刷新 OAuth 访问权限?

连接账户的个人或团队应批准最初的授权。其他操作员可以运行操作层,但不应悄悄扩大作用域,或重新连接他人的账户。在首次授权前写下所有者,因为令牌本身通常不会告诉你谁批准了它的使用。

哪种 OAuth 流程适合把人的账户连接到代理?

当人通过交互式浏览器会话连接自己的账户时,应使用带 PKCE 的授权码流程。不要仅因为设备码流程看起来更适合命令行代理就选择它,因为它会增加一个人们经常疏于监控的审批入口。客户端凭据适用于机器身份,不适用于个人的 SaaS 账户。

什么是刷新令牌轮换?

刷新令牌轮换是指授权服务器每次使用旧刷新令牌时,都签发一个替代令牌。操作层必须先保存替代令牌,再进行下一次刷新,并丢弃旧值。如果两个进程同时刷新,其中一个可能触发重复使用检测,使整个授权令牌族失效。

OAuth 刷新令牌过期后应该怎么办?

已过期或已撤销的授权应停止操作,并向账户所有者发出清晰的重新连接请求。不要退回到另一个已保存的账户,不要悄悄请求更广泛的权限,也不要连续重试数小时。刷新失败通常是在提醒你,之前的授权已经不再属于当前任务。

如何撤销 OAuth 刷新令牌?

如果服务提供撤销端点,应先使用该端点,然后删除本地凭据,并停止能够请求该凭据的活动代理会话。RFC 7009 定义了撤销请求格式,但不同服务在提交刷新令牌后撤销的内容可能不同。如果服务支持,可以通过一次无害的 API 调用或提供商审计记录确认结果。

OAuth 代理的审计日志应该记录什么?

日志应标明发起请求的代理进程或会话、允许该会话的人工批准、连接的账户引用、目标、使用的作用域和结果。不要保存 bearer 令牌、授权码或敏感的 API 响应正文。只有时间戳无法说明一次刷新是否合法。

密钥管理器足以保护代理的 OAuth 令牌吗?

密钥管理器可以保护存储,这很有必要,但它不会决定某次代理运行是否可以使用凭据。操作层在代理与提供商之间增加了一个决策点。如果自治进程能够发起外部调用,就需要两者。

我能查看 OAuth 刷新令牌来判断它可以访问什么吗?

不行。许多提供商使用不透明的刷新令牌,无法仅凭令牌值判断其作用域、所有者、到期时间或撤销状态。创建授权时,应把这些信息保存到自己的授权清单中,并通过提供商验证实际行为,而不是相信令牌的格式。

Sallyport

Sallyport 替你的 AI 智能体执行 API 调用和 SSH 命令。密钥留在你 Mac 上的本地密钥库里;每次运行由你批准,每个操作都落入一份密封的审计日志。

© 2026 Sallyport · 依据 Apache-2.0 开源 · Oleg Sotnikov