发现与框架问题
资产调查中反复出现的一个结果是,大多数已部署的 agent 什么都没在做。Arrakis 在其对 agent 集群的分析中报告称,在被调查的资产中,大多数 agent 处于休眠状态,却仍持有凭据以及与下游系统的活跃连接。数字本身不如问题的形态重要:资产的活跃面与授权面已经分道扬镳。
这种背离是结构性的,而非偶然的。休眠是一种使用状态,由记录调用的任何系统观察到——API 网关、trace 存储、提供商的用量仪表盘。授权是另一种独立状态,由身份提供商、密钥管理器、数据库的授权表、SaaS 供应商的 OAuth 记录持有。没有任何机制强制两者保持一致。一个 agent 可以不再出现在遥测中,而它的 token 仍然有效数月,且没有任何单一系统处于能够察觉这一错配的位置。
安全审查继承了这种割裂。惯常的问题——这个 agent 是否活跃?——由可观测性栈来回答,而它是错误的证人。真正关键的问题是 agent 还能触及什么,这由授权图来回答。一个没有流量、却持有对生产 CRM 的写作用域 token 的 agent 并未退役。它是一个无人值守的主体:一组常设权限,没有操作者监视它,没有进程需要打补丁,也没有任何所有者会在它被使用时察觉。
为什么 agent 会休眠却不失去访问权
三种机制解释了大部分漂移,而三者都是普通的工程实践,而非疏忽。
第一种是一次性供给。Agent 的引导程序往往是原型阶段写的一次性脚本:它铸造一个 token,写入环境文件或密钥存储,然后退出。该脚本没有记录所有者的概念,也没有过期的概念,因为在编写它的时候,agent 被预期是临时的。当替代方案是把续期流程接入一个可能根本不会持续运行的系统时,长期有效的 token 就是阻力最小的路径。

第二种是以暂停代替删除。删除一个 agent 需要知道有什么依赖它,而这种知识很少被写下来。被另一个 agent 引用的工具定义、一个 webhook 目标、一个定时任务、一个测试夹具——这些中的任何一个都可能在一个主体被移除时静默失败。暂停是可逆且廉价的;删除是不可逆的,且需要一份没人拥有的清单。理性的操作者选择暂停,而暂停在从未被决定的情况下变成了永久。
第三种是对方惰性。连接之所以持续存在,是因为另一端的系统没有收到 agent 已停止的信号。一个 SaaS 集成会一直持有 refresh token,直到它被撤销。一个内部服务会一直持有服务账号授权,直到有人编辑 ACL。Agent 自身的沉默不是这些系统中任何一个会收到的消息。因此,除了那个停止调用的当事方之外,从其他每一方的视角看,休眠都是不可见的。
回收时间表
纠正之道是把凭据过期视为一个按计划发生的生命周期事件,与签发具有同等地位,并使该时间表可强制执行,而非仅供参考。
签发时的最大生存期与记录所有者。 为 agent 铸造的每一个凭据都携带两个强制属性:硬性最大生存期,以及一个具名所有者(一个团队、一个轮值、一个 on-call 别名——而不是某个人的账户)。续期是由该所有者执行的显式动作,而不是静默地无限延长凭据的自动刷新。短最大生存期加上廉价的续期颠倒了默认值:除非有人明确地让它保持存活,否则凭据就会失效,这把疏忽转化为过期,而不是转化为常设访问。
基于最后成功调用遥测的休眠检测。 进程存活是错误的信号。一个正在运行但持续报错的容器,或一个触发到已撤销权限上的调度器,看起来是活的,功能上却是死的。真正重要的信号是最后一次成功认证调用的时间戳,按凭据而非按进程聚合。回收任务读取的就是这个时间戳。
分阶段回收,每个阶段都记录日志。 撤销不是一个单一动作,把它当作单一动作正是退役会破坏依赖方的原因。时间表分阶段运行,每一阶段以上一阶段为前提,且每一阶段都被记录:
- 撤销写作用域。 主体保留读取访问,因此任何只读的消费方继续正常工作,而任何写入的消费方会在有人能够响应的窗口期内响亮且可见地失败。
- 撤销读取作用域。 在定义的静默期且无事故之后,主体完全失去对数据的访问。挺过了第一阶段的依赖方此时会浮现出来。
- 删除主体。 身份、其组成员关系、其密钥条目及其对方授权被移除,删除操作被写入与先前阶段相同的审计日志。

控制平面将要评估的策略对象草图:
principal: agent.reporting.summariser
owner: team-data-platform
issued: 2025-02-11
max_lifetime: 90d
renewal: explicit
last_successful_call: 2025-03-04
stages:
- revoke_write: on_dormant(30d)
- revoke_read: on_dormant(60d)
- delete: on_dormant(90d)
log: audit.credentials
诚实的权衡是运维噪音。分阶段撤销按设计会产生失败,其中一些失败会带来不便:一份季度报告、一个很少使用的集成、一个演示环境。替代方案不是更少的失败,而是更晚、更安静的失败——一个被他人使用的无人值守主体,在事后才被发现。该时间表用当下可预测、可归因的故障,换取日后不可预测、不可归因的访问。采用它的团队应当预期在第一个周期里为那些最终证明很重要的 agent 延长生存期,而这本身就是他们此前没有的清单。
这对集群意味着什么
清单不再是进程列表,而变成了授权图。关于任何 agent 的有用问题不是它是否在运行,而是它能以哪些主体身份行动、这些主体能触及哪些资源,以及每条边归谁所有。这样枚举下来,休眠但仍连接的 agent 是资产中最廉价的可移除攻击面——无需迁移、无需与活跃消费方协调、无需变更窗口——同时也是最不可能被注意到的,因为正常运营中没有任何东西会触及它们。
回收策略也应当与签发处于同一个控制平面,而不是放在季度审计里。审计是抽样;控制平面是强制执行。如果铸造凭据的系统就是使凭据过期的系统,那么休眠就成为平台按定时器解决的状态,而不是审查者提出、团队推迟的发现。审计的角色转变为检查时间表是否运行、例外是否合理,这比重建一个 agent 资产能触及什么要小得多、也可控得多。
缺失的那个生命周期阶段
Agent 生命周期模型往往从设计经过部署到退役,而退役被当作末尾的清理步骤。Arrakis 的发现表明该模型缺失了一个阶段:回收,它从 agent 最后一次成功调用开始远去的那一刻开始,到其主体被删除时结束。这个阶段有自己的遥测、自己的所有者、自己的时间表和自己的审计轨迹。一个停止工作后仍持有密钥的 agent 并未退役——它只是在攻击者唯一在意的意义上仍在运行。集群卫生就是在别人之前、按定时器察觉的实践。
