AI agent Secret Rotation 与 Credential Expiry Recovery:长期运行任务里的 token 过期后怎么续,不把权限暴露给模型

HTMLPAGE 团队
16 分钟阅读

长任务、定时任务和企业代理一上来,token 过期就不再是偶发异常。本文讲清 rotation window、expiry detection、re-auth handoff 与恢复边界,避免系统为了续签成功而把权限交给模型。

#AI agent #Secret Rotation #Credential Recovery #工程实践

很多团队第一次把 AI agent 接进真实系统时,凭证问题看起来还不大。一个短请求、一次工具调用、几十秒内结束,环境变量里塞个 token,似乎就够了。真正的麻烦通常出现在系统开始跑长任务之后:凌晨 2 点的同步任务做到第 47 分钟,合作方 API token 过期;浏览器 runner 正准备提交表单,SSO session 被统一轮转;某个 MCP 代理因为短期证书失效,把整条执行链卡在半路。

这时最危险的反应往往是“先让它续上”。如果系统为了追求恢复率,把 refresh token、长期密钥或者人工 re-auth 流程直接暴露给模型,问题并没有被解决,只是从可恢复故障变成了权限边界被打穿的事故。

真正要设计的不是“401 后再试一次”,而是凭证生命周期和 agent 生命周期如何对齐。否则你会发现:模型确实还在工作,但系统已经不知道它现在拿着的到底是旧权限、新权限,还是一个本不该出现在当前租户上下文里的 capability。

建议配合 AI agent 凭证委托与 Token Vault ProxyAI agent Policy Engine 规则分层AI agent 多执行环境路由AI agent Harness 崩溃恢复与 Wake 流程 一起看。

先给结论:不是所有凭证都该“自动续”

凭证类型常见寿命能否自动续签系统应该怎么做
短期访问 token分钟到小时可以,但必须在 proxy/vault 内完成刷新 capability 句柄,不把 token 明文交给 agent
人工登录态 / SSO session小时到天通常不能无感自动续进入 needs_reauth,暂停高风险动作并触发人工接力
合作方 API key天到月不该由 agent 自行续用版本化 secret 引用替换,不让模型接触长期秘钥
临时签发的 scoped credential很短可以重新签发旧 scope 失效后,必须重新过 policy 和租户校验

如果系统一律把“过期”看成“再试一次”,它迟早会在权限升级、租户串线或副作用重放上出事故。

401 不是设计,过期状态必须先被结构化

多数恢复失败不是出在续签本身,而是系统只看到了 HTTP 401,却没看见 401 背后的语义差别:

  • 是 access token 到期,还是 refresh credential 本身失效
  • 是租户切换导致 scope 变了,还是用户撤销了授权
  • 是 runner 本地缓存了旧 token,还是 vault 已经完成轮转但句柄没更新

更稳的做法,是把“凭证异常”抬成显式状态,而不是散落在工具日志里:

{
  "runId": "run_2981",
  "tool": "partner.crm.sync",
  "authState": {
    "status": "expired",
    "credentialHandle": "cred_partner_crm_v12",
    "scopeVersion": "tenant-a.standard",
    "recoverability": "requires_reauth",
    "sideEffectGate": "freeze"
  }
}

一旦 recoverability 被结构化,调度器、proxy、人工 review 和用户界面才能说同一种话。否则前端只会显示“失败,请重试”,而后台根本不知道这次重试是不是已经越过了原本的权限边界。

续签动作应该发生在系统里,不应该发生在 prompt 里

真正生产可用的流程里,agent 拿到的应该是 capability,而不是裸 token。也就是说,模型最多知道“我现在可以发起一次 Git push”或“我有权限读取 CRM 某个范围的数据”,但不知道那串真正能完成动作的秘密是什么。

这件事听起来像安全洁癖,实际上是恢复能力的前提。因为只有这样,secret rotation 才能在不惊动模型的情况下完成:

  • vault 更新长期密钥版本
  • proxy 把旧 capability handle 映射到新 token
  • 旧 session 收到 credential_version_superseded 事件
  • 后续工具调用重新以新 handle 执行

如果 token 是直接进了模型上下文,rotation 一旦发生,系统只能在三种坏选择里挑一种:继续用旧 token 硬撞、让模型向用户索要新 token,或者把整个 run 作废重来。

真正难的是“半路过期”,不是“启动前没授权”

启动前缺权限,至少还容易处理。半路过期才会把系统逼到角落里,因为这时 run 往往已经积累了上下文、草稿、中间结果,甚至拿到了准备提交的副作用。

设计上至少要分清三层:

  1. 可继续读取的动作:例如本地草稿整理、已有证据排序、低风险分析。
  2. 必须冻结的动作:例如发邮件、写外部数据库、执行 Git push、提交工单。
  3. 必须等待人工接力的动作:例如 scope 升级、组织级授权、涉及账单或客户数据的跨系统写入。

也就是说,credential expiry recovery 不是简单的 yes/no,而是一种有边界的降级。系统应该允许 run 进入 degraded_read_only,而不是一刀切让整条任务死掉;但它也不能为了“尽量不中断”就让所有副作用继续执行。

Re-auth handoff 要像任务移交,不要像报错弹窗

很多产品在 token 过期后只会弹一句“请重新登录”。对单页应用还勉强说得过去,对长任务 agent 来说几乎没有帮助。因为用户真正关心的不是“我是不是掉线了”,而是:

  • 当前任务做到哪一步了
  • 哪些结果已经安全保留
  • 哪些动作因为权限问题被暂停
  • 重新授权后系统会从哪一层继续,而不是全部重做

所以更好的 re-auth handoff 应该是一份结构化移交单,而不是一个无上下文的按钮。至少要包含:

  • 当前 run 和 session 标识
  • 已完成步骤与已冻结副作用
  • 需要补的权限范围
  • 恢复窗口,例如 30 分钟内恢复可原位继续,超时后转人工复核

这也是为什么 credential recovery 不该被藏在某个 SDK 的自动重试里。只要用户和操作员看不见 handoff envelope,他们就无法判断这次“重新授权”到底是在继续任务,还是在重跑任务。

一个常见事故:统一轮转做对了,系统恢复仍然做错了

一支团队把企业客户的第三方工单 token 统一接到了 vault,安全上看是进步。但上线后第一周,夜间 agent 仍出现大面积失败。原因不是 vault 没更新,而是 runner 在任务启动时把 capability 解析成了本地缓存 token,后续 50 分钟都没有再询问 proxy。结果是:

  • vault 中 secret 已成功轮转
  • proxy 已经知道新的 credential version
  • 正在运行的旧 session 还在持续使用旧 token
  • 系统自动重试 3 次后触发合作方风控,整个租户被临时封禁

最后修复不是“把重试次数调低”,而是把 capability 解析改成分层:

  • 本地只缓存极短期 access token
  • 每次进入高风险工具调用前重新做一次 handle resolve
  • session log 记录 credential_version_seen
  • 一旦发现 superseded,直接冻结写操作并要求重新确认

这个例子很典型。很多团队以为 secret rotation 做完就结束了,实际上 rotation 只解决了“秘钥如何替换”,没有解决“正在运行的任务如何意识到世界已经变了”。

如果你现在要先补一层,优先补这三件事

第一,把 credential error 从原始报错里抬出来,变成调度器和前端都能理解的状态。

第二,让所有续签和轮转动作都发生在 vault/proxy 侧,agent 只拿 capability,不拿裸 secret。

第三,给长任务设计 re-auth handoff,把“权限恢复”做成一次可继续的任务移交,而不是一次模糊报错。

系统真正要守住的,不是某次 token 过期后还能不能马上跑通,而是权限生命周期是否仍由系统控制。如果凭证一过期,你就只能把秘密暴露给模型或者让用户手工补洞,那说明这套 agent 还没真的进入生产级。

延伸阅读: