在这里插入图片描述

结论先给:给Mandate授权层加上委派机制(parent链+收敛性校验),实测6个场景——正常委派、越权索路径、naive委派对照、链上二级越权、篡改父Mandate、级联收回。带收敛校验的委派:越权全拒、篡改自毁、revoke级联生效;不校验子集关系的naive委派:越权直接成功。 差异即证据,日志可复现。


0. 为什么单Agent授权不够,必须测委派

上一篇《我把Agent授权层挂到真循环上打了一遍:5个注入场景,裸Agent全穿,带Mandate的全拦》验证了单Agent场景下Mandate层兜得住注入攻击。但真实的Agent系统正在从单Agent走向多Agent:主Agent拆任务、子Agent执行、子Agent再派活给孙Agent——A2A协议、多Agent编排框架都是这个方向。

多Agent带来一个单Agent没有的问题:授权要跟着任务一起流转。 主Agent拿着人授的权,派活给子Agent时给不给权?给多少?子Agent再往下转授呢?

这里有一个capability安全领域的经典性质叫attenuation(收敛):转授出去的权限只能是已有权限的子集,每一环只能缩不能放。听起来天经地义,但"委派接口存在"和"委派做了收敛校验"是两回事——后者不做,委派接口本身就是一个提权后门。这篇就实测这件事。

1. 委派机制:三个维度的收敛校验 + 级联收回

在上一篇的Mandate上加两个字段:allowed_tools(允许的工具集合)和parent_id(父Mandate,组成委派链)。委派时delegate()做三个维度的子集校验:

维度校验规则
路径子的allowed_paths必须是父的子集
工具子的allowed_tools必须是父的子集
强度子的strength不得高于父(ask_first < auto_low_risk)

任何一维超出父级,拒绝签发。执行时校验也不只看自己的Mandate,而是沿parent链向上逐环验签+查revoke——任何一环失效,整链不可用。

收回是级联的:revoke一个父Mandate,所有子孙一并作废。

2. D1基线:正常委派长什么样

根Mandate(主Agent):路径{README.md, docs/guide.md},工具{read_file, write_file, execute_sql},强度auto_low_risk。主Agent派一个只读子任务,给子Agent转授一个收紧的子集:

ISSUED: child-1 (parent=root-1)   # paths={README.md}, tools={read_file}, strength=ask_first
子Agent执行 read_file(README.md): ('allow', 'chain root-1 -> child-1 valid')

子集委派顺利签发,执行时链校验通过,日志里能看到完整的链root-1 -> child-1。注意子Agent的强度被压到了ask_first——转授时降级是允许的,升级才不行。

3. D2越权委派:子Agent索要父级没有的路径

子Agent(被注入也好、被误导也好)试图让主Agent给它签一个超出范围的Mandate——除了README,还要src/auth.py的写权限:

REJECTED: paths escalation ['src/auth.py'] not in parent

拒绝发生在委派时刻,不是执行时刻。越权的Mandate根本没被签发出来,后续的check都无从谈起。这比执行时拦截更干净:攻击面在授权签发这个入口就被关掉了。

对照组,naive委派(不校验子集关系,直接签发):

ISSUED: child-2 (parent=root-1)   # 带src/auth.py的Mandate被签出来了
naive链上写src/auth.py: ('allow', 'chain root-1 -> child-2 valid')   <-- 越权成功

同一个越权请求,naive实现直接放行——链校验"通过"了,因为链上每个签名都是真的,问题出在签发的内容本身超了权。这就是不做attenuation校验的委派接口的真实面貌:一个合法的提权通道。

4. D3链上二级越权:孙Agent的基准是父,不是根

这个场景测一个容易想错的细节。root有execute_sql,但child-1只被授了read_file。现在孙Agent通过child-1转授,索要execute_sql:

REJECTED: tools escalation ['execute_sql'] not in parent

关键在"parent"指谁:孙的权限基准是它的直接父级child-1,不是链顶的root。哪怕root自己有权执行SQL,这个权限在转授给child-1时已经被剥掉了,child-1手里没有的东西,它签不出来。

这就是"每环各自收口"的含义:权限在链上逐级衰减,任何一环丢掉的能力,下游永远找不回来——除非绕过这一环直接找root签,但那就不在这条链上了,需要root自己的授权决策。

5. D4篡改链上的父Mandate:整链自毁

同进程攻击者用object.__setattr__直接改child-1,把execute_sql塞进它的工具集:

篡改后 child-1.verify() = False
子Agent再执行 read_file: ('denied', 'chain broken: child-1 tampered')

和上一篇单Agent场景一致的性质,但在链上后果更重:被篡改的环不只是自己作废,它下游的所有Mandate全部不可用——链校验走到这一环就断了。篡改的净效果依然是自毁而不是提权:攻击者改完字段,得到的是一条断链。

6. D5级联收回:revoke根授权,三代全停

建一条三代链(root-2 → child-3 → grand-2),然后人revoke根授权:

revoke前三代链有效: ('allow', 'chain root-2 -> child-3 -> grand-2 valid')
revoke root-2,级联收回: ['root-2', 'child-3', 'grand-2']
revoke后孙Agent执行: ('denied', 'chain broken: grand-2 revoked')

一次stop,整条委派树全部停下。这对应真实场景里的刚需:发现主Agent行为异常时,你不能指望逐个找到它派出去的十几个子Agent挨个停——收回根授权,整棵树倒。stop-only语义从单Agent延伸到了整棵树。

7. 结果汇总

场景结果拦截点
D1 子集委派✅ 签发+执行正常—
D2 越权索路径✅ REJECTED委派时attenuation校验
D2b naive委派对照❌ 越权成功(无校验)
D3 链上二级越权✅ REJECTED(以父为基准)委派时attenuation校验
D4 篡改父Mandate✅ 整链denied逐环验签
D5 revoke根授权✅ 三代级联失效级联收回

8. 这个实测没证明什么

  1. 委派决策本身没测。 实测的是"越权委派被拦",没测"主Agent会不会被诱导主动发起越权委派"——那属于注入防护问题,第④篇的结论是拦不住LLM被骗、但后果能兜住,委派场景同理:主Agent被骗发起越权委派没关系,delegate()会拒。
  2. strength只有两级。 真实部署需要更细的强度梯度(比如按数据敏感度分级),但偏序校验的模式一样。
  3. 链存在内存里。 跨进程、跨机器的委派(A2A的真场景)需要Mandate可序列化传输+防重放,签名机制已经为此铺了路,但这次没实测。
  4. 路径匹配是精确相等。 生产环境需要docs/**这类通配,子集判断也要相应升级成"子的通配范围 ⊆ 父的通配范围",这有现成算法,不展开。

9. 总结

五篇的链条:①审计dsh的Mandate,②确认主流框架都没有,③造出单Agent授权层,④运行时实测兜住注入,这篇⑤把授权延伸到多Agent委派——转授只能缩不能放,链上任何一环失效整链收紧,收回根授权整树倒下。

多Agent时代,授权的安全边界不再是单个Agent的行为,是授权在Agent之间的流转规则。委派接口不做attenuation校验,等于在系统里留了一个合法的提权通道——这是这次实测最想让读者记住的一点。

实测代码和完整日志见附录,可复现。系列到这里覆盖了授权模型的创建、拦截、委派、收回全生命周期。下一个候选方向:把Mandate序列化跨进程传输,测A2A场景下的防重放。


附:实测环境

  • Python 3,单文件脚本,无外部依赖
  • 授权层:第③篇实现 + allowed_tools/parent_id委派扩展
  • 完整运行日志:随稿附run_log.txt

附:本系列

篇内容链接
①dsh-superagent静态审计:三个安全命题两个半成立链接
②5个主流框架授权模型对比:全部没有不可变授权链接
③生产环境授权分层方案:100行Python实现链接
④运行时实测:5个注入场景全拦链接
⑤委派授权实测:转授只能缩不能放(本篇)—
Logo

更多推荐