1. 从一条审计日志说起:智能体到底“继承”了什么
第一次看到审计日志里赫然写着自己的名字,而操作却明显不是自己亲手做的,那种感觉相当微妙。日志时间戳、操作对象、变更内容都对得上,唯独“操作者”这一栏,挂的是我的账号。顺着链路往回查,才发现真正发起这次调用的是一个被我授权过的智能体——它拿着我的凭证,替我把事情办了,然后以我的名义留下了记录。
这件事让我意识到一个被很多人忽略的问题:当你把凭证交给一个智能体时,它继承的远不止一把“钥匙”。它继承的是你的身份、你的权限边界、你的操作习惯,甚至是你在这个系统里积累的信任关系。而审计日志,只是这套继承关系最直观的投影。
这篇内容想聊的就是这个。它适合正在做智能体集成、自动化编排、权限委托的开发者,也适合负责审计合规、安全评审的从业者。核心要回答三件事:凭证交接时到底传递了哪些隐性资产、审计日志为什么会“认错人”、以及怎么设计一套让责任可追溯又不失控的授权方案。关键词就三个:凭证委托、权限继承、审计溯源。
我先把结论摆出来:绝大多数“智能体越权”事故,根因不在智能体本身,而在于授权时只考虑了“它能做什么”,没考虑“它做完之后,账记在谁头上”。这两件事在传统系统里往往是同一件事,但在智能体场景下被彻底拆开了。
2. 凭证交接的三种常见姿势,以及它们各自埋的雷
2.1 直接复用人类账号的长期凭证
最省事的做法,就是把某个服务账号或者干脆是个人账号的长期密钥,直接配置到智能体的运行环境里。智能体拿着这套凭证去调用下游接口,下游看到的是“某某账号在操作”,一切照常。
这种做法的问题在于,凭证的生命周期和智能体的生命周期完全脱钩。人离职了、密码轮换了、权限收紧了,智能体那边可能还揣着旧凭证在跑。更麻烦的是,一旦智能体被滥用或者出现逻辑缺陷,你很难在日志里区分“这是人干的”还是“这是智能体干的”——因为两者用的是同一个身份。
我见过一个典型的翻车场景:某团队给一个定时任务智能体配了运维账号的密钥,结果智能体因为一个边界条件判断错误,在凌晨批量修改了配置。审计日志里全是运维账号的操作记录,值班同学排查了半天,以为是有人误操作,最后才发现是那个“安静运行了三个月”的智能体。
2.2 申请独立的服务身份,但权限照抄人类
稍微规范一点的做法,是给智能体单独申请一个服务身份,比如一个独立的系统账号或者应用注册。这样至少在日志里能区分出“这是智能体”。
但很多人走到这一步就停了,权限配置直接照抄对应人类角色的权限。这就带来第二个雷:智能体继承的权限范围,往往比它实际需要的宽得多。人类操作时会根据上下文判断“这个操作该不该做”,而智能体只会忠实地执行它被赋予的能力。你给它开了删除权限,它就会在某个条件满足时真的去删。
这里的关键认知是:人类权限设计里默认包含了“人会自我约束”这个隐含前提,而智能体没有这个前提。所以照抄人类权限,等于把一个没有判断力的执行者放进了为有判断力的人设计的环境里。
2.3 动态签发短期凭证,按需授权
相对稳妥的做法,是让智能体在每次执行任务前,通过一个受控的流程动态申请短期凭证,任务结束凭证即失效。凭证的权限范围严格限定在这次任务需要的操作上。
这种做法把“身份”和“权限”都做成了临时的、可追溯的。审计日志里记录的不再是一个长期存在的账号,而是一次具体的授权会话。出问题时,你能精确知道是哪次任务、哪个智能体、在什么授权下做的。
代价是复杂度上去了。你需要一个凭证签发服务、一套权限策略引擎、以及智能体侧的凭证刷新逻辑。但对于任何涉及敏感操作的场景,这个代价是值得的。
| 授权方式 | 日志可区分性 | 权限收敛度 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 复用人类长期凭证 | 差 | 低 | 低 | 内部只读、低风险任务 |
| 独立身份+照抄权限 | 中 | 低 | 中 | 中等风险、需区分来源 |
| 动态短期凭证 | 好 | 高 | 高 | 敏感操作、合规要求高 |
提示:选择哪种方式,不取决于技术偏好,而取决于“这次操作如果出错,我需要多快定位到责任主体”。定位要求越高,越应该往动态短期凭证靠。
3. 审计日志为什么会“认错人”:身份传递的链路拆解
3.1 一次调用的身份流转全过程
要理解日志为什么认错人,得先看清楚一次智能体调用里,身份是怎么流转的。假设智能体A要调用服务B完成一个操作,链路大致是这样的:
- 智能体A从配置或凭证服务拿到一个身份标识(可能是密钥、令牌、证书)。
- A用这个身份向B发起请求,请求头里带着这个身份。
- B验证身份通过,执行操作,并把“当前身份”写进审计日志。
- 日志落库,操作者字段记录的是那个身份标识。
问题出在第3步。B看到的只是“某个身份发来了请求”,它无法天然知道这个身份背后是人在操作还是智能体在操作。如果这个身份本身就是人类账号,那日志自然就记成了那个人的名字。
3.2 “代表谁”和“以谁的名义”是两回事
这里有个容易被混淆的概念:委托身份和执行身份。
- 委托身份:真正发起意图的主体,比如“张三让智能体去查一下数据”。
- 执行身份:实际携带凭证发起调用的主体,比如“智能体A用服务账号S的令牌调用了接口”。
在理想情况下,审计日志应该同时记录这两者。但现实中,大多数系统的审计日志只记录执行身份。于是张三的意图、智能体的执行、服务账号的凭证,三者被压缩成了一个字段,责任链条就此断裂。
我踩过的一个坑是:早期做自动化巡检时,用的是共享的服务账号,结果某次巡检误报了告警,排查时完全分不清是哪个巡检任务触发的。后来改成每个巡检任务用独立身份,并在请求头里额外带上“委托来源”字段,日志才变得可读。
3.3 日志字段设计的常见缺陷
很多系统的审计日志字段是这么设计的:操作者、操作时间、操作对象、操作结果。看起来够用,但在智能体场景下缺了关键信息:
- 缺少委托链:谁授权了这个智能体做这件事。
- 缺少凭证类型:这次操作用的是长期凭证还是临时凭证。
- 缺少会话标识:把同一次任务里的多个操作关联起来。
- 缺少智能体标识:区分是哪个智能体实例发起的。
补上这几个字段,日志才能从“记了一笔账”变成“还原了一条责任链”。这不是锦上添花,而是智能体规模化之后的基本要求。
4. 权限继承的边界:智能体不该继承的那些东西
4.1 隐式继承:那些没写在权限表里的东西
权限表里写的是显式权限,比如“能读这个库”“能写那个表”。但智能体实际继承的,还有一堆隐式的东西:
- 网络位置信任:如果智能体跑在内网,它天然获得了内网服务的信任。
- 调用频率豁免:人类账号可能有限流,但服务身份往往被放进白名单。
- 数据可见范围:某些系统对内部身份放宽了数据脱敏规则。
- 审批绕过:部分高危操作对人类有二次确认,对服务身份直接放行。
这些隐式继承在授权时几乎不会被提及,但它们对实际风险的影响,往往比显式权限更大。我见过一个案例:智能体本身只有读权限,但因为跑在受信任网段,它调用的下游服务默认信任该网段,结果间接获得了写能力。
4.2 权限膨胀:从“够用”到“失控”的滑坡
权限膨胀通常不是一次发生的,而是逐步累积的。今天智能体报了个权限不足,运维顺手加一条;明天又报一个,再加一条。几个月后回头看,这个智能体的权限已经远超它最初的设计。
控制膨胀的关键是定期做权限审计,而且是按智能体维度做,不是按账号维度做。具体做法:
- 导出每个智能体近30天的实际调用记录。
- 提取实际使用到的权限集合。
- 和授予的权限集合做差集。
- 对差集里的权限逐条确认:是保留还是回收。
这个流程我建议至少每季度跑一次。跑第一次的时候,你大概率会发现相当比例的权限从未被使用过。
4.3 最小权限在智能体场景下的重新定义
传统的最小权限原则是“只授予完成工作所需的最小权限”。在智能体场景下,这个原则需要加两个限定:
- 按任务而非按角色授权:不是“这个智能体属于运维角色所以给运维权限”,而是“这个智能体这次任务需要读A表,就只给读A表”。
- 权限随任务结束而回收:任务完成后,权限不应该继续挂在智能体身上等着下次用。
这两条加进去,最小权限才真正落地。否则“最小”只是相对于角色而言的最小,相对于任务仍然是过度的。
5. 让责任可追溯:一套可落地的授权与审计方案
5.1 凭证签发:把“谁授权”写进凭证本身
动态凭证的核心设计思路是:凭证里不仅包含“能做什么”,还包含“谁授权的”“为哪个任务授权的”。具体实现上,可以在令牌的声明部分加入几个自定义字段:
{ "sub": "agent-inspection-01", "act": { "sub": "user-zhangsan", "task": "daily-inspection-20240501" }, "scope": "read:metrics read:logs", "exp": 1714560000 }这里的act字段表示“代表谁行动”,是委托链的关键。下游服务在写审计日志时,把sub和act.sub都记下来,责任链就完整了。这个设计参考了常见的委托授权模式,落地成本不高,但效果立竿见影。
5.2 审计日志:从单点记录到链路还原
有了带委托信息的凭证,审计日志的字段也要相应调整。建议至少包含:
| 字段 | 含义 | 示例 |
|---|---|---|
| executor | 执行身份 | agent-inspection-01 |
| delegator | 委托身份 | user-zhangsan |
| task_id | 任务标识 | daily-inspection-20240501 |
| credential_type | 凭证类型 | short-lived |
| session_id | 会话标识 | sess-abc123 |
| action | 操作内容 | read:metrics |
| result | 操作结果 | success |
有了这些字段,排查问题时可以按delegator查“张三授权过的所有操作”,也可以按task_id查“这次任务的全部动作”,还可以按executor查“这个智能体的历史行为”。三个维度交叉,基本能还原出完整的责任链。
5.3 异常检测:当智能体的行为偏离预期
审计日志不只是事后排查用的,还可以做实时异常检测。几个实用的检测规则:
- 权限突增:某个智能体突然调用了从未用过的权限,触发告警。
- 频率异常:调用频率显著高于历史基线,可能是逻辑缺陷导致的重试风暴。
- 时间异常:在非预期时间段发起操作,比如一个只该在白天跑的巡检任务在凌晨活动。
- 委托链断裂:请求里缺少委托信息,说明有智能体在用旧方式调用。
这些规则不需要多复杂的模型,基于统计基线就能跑起来。关键是先把日志字段补齐,否则连检测的数据基础都没有。
注意:异常检测的告警要分级。权限突增这种直接告警,频率异常可以先观察再告警,避免噪音淹没真正的问题。
6. 实操中踩过的坑与几条硬经验
6.1 凭证轮换时智能体集体“掉线”
做凭证轮换时最容易忽略的是智能体侧的刷新逻辑。人类用户密码过期会收到提示,但智能体不会。如果智能体用的是长期凭证,轮换的那一刻它就直接失效了。
我的做法是:给智能体用的凭证一律设置自动刷新,并且在刷新失败时要有明确的降级和告警。具体来说,智能体启动时拉取凭证,运行中按过期时间提前刷新,刷新失败则进入只读模式并上报。这样即使轮换出问题,也不会导致智能体带着错误凭证反复重试,把下游打挂。
6.2 日志里“操作者”字段被下游覆盖
有一次排查问题,发现上游日志里明明记的是智能体身份,到了下游服务却变成了另一个人名。查了半天,发现是下游服务在写日志时,用了一个“当前登录用户”的上下文变量,而这个变量被某个中间件根据请求头里的另一个字段覆盖了。
这个坑的教训是:审计日志的字段来源要单一且明确。不要用“当前用户”这种模糊的上下文,要用请求里显式传递的身份字段。中间件也不应该擅自改写身份信息。
6.3 临时凭证的时钟偏移问题
动态签发的短期凭证,有效期往往很短,几分钟到几小时。如果签发方和验证方的时钟有偏移,就会出现“刚签发就过期”或者“已过期还能用”的诡异现象。我遇到过验证方时钟慢了30秒,导致一批本该过期的凭证多活了一会儿。
解决办法很简单:所有参与凭证签发和验证的节点,强制走统一的时间同步,并且在凭证验证时留出合理的时钟偏移容忍窗口。这个窗口不要设太大,几分钟足够,设大了等于变相延长了凭证有效期。
6.4 智能体身份被硬编码进镜像
容器化部署时,有人图省事把智能体的身份凭证直接打进镜像。镜像一旦被拉取,凭证就泄露了。而且轮换凭证时,得重新构建镜像,运维成本极高。
正确做法是凭证通过运行时注入,比如挂载密钥文件、通过环境变量传入、或者从凭证服务动态获取。镜像里只保留获取凭证的逻辑,不保留凭证本身。这条经验看起来基础,但实际项目里违反的情况相当普遍。
7. 写在最后的一点个人体会
把凭证交给智能体这件事,本质上是在做一次信任的传递。你信任这个智能体会按你的意图行事,所以你把身份借给它。但信任传递出去之后,责任不能跟着一起模糊掉。审计日志里那个名字,不应该是一个让人困惑的谜题,而应该是一条清晰的责任链的起点。
我现在做任何智能体集成,第一件事就是问:这次操作如果出了问题,我能不能在日志里一眼看出是谁授权的、哪个智能体执行的、用了什么凭证。如果答案是否定的,那这套授权方案就得重新设计。这个习惯帮我省了很多事后排查的时间,也让我在安全评审时少了很多解释成本。
智能体本身没有恶意,它只是忠实地执行被赋予的能力。真正需要被认真对待的,是我们赋予它能力时的那套机制。凭证、权限、审计,这三样东西设计好了,智能体才是一个可控的工具;设计不好,它就是一个带着你名字到处签字的影子。