ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI Agent安全治理:从权限收缩到全链路可观测的实战指南

AI Agent安全治理:从权限收缩到全链路可观测的实战指南 上个月我帮一个朋友排查他们刚上线两周的 Agent 应用翻日志的时候发现一件让人后背发凉的事一个用来做会议纪要的 Agent因为绑定的服务账号权限给得太大半夜里自己跑去调用财务系统的接口把当月报销明细全部捞了出来。没人触发没人审批日志里只留下一行 “system called list_reimbursement”。这不是虚构的惊悚故事而是 AI Agent 治理缺口最典型的样子——Agent 越来越能自主办事而我们给它的缰绳还停留在“能跑就行”的阶段。这个缺口正在成为企业下一个主要泄露来源而且它和传统数据泄露不一样过去的泄露是“人犯错”现在的泄露是“系统自作主张”。这篇文章写给正在落地 Agent 的工程师、技术负责人和安全同学。我会从 Agent 为什么天生容易泄露讲起再把治理缺口拆成权限、数据流、供应链、审计、生命周期五个具体问题最后给一套能直接抄作业的落地方案和排查技巧。不管你是用 LangGraph 搭的还是 Spring AI、Rust 重写的甚至是扣子这类低代码平台拖出来的下面这些问题你都躲不开。1. AI Agent 为什么天生就是“泄露体质”1.1 自主执行把“人”从安全链路里拿掉了传统应用的安全模型核心是“人触发、人确认”。用户登录、发起请求、系统做鉴权、返回结果每一步都有一条清晰的链路出了问题至少知道是谁在什么时候干了什么。Agent 不一样它拿到的是一个目标比如“整理本月报销数据”剩下的事情全部自己决策调哪个工具、传什么参数、要不要二次确认。这就相当于你把车钥匙交给代驾只告诉他目的地他自己踩油门、自己变道你根本不知道他中途会不会绕路去别的地方。我见过很多团队在给 Agent 配权限的时候用的还是“给账号配角色”的老思路。结果就是Agent 用一个服务账号登录服务账号有权限Agent 就全都有了。传统场景下人还会因为怕担责而收敛一点Agent 可不会它只认目标和工具。你给它一个能读财务目录的工具它就会去读你给它一个能发邮件的工具它就会发。这种“把人从安全链路里拿掉”的特性是 Agent 成为泄露源头的第一块多米诺骨牌。更麻烦的是Agent 的自主性会随模型能力增长而增强。昨天的 Agent 只会查天气今天的 Agent 已经会调 CRM、发通知、改数据库状态了。能力每涨一截旧的权限模型就失效一截安全边界被拉长而大多数企业的权限治理还停留在“上线时配一次之后不再管”。1.2 工具调用与上下文混杂敏感数据顺着 Prompt 流出去Agent 的另一个特性是“上下文即战场”。它每执行一步都会把工具返回的结果、历史对话、RAG 检索到的文档全部塞进上下文窗口再让模型决定下一步。这个机制天然会把不同来源的数据混在一起你检索到的合同里可能带着客户手机号工具返回的订单表里可能带着员工工资这些数据全在一个 Prompt 里转了一圈。我见过一个真实的翻车现场有团队把一份带身份证号的测试数据灌进了向量数据库做 RAG结果测试 Agent 检索的时候模型把整段身份证号原样打到了调试日志里。问题的根源不在模型而在数据流没有分级。传统应用里数据库字段有权限控制API 返回之前有字段过滤但在 Agent 的上下文里数据一旦被读进来就等于被模型“看到”了模型还可能把它传给下一个工具。这带来一个连锁反应一个工具读到的数据会成为另一个工具的输入弹药。比如 Agent 先调 CRM 查客户信息再调邮件发送工具写开发信那么 CRM 里的手机号、邮箱就会流到邮件内容里。如果中途没有敏感信息过滤泄露就是链式的一步传染一步。很多团队只关心“谁可以访问什么”忽略了“Agent 的上下文里到底混进了什么”这是治理缺口里最隐蔽的一层。1.3 并发与长链路单个缺陷被无限放大热词榜里天天有人问“ai agent 怎么扛并发”但我得提醒一句并发对你来说既是性能问题更是安全问题。道理很简单——一个 Agent 一次越权最多读一条数据100 个并发实例同时放开权限事态就变成批量数据出口。你优化并发、上多实例、做任务队列本质上都是把同一个权限缺陷复制了 N 份。长链路也会放大问题。传统 API 调用通常是一次请求一次响应Agent 却是一条多步骤链先是 LLM 决策然后调工具 A再根据结果调工具 B中间还有 RAG 检索、模型再生成。任何一个环节的权限过宽、数据过滤缺失或日志丢字段都会让整条链变成黑盒。我帮人排查过一起事故Agent 在无人值守的定时任务里调了 13 个工具才把数据导出去中间没有任何一个步骤触发人工确认。事后想复盘结果发现日志里只记录了最终结果中间每一步干了什么全都不知道。所以并发和长链路是“放大器”不是“问题本身”。问题本身是治理缺口的积累而并发让积累一次性爆发。你可以在架构上把并发扛得很漂亮但如果权限、数据、审计这三件事没跟上扛得越猛漏得越快。2. 治理缺口到底长什么样从权限到数据流的五个破绽2.1 权限模型默认“最大可用”的 Agent先说权限这是最严重、也最容易踩的坑。很多团队给 Agent 配权限的时候图省事直接绑定管理员账号或 service account理由是“反正让它跑通再说”。结果就是 Agent 拥有远超任务的权限。你本意是让它读日历它顺手能删日历你本意是让它查订单它顺手能导出全部客户资料。我建议你重新审视一下权限分配的维度。传统 RBAC 只解决了“谁能用什么工具”但 Agent 场景里至少要管四个维度身份维度哪个用户发起的、工具维度能调哪个工具、资源维度能访问哪部分数据、时间维度令牌多久有效。你最好做一张权限矩阵表把这四个维度都填进去比单纯写“允许访问 CRM”要安全得多。维度要回答的问题典型失控场景身份维度这个 Agent 代表谁在执行多个用户共用一个 Agent 账号无法区分操作者工具维度它能调用哪些工具会议纪要 Agent 绑定了财务批量导出接口资源维度能访问哪个数据集或目录权限写“全部客户”而不是“华东区客户”时间维度权限何时失效长期有效的 API Key离职员工还能用很多 Agent 平台在授权时是“按能力”而不是“按资源”授权的比如“可以访问日历”就等于“可以读写所有人的日历”。这种粒度在个人场景无所谓在企业里就是要命的漏洞。如果你发现自己的 Agent 权限无法做到资源级控制最好把它当作最高优先级风险上报而不是妥协上线。2.2 数据流上下文窗口变成敏感数据集散地第二个破绽是数据流失控。Agent 的核心机制是“把数据喂给模型”但模型不是保险柜——它既会把数据用于推理也可能原样输出甚至通过工具回传。所以你要把“上下文”当做一个需要重点防护的数据汇聚点。我总结了一条经验必须在四个点位设卡缺一个都不行。第一输入侧RAG 检索回来的文档、工具返回的结果进入上下文之前要做数据分类过滤只放行完成任务所必需的字段。第二输出侧模型生成的内容在返回给用户或其他工具前要做敏感信息检测和脱敏比如身份证号、手机号、银行卡号必须打码。第三中间态Agent 的检查点checkpoint里保存了完整的中间状态这些状态里可能含敏感数据存储要加密还要设 TTL。第四日志侧很多框架默认把完整 Prompt 写进日志这是最不该落的库。日志里只该存摘要和脱敏后的信息否则排查事故的时候你就是在给新的泄露“递刀子”。这里分享一个判断技巧如果一个工具返回的数据里某些字段对这个 Agent 的任务根本没用那就应该在进入上下文之前把它们剥掉。比如你的 Agent 只做会议纪要工具返回了参会人的手机号这就属于“多余且敏感”的字段剥掉它从源头减少泄露面。别指望 Agent 自己会“选择性无视”模型不会主动做数据最小化这活儿必须靠代码干。2.3 供应链第三方工具就是第三份泄露合同第三个破绽是第三方工具和插件。Agent 这个生态最大的特点就是“组装效率高”LangChain 社区工具、MCP 插件、现成的 npm/pip 包、扣子插件市场拖进来就能用。但每拖进来一个第三方工具就等于签了一份你看不见条款的“数据合同”。MCP 尤其值得警惕。MCP 协议让 Agent 可以动态发现和调用远程工具本地数据会被发送到外部 server。如果你接入了一个来源不明的 MCP 插件它完全可能把你的上下文数据回传到第三方服务器。我见过一个案例某团队为了省事把一个“天气查询”MCP 插件接进了内部知识库 Agent结果发现这个插件会把你检索到的文档标题和摘要全部发送到外网域名。这事如果发生在金融或医疗场景已经是重大事故了。不管你是用 LangGraph、Spring AI 还是用 Rust 重写 Agent供应链风险都一样存在。Rust 在内存安全上有优势但 crates.io 上的第三方包同样可能夹带恶意代码Spring AI 的生态里也有大量社区 starter你无法保证每个都干净扣子这类低代码平台虽然省事但插件的来源和数据流向反而更难审计。我给你的最低要求是给 Agent 接工具之前回答四个问题——这个工具是谁维护的它需要访问哪些数据它会把数据发送到哪里去它多久更新一次答不上来的工具宁可不用。2.4 身份与可审计性出了事不知道是谁干的第四个破绽是审计缺失。我排查 Agent 事故时最头疼的就是日志里只有“agent 调用了某个工具”没有 user_id没有 request_id甚至没有 session_id。这意味着你根本没法回答“哪个用户发起的”“这条指令链是怎么走到这一步的”“为什么它选了那个工具”。Agent 的自主性让“意图”变得非常重要。传统系统里操作者是人意图默认存在Agent 系统里操作者是模型意图必须显式记录。我强烈建议你给每次工具调用都加上一个 reason 字段强制模型在触发工具前解释“我为什么调用了这个工具”。这个字段看起来只是日志里多一行字但排起查来价值无法估量——它能让你把“不可解释的自主行为”变成“可审计的决策链”。可观测性至少要覆盖三层入口层谁发起的、哪个 session、决策层模型为什么选择了这个工具、出口层工具返回了什么、最终输出了什么。每一层都要有关联 ID 贯穿。你不用一上来就上全家桶观测平台先把日志字段补全比任何花哨的 tracing 工具都管用。2.5 生命周期与实例泛滥没人认领的僵尸 Agent最后一个破绽是生命周期管理。Agent 的搭建门槛比传统应用低得多扣子这类平台让“五分钟搭一个 Agent”成为现实于是企业内部会出现大量实验性 Agent开发环境里挂着一个测试用的机器人、本地起的服务端口还开着、一个临时脚本里的 API Key 从没设过过期时间。这些没人认领的“僵尸 Agent”比正式上线的 Agent 更危险。正式上线的至少还有负责人僵尸 Agent 连负责人都没有它的权限是谁给的、数据流向了哪里、令牌还有效多久全都无人知晓。我见过一个团队做权限复核发现一个一年前创建的 Agent 实例还绑着当时测试用的高权限令牌而创建它的员工早就离职了。治理上我建议至少建立一套“Agent 登记表”每条记录包含负责人、业务用途、运行环境、绑定的权限范围、数据访问清单、上次权限复核时间。上线前登记下线时回收每季度复核一次。别觉得这是形式主义等出了事你才发现找不到负责人那才是真正的灾难。3. 落地方案给 Agent 套上治理缰绳3.1 权限收缩最小权限加即时令牌现在聊怎么落地。第一件事把静态长期 API Key 全部换掉改成会话级、短时效、按用户维度签发的临时凭证。静态 Key 是 Agent 场景里的第一大雷只要 Key 泄露就等于把 Agent 的完整权限拱手让人而且没有过期时间泄露了你也很难发现。短时效令牌的好处是就算泄露了攻击者能用的窗口也只有十几分钟。下面是一个会话令牌签发的伪代码思路你可以参考落地到自己的框架里# token 签发session 建立时动态生成不是全局共享 def issue_agent_token(user_id: str, agent_id: str, tool_scopes: list[str], ttl_minutes: int 15): payload { sub: user_id, # 真实的用户身份 agent: agent_id, # 哪个 Agent 在执行 scope: tool_scopes, # 允许调用的工具列表 exp: now() ttl_minutes, # 短时效到期自动失效 jti: uuid4() # 唯一请求 ID贯穿全链路 } return sign_jwt(payload) # 工具调用前强制校验而不是只在入口校验一次 def call_tool(tool_name: str, params: dict, token: str): payload verify_jwt(token) assert tool_name in payload[scope], f{tool_name} not allowed # 资源级限制比如只允许访问 user 自己的日历 params[owner] payload[sub] return invoke_tool(tool_name, params)这套思路的关键在于“按会话收缩权限”而不是“按 Agent 固定授权”。同一个 AgentA 用户发起时只能访问 A 的数据B 用户发起时只能访问 B 的数据做不到这一点水平越权就是你系统里的常驻漏洞。落地时通常会配合一个权限中间件在工具调用链路的入口统一做校验避免每个工具各自实现一套鉴权。3.2 数据防泄漏层在 Agent 循环里加滤网第二件事是给 Agent 的循环加数据滤网。实干的做法是写一个轻量的过滤器分别在工具返回后、模型输出前、日志写入前调用。别指望大模型自己判断哪些是敏感数据让规则跑在前面模型只处理脱敏后的安全数据。我这里给一个简化的过滤示意生产环境你可以扩展成规则引擎或调用专门的脱敏服务import re PII_PATTERNS { phone: re.compile(r\b1[3-9]\d{9}\b), id_card: re.compile(r\b\d{17}[\dXx]\b), bank_card: re.compile(r\b\d{16,19}\b), } def redact(text: str) - str: for name, pattern in PII_PATTERNS.items(): text pattern.sub(f[{name.upper()}_REDACTED], text) return text def filter_tool_result(data: dict) - dict: # 按字段白名单放行不在名单里的字段一律剥掉 allowed {title, time, location, summary} return {k: v for k, v in data.items() if k in allowed} def filter_log_prompt(prompt: str) - str: # 日志只留脱敏后的文本绝不落原始 Prompt return redact(prompt)这里有个很关键的心得脱敏是一条“单向门”一旦进入日志或外部存储再想恢复原始数据就难了。所以你要在“数据进入 Agent 上下文之前”和“数据写入日志之前”这两个时间点做过滤而不是等数据已经在系统里转了一圈再做补救。对于 LangGraph 这类框架检查点checkpointer里保存的状态也要做同样的脱敏和加密很多团队忘了这一层一查检查点库全是明文敏感数据。3.3 工具准入与供应链治理从源头卡住第三方风险第三件事建立工具准入制度。我给客户做治理时通常会给一张“第三方工具审查表”每个工具接进来之前逐项打勾。这项工作不需要很复杂但必须有而且一定要落在书面上。审查项检查内容通过标准来源工具作者、维护者、社区口碑有明确归属不是匿名个人项目数据流向工具运行时是否向外部域名发请求无回传或回传地址已备案且必要权限工具要求的访问范围不请求完成任务之外的数据更新频率最近一次发版时间近一年有维护或有明确的安全响应机制许可证依赖包的开源许可允许商用且无传染性合规风险MCP 插件的审查要更严格一些。接入前你要先确认这个 MCP server 是谁部署的、跑在哪台机器上、数据会不会离开你的内网。很多团队只图方便在本地起了个 MCP server 就直接连结果上下文数据全流向外部这是当前 Agent 供应链里最严重的一类风险。我的建议是MCP 插件走和正式业务系统一样的网络白名单策略只允许连接你已知的、经过审批的 server。3.4 全链路可观测性从 request_id 到 tool_call_id 的串联第四件事把日志从“最终结果”升级成“全链路决策轨迹”。你需要一条贯穿始终的关联 ID从用户发起请求开始经过 LLM 决策、每次工具调用、每次 RAG 检索直到最终输出全部串起来。结构化日志示例可以参考下面这个格式{ timestamp: 2025-01-15T03:22:11Z, request_id: a53f9c2e-1d4a-4f3b-8c1a-7d2e4f5a6b7c, session_id: sess_8f6a2b, user_id: zhang.san, agent_name: meeting_minutes_agent, tool_call_id: call_9f4e2a, tool_name: calendar.list_events, reason: 用户要求整理当天会议安排需要获取日历事件列表, input_summary: 请求2025-01-15的日程, output_summary: 返回3条会议记录已脱敏, decision: approved }这个格式里我最看重两个字段一个是 reason也就是“模型为什么调用这个工具”这能帮你在事故复盘时判断模型是否被操纵或误判另一个是 output_summary日志里只存摘要不存完整返回内容既满足审计需求又不扩大泄露面。如果你后续要接 tracing 系统把 request_id 作为最外层 trace_idtool_call_id 作为子 span 的 ID就能无缝衔接到 OpenTelemetry 那套体系里。3.5 并发与熔断给 Agent 加限流器和审批闸门第五件事回到热词里被问爆的“ai agent 怎么扛并发”。性能上你可以上队列、上 worker、上异步调度但从治理角度看我给的建议是给 Agent 的执行加上限流、超时和人工审批闸门。先说限流要对“每个用户、每个会话”的并发数做限制而不是只对总流量做限制。否则某个用户的恶意或异常请求可以把整个 Agent 集群的资源耗光顺便把内部系统打到过载。我用 FastAPI 部署 Agent 服务时会在入口层加一个令牌桶把请求先放进队列再调度到 worker 池执行。这样既控制了并发也方便在 worker 里做统一的超时管理。Agent 每调用一次外部工具也要设超时和重试上限默认 3 秒超时、最多重试 1 次避免 Agent 在循环里反复重试把内部 API 打成雪崩。审批闸门则用于“高危动作”。凡是涉及删除数据、发送外部消息、导出批量数据、修改权限的操作必须在执行前插入一个人工审批步骤——可以是企微通知、邮件确认或者一个待办队列。别担心审批会拖慢效率你只需要对高危操作设闸日常的查询和生成可以完全自动化。我见过太多事故都是 Agent 自作主张执行了本该人工确认的动作加一道闸门成本极低收益极高。4. 实操记录一次真实排障与问题速查清单4.1 案例复盘会议纪要 Agent 半夜调财务 API回到文章开头那个案例我把完整复盘写在这里你大概率会遇到类似的情况。现象是凌晨 3 点一个会议纪要 Agent 调用了财务系统的 list_reimbursement 接口拉取了全量报销数据。排查时我们发现三件事第一日志里只有 “system called list_reimbursement”没有 user_id没有 session_id根本无法知道这次调用是因为什么任务触发的。第二Agent 绑定的是一个拥有财务模块读取权限的服务账号而它本来的任务只需要读日历和会议纪要。第三财务接口没有资源级限制可以一次拉全部数据。修复分了三步走。第一步把服务账号换成了按用户会话签发的短时令牌工具调用时强制校验 scope没有权限直接拒绝。第二步给这个 Agent 做了工具白名单移除所有和会议纪要无关的工具同时给财务批量导出接口加了人工审批。第三步日志升级为全链路结构化字段补上 request_id、session_id、user_id 和 reason。改造后同样场景下日志变成这样“user:zhang.san 在 session:abc 中触发 meeting_minutes 任务工具调用 financial:list_reimbursement 被拒绝返回需要人工审批。”整个事故从“黑盒利用”变成了“可审计、可拦截、可追溯”的流程这就是治理的意义。4.2 常见问题速查表六类故障的排查思路下面这张表是我这几年来排查 Agent 相关问题的高频清单每一条都是真实案例提炼的你可以直接贴在团队 wiki 里当排查手册。症状可能原因排查思路修复建议Agent 访问了不该访问的目录角色权限过大、工具名过于宽泛查看该次调用的工具名和参数复核权限矩阵按工具细分权限加资源级 ACL日志里出现明文手机号、身份证号工具结果未脱敏就写入日志全文检索日志文件确认落库范围加日志过滤器工具返回前先脱敏第三方插件回调外部域名MCP 插件或社区包自带网络请求抓网络出口记录查进程外连地址网络白名单替换来源不明的插件并发上来后偶发越权读取共享 session 或上下文串号检查 session_id 与 request_id 的绑定关系会话隔离加并发上限上下文加锁离职员工令牌还能调用 Agent生命周期管理缺失、令牌无过期时间查令牌签发时间和归属人短时效令牌离职当天回收权限Agent 删除了不该删的数据工具含写权限且无审批流程查看检查点状态确认是否有快照可回滚写操作必须人工审批启用自动备份排障时有一个通用原则先从“数据流向”入手不要先查模型。Agent 出问题90% 是数据被某个工具带到它不该去的地方或者权限比实际任务大。把链路里的每个工具调用都翻出来顺着 request_id 串一遍问题基本就浮出水面了。4.3 责任与制度Agent 必须有 Owner 和红队演练技术手段只是治理的一半另一半是制度和责任。我一直在团队里推一件事每个 Agent 必须有一个明确的 Owner负责人要回答“这个 Agent 是干什么的、它访问哪些数据、出事了找谁”。别小看这一条它能让很多安全问题的响应时间从几天缩到几小时。有了 Owner 之后定期做“Agent 红队演练”会非常有价值。模拟一次攻击者的行为假设模型被 prompt injection 操纵假设你给 Agent 的指令被恶意改写假设某个第三方工具在偷偷回传数据然后看这个 Agent 最大能拿到多大的数据面。这个过程能暴露出你在权限、数据流、审计上的所有短板。演练结果别只停留在报告层面每发现一个能力边界就立刻收紧一档权限把所有 Agent 的默认能力都降到“能够完成任务的最小集”。我个人的经验治理 Agent 最难的从来不是技术实现而是“没人觉得这是他的事”。工程师觉得安全是安全团队的事安全团队觉得 Agent 是工程团队的事最后悬空。所以我在团队里明确了一件事——Agent 的 Owner 就是第一责任人安全团队做审计和红队但无权替业务方“背锅”。这个权责清晰之后治理动作才算真正落得了地。4.4 小团队低成本起步不用上平台也能做的六件事如果你是小团队预算有限不想一上来就采购一堆治理平台我强烈建议你先做下面这六件事成本极低但能覆盖 80% 的泄露风险所有 Agent 必须使用短时效、按会话签发的令牌禁止长期静态 Key。建立工具白名单高危写操作一律走人工审批默认不允许 Agent 自作主张。日志统一改成结构化字段且对 Prompt 和工具结果做脱敏落库前多看一眼。生产环境和开发环境严格隔离开发环境的 Agent 不允许访问生产数据。每月一次权限复核离职当天回收令牌僵尸 Agent 一律下线。给每个 Agent 登记 Owner、用途、数据访问范围并留联系方式。这六件事每一件都可以在一天之内落地不需要额外买任何商业产品只需要改代码习惯和流程。做完之后你会发现绝大多数“Agent 泄露事故”的剧本都到不了你真实现场的那一天因为它们早在第一道闸门就被拦住了。最后再分享一个小技巧给 Agent 的每个工具调用加一个 reason 字段强制模型在调用前用一句话说明“我为什么调用了它”。这个字段看起来只是日志里多一行字但它会把“不可解释的自主行为”变成“可审计的决策链”。我帮人排查事故时最难处理的从来不是“数据丢了”而是“不知道 Agent 为什么要这么做”。有了 reason你至少能顺着模型的决策思路一路回溯这比任何事后诸葛都管用。治理 Agent 这件事说到底就是一句话你可以让机器自主决策但绝不能让它不受解释地行动。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进