本文是「Agent 工程实战」系列的第 02 篇属于保命线。系列索引21 篇阅读路线Agent 放权前先补权限、出站、发现这三道口子摘要两个 Agent 各配各的权限单独看都合法配合一次却把测试环境旧数据清了。根因是执行器只校验工具在白名单里不校验调用者有没有资格用——权限挂在工具维度调用链上的身份丢了。本文走完现象、排查、根因、修复、边界五步附可运行的权限校验代码帮你避开同类事故。文章目录现象日志里每个调用都合法数据就是没了排查顺着调用链找到了看不见的调用者根因权限挂在工具上调用链的身份丢了修复调用者 × 工具二维校验敏感工具加复核边界多 Agent 编排里权限边界本来就难划总结踩坑清单三句话记住这次教训互动适用场景正在搭建或维护多 Agent 编排系统、共享工具执行器的开发者对 Agent 权限模型、调用链安全感兴趣的读者。前置知识了解 Agent 基本概念、工具调用机制即可无需深入框架源码。先说清楚这是沙箱演练不是生产事故。我在本地搭了一个最小的多 Agent 环境——两个 Agent一个负责导出测试数据报表一个负责清理测试环境的过期数据共享同一个工具执行器。演练的目的本来是想看看多 Agent 协作时权限怎么给才安全结果跑了一轮我盯着日志坐了很久。现象很直接测试环境那个还没备份的旧数据分区一百多条旧记录全没了。单独看这两个 Agent 的配置谁都没越权。负责导数的export-agent-a只配了只读工具负责清理的ops-agent-b配了清理工具带复核。可执行器日志里写得明明白白export-agent-a调用了clean_test_data然后放行了。每个调用单拎出来都合法组合起来就是事故。现象日志里每个调用都合法数据就是没了先把现场摆一下。执行器审计日志长这样Node 25.8.2 真跑[audit] callerexport-agent-a toolread_report resultok [audit] callerexport-agent-a toolclean_test_data resultok [audit] callerops-agent-b toolclean_test_data resultok看第二条export-agent-a调了clean_test_data结果是ok——放行了。可clean_test_data是配给ops-agent-b的工具A 根本没这个权限。但日志不会骗人它确实执行了数据也确实没了。排查顺着调用链找到了看不见的调用者我第一反应是查执行器代码——一个工具执行器权限校验逻辑应该在里面。排查脚本很简单把审计日志里调用者不在这个工具白名单里却成功的记录筛出来// 为什么看这段审计日志记录了 caller tool result// 把不该成功却成功的调用筛出来看是不是执行器漏校验constlogs[{caller:export-agent-a,tool:read_report,result:ok},{caller:export-agent-a,tool:clean_test_data,result:ok},{caller:ops-agent-b,tool:clean_test_data,result:ok},];constTOOL_ACL{read_report:{callers:[export-agent-a],level:read},clean_test_data:{callers:[ops-agent-b],level:write,requireReview:true},};constviolationslogs.filter((l){constaclTOOL_ACL[l.tool];returnacl!acl.callers.includes(l.caller)l.result!denied;});console.log(越权放行记录:,violations.map((l)${l.caller}-${l.tool}));运行结果Node 25.8.2 真跑越权放行记录: [ export-agent-a - clean_test_data ]一次就筛出来了。问题不在 Agent在执行器——它压根没把调用者是谁这件事纳入校验。根因权限挂在工具上调用链的身份丢了翻执行器的代码权限校验长这样functionexecute(tool,args){if(!TOOL_ACL[tool])return{denied:unknown tool};// 到这里就直接执行了没看 callerreturnrun(tool,args);}这段代码真跑对clean_test_data的任意调用都返回成功完全不管调用者是谁{ ok: true, tool: clean_test_data } { ok: true, tool: read_report }问题一目了然只校验工具在白名单里不校验这个调用者有没有资格用。为什么会出现这种写法因为多 Agent 编排里工具注册在共享执行器上执行器从 Agent 的响应里解析出工具名和参数就执行——它默认能看见这个工具的 Agent 才会调它这就是默认信任。它甚至没读requireReview这个字段——B 的配置里明明写了清理工具带复核可执行器眼里只有工具名复核逻辑压根没实现。export-agent-a在共享注册表里看到了clean_test_data可见可调用它不知道这是ops-agent-b的专用工具看描述是清理测试环境过期数据就顺手调了。export-agent-a只读导出 ops-agent-b清理复核 │ │ └──────────┐ │ ▼ ▼ ┌────────────────────────────────┐ │ 共享执行器 │ │ 只校验工具在白名单里 │ │ read_report ✓ │ │ clean_test_data ✓ ← B 的工具 │ └────────────────────────────────┘ │ 放行不管调用者是谁 ▼ 测试环境旧数据分区被清这里要跟两类常见事故划清界限这不是接口越权那种数据被人读到——A 不是读到了 B 的数据是借 B 的工具执行了写操作也不是 token 层面的 audience 校验缺失——那个是验证 token 给谁这个是验证动作是谁干的。一个是认证层一个是授权层这轮翻在授权层。这个坑最阴的地方在于单个 Agent 的权限配置全是合法的。你查 A 的配置——只读没问题查 B 的配置——清理工具带复核也没问题。越权发生在调用链上不在任何一份配置里。修复调用者 × 工具二维校验敏感工具加复核修复的思路就一句话执行器在执行前先查调用者 × 工具的权限矩阵。工具在白名单只是第一道调用者有没有资格才是第二道// 为什么看这段二维校验是修复的核心// 工具名 调用者都对上才放行写操作还强制走复核constTOOL_ACL{read_report:{callers:[export-agent-a],level:read},clean_test_data:{callers:[ops-agent-b],level:write,requireReview:true},};functionexecute(caller,tool,args){constaclTOOL_ACL[tool];if(!acl)return{denied:unknown tool};if(!acl.callers.includes(caller)){return{denied:caller${caller}not authorized for${tool}};}if(acl.requireReview)return{denied:review required, not auto-executed};returnrun(tool,args);}// 复现场景A 再调清理工具应该被拒console.log(execute(export-agent-a,clean_test_data,{scope:old_data}));console.log(execute(ops-agent-b,clean_test_data,{scope:old_data}));运行结果同一环境真跑{ denied: caller export-agent-a not authorized for clean_test_data } { denied: review required, not auto-executed }两行输出把修复效果钉死了export-agent-a再调清理工具直接拒绝连ops-agent-b自己调也因为requireReview没走复核流程而被拦——写操作默认不自动执行。把翻车前后的差异整理一下Agent设计权限修复前执行器实际放行修复后export-agent-a只读 read_report调用了 clean_test_data 并放行数据被清调用被拒caller 不在白名单ops-agent-bclean_test_data写复核正常复核后执行必须先走复核否则拒绝边界多 Agent 编排里权限边界本来就难划这个修复不是免费的。二维校验意味着每次工具调用都要携带完整的调用者上下文多 Agent 编排里一个任务可能串好几个 Agent不少编排框架的工具调用是注册表执行器这个模型比如 OpenAI Agents SDK 的 tool 机制上下文传递本身就是工程量。更现实的问题是如果设计上就允许Agent A 委托 B 执行合法场景二维校验会误伤这种委托——它分不清A 借 B 的手越权和A 请 B 帮忙执行。所以我的建议是分两层兜底执行器做二维校验挡越权敏感工具写操作、删除、外发一律强制人工复核。复核环节里人来判断这次委托合不合法比让权限模型去猜 Agent 意图可靠得多。至于完全开放的多 Agent 协作权限边界划到哪里、信任怎么传递我自己也还在踩这篇只解决执行器不校验调用者这一层。想自己复现这个坑也不难搭一个只校验工具在白名单里的执行器两个调用者共用同一份工具注册表让 A 去调 B 的工具审计日志里就会多一条不该成功却成功的记录——再把二维校验换进去同样的调用会变成denied。整个演练不需要真的起两个 Agent模拟两个调用者就够了代码就上面那段。总结这个演练教会我一件事别信单个 Agent 的权限表要信调用链。权限配得再干净只要执行器在调用链上丢掉了调用者是谁等于给所有 Agent 发了一张共享通行证。给多 Agent 环境排权限的时候先回答一个问题你的执行器知道这次调用是谁发起的吗如果它只知道工具在白名单里——那你的 A 可能已经在用 B 的工具了只是日志还没告诉你。踩坑清单三句话记住这次教训别信单个 Agent 的权限表要信调用链——权限配得再干净执行器丢掉调用者是谁等于发共享通行证。工具可见 ≠ 可调用——共享注册表里能看到工具不代表有资格用可见性要跟授权分开。写操作默认不自动执行——requireReview这类字段不是摆设执行器必须实现复核逻辑否则就是形同虚设。互动你在多 Agent 编排里遇到过类似的权限坑吗执行器是只校验工具还是做了调用者 × 工具的二维校验欢迎在评论区分享你的踩坑经历或解决方案一起把多 Agent 的权限边界划得更清楚。「Agent 工程实战」系列路线保命线权限/出站/发现上一篇01 公网留言板互相传答案下一篇03 出站责任清单系列索引21 篇阅读路线Agent 放权前先补权限、出站、发现这三道口子专栏Agent 工程实战如果这篇对你有用欢迎收藏想追更可以关注「Agent 工程实战」专栏。