自己的 Agent 系统安全吗:从 OWASP Top 10 到九类风险与防护做法
Оригинальный заголовок: 自己的Agent 系统安全吗?
Заголовок и краткое изложение на выбранном языке ожидают перевода.
文章梳理了 LLM 与 Agent 系统的九类安全风险,包括 Prompt Injection、过度授权、身份与委托、敏感信息泄露、不安全的输出处理、供应链、RAG 与记忆投毒、成本放大和 agent 间通信不安全,并逐条给出防护做法。
Полный текст на выбранном языке ожидает перевода. Пока показан оригинал.
少给权限、分开信任域、把不可信的东西始终当作不可信、让确定性的策略层去兜底?!
【引】我们已经把一个 LLM 应用推上线了,RAG能正常检索上下文,Agent 能调用几个工具,用户反馈也不错。但团队里有没有人问过一句:如果有人主动来搞它,会怎么样?大多数在做 AI 系统的团队,都把安全当成后置事项——产品先跑通,安全之后再说。2023 年,这个赌注还算合理,因为那时候大家连产品形态都没想清楚。但到了现在,这个赌注越来越不划算了。本体论如何指引Agent乃至AI系统的安全性呢?可以参考《本体驱动的AI大模型:方法与实践》一书——
先看 2025 年到 2026 年披露的几件事:
2025 年 6 月,一个跑在 Cursor 里的 Supabase agent,携带着 service_role key——这个 key 会完全绕过数据库权限,连 RLS(行级安全)都不生效。攻击者怎么做的?他没有 Cursor 的访问权,也碰不到开发者的机器。他只是提交了一张支持工单。而这个 AI 助手正在处理支持工单,同时手里握着那个高权限 key。一张投毒的工单之后, key 就出现在了一个公开的帖子里。
2025 年 5 月(Invariant Labs 披露)GitHub 官方 MCP server 存在 prompt injection 缺陷,攻击者通过在 issue 里发一条投毒评论,就能让 agent 读出私有仓库的内容。
CVE-2025-6514:mcp-remote 里的一个严重 OS 命令注入漏洞,这个包下载量超过 43.7 万次。攻击者可以从一个恶意的 MCP 端点实现远程代码执行。
2026 年上半年,研究者仅在头五个月里,就披露了 40 多个针对 MCP 实现的 CVE。这不是什么理论上的威胁模型,这是已经发生过的事故清单。
实际要交付系统的人——不是对抗学习研究,也不是模型训练。如果你在做基于 LLM 的应用、把 agent 接到工具上、或者部署 MCP server,那么攻击面就是你要负责的东西。
为什么 AI 系统的安全性跟传统软件不一样
传统软件有一条清晰的分界线:代码是代码,数据是数据。你的 Web 应用不会把用户提交的表单内容当成给服务器的指令来执行(前提是你把 SQL 注入补掉了)。这条分界线,是绝大多数安全思路的地基。
LLM 没有这条分界线。它接收到一串 token,产出一串 token。它无法可靠地区分"这是我要处理的数据"和"这是我要执行的指令",在模型眼里,两者长一样,都是文本。
这不是一个下个版本会修掉的 bug,而是一条根本性的架构属性。而这一条事实,就是绝大多数 LLM 特有漏洞的根源。其他问题都是从这里派生出来的。
还有两条性质,值得铭记:
1. 不确定性(Nondeterminism)。同样的输入可能产生不同的输出,这会让安全测试变难。但反过来看,一个在 99% 情况下守得住的防御,放到规模化场景里依然是可以被攻破的。
2. 没法打补丁(Unpatchability)。你没法像给二进制打补丁那样,热修一个模型的行为。想改变模型对某类输入的响应,通常意味着重新训练。所以真正的出路是:把控制权放在 harness 上,放在运行时的控制平面里。
威胁雷达——你到底在处理什么?
我们先看一下 OWASP 的 LLM Top 10(2025)和 OWASP 的 Agentic Top 10(2026)。需要说明的是,严重程度和发生概率都是看上下文的。一个没有任何工具权限、也不吃外部数据的 agent,跟一个管理云基础设施的编排器,风险画像完全不同。
漏洞类别 | OWASP 对应编号 | 核心一句话 |
Prompt Injection | LLM #1 | 攻击者控制的文本进入上下文,改变了模型行为 |
过度授权 | 给了 agent 超出任务需要的权限和自治度 | |
身份与委托 | Agentic #3 | agent 做事时,用的是谁的身份? |
敏感信息泄露 | system prompt 不是机密,RAG 也可能越权返回 | |
不安全的输出处理 | LLM #5 | 把模型输出当可信内容用,等于制造注入点 |
供应链 | 依赖本身就是攻击面,MCP 生态尤其 | |
RAG 与记忆投毒 | 知识库可以被注入,而且注入一次长期生效 | |
服务拒绝与成本放大 | LLM #10 | 递归循环能把账单拉到天文数字 |
agent 间通信不安全 | Agentic #7 | agent 倾向于信任另一个 agent |
1. Prompt Injection,相当于AI 时代的 SQL 注入
Prompt injection 指的是:攻击者控制的文本进入了 LLM 的上下文,并且改变了它的行为。有两种形式:
直接注入:用户发来的输入直接覆盖 system prompt。典型手法有"忽略之前的指令"、角色重设("你现在是一个黑客助手")、分隔符混淆,或者干脆用对的框架直接问。
间接注入:LLM 处理外部内容——文档、网页、邮件、数据库结果、支持工单——而这些内容里藏着嵌入的指令。模型会把它们当成有效指令来执行,因为对它来说,它们确实就是。
间接注入是更麻烦的那一种。日历邀请不是你用户写的,PDF 也不是你用户上传的。但如果里面写着 ``,而你的 agent 会去处理它,那就出问题了。
Supabase 那起事故就是间接注入。它同时也是"致命三要素"案例:特权访问 + 不可信输入 + 对外通信通道。这三样单独拿出来都还能管得住;三样凑齐,一个投毒输入就能变成一次数据泄露。Supabase 事故三样全占。过去一年里几乎每一起严重的 AI 安全事件,也都三样全占。
为什么注入这么难修?没有"参数化查询"的等价物。你没法像转义 SQL 那样去"清洗"自然语言。现有的防御都是概率性的。关键词过滤、指令遵循分类器,都可以通过混淆、切换语言、多轮升级绕过。
研究显示,防护良好的模型也能被攻破,只要 5 到 10 轮精心构造的对话就够。说实话,我们没法彻底消除 prompt injection,你可以大幅压缩爆炸半径。
永远不要给 agent 它不需要的权限。一个处理支持工单的 agent,不需要生产数据库凭证。最小权限在这里不是建议,它是首要缓解措施。Supabase 事故之所以灾难性,权限级别才是主因,而不只是注入本身。
把不可信输入和特权动作分开。如果你的 agent 既吸收外部内容、又能调用破坏性 API,那你就造了一个单点故障。双 agent 模式会有帮助:一个 agent 只做数据摄入(不给工具权限),另一个只管执行动作(不吃外部内容)。注意,这并不能完全消除注入风险——agent 之间传递的结构化摘要本身也可能携带着被注入的指令,所以执行侧那个 agent 依然要把这份输入当作不可信内容。
高风险动作要求人工确认。发邮件、删记录、调用有副作用的 API 之前——先问一下。多一次点击的成本,远低于一次事故的成本。
监控行为异常。把 agent 干的事记下来。如果一个平时只读记录的 agent,突然想往管理员表里写数据,这就是信号。
2. 过度授权agent 能做的事
这一条与其说是对抗性攻击问题,不如说是系统设计问题。但一旦和注入组合起来,它就变成了攻击的放大器。
过度授权指的是给了一个 agent 超出它当前任务所需的访问权、权限或自治度。具体长这样:
只需要读一张表,却给了读写整个数据库的权限
一个编码助手,能访问所有仓库,包括生产环境的密钥
一个能发邮件、却没有任何确认步骤的 agent
一个 service account,被多个信任级别不同的 agent 共用
风险不只是注入。就算没有对手,agent 也会犯错:误解指令、幻觉、以意料之外的方式串联工具。当权限很窄的 agent 犯错时,爆炸半径是可控的。当权限很宽的 agent 犯同样的错时,爆炸半径是你整个生产环境。
应对方法如下:
把每个工具的范围写死,不是"能读数据库",而是"能读 support_tickets 表,最多 100 行"。
按 agent、按上下文分开凭证,不要共用 service account。短时效、按任务限定的 token,优于长时效、按服务限定的 key。
画出每个 agent 的影响面。上线前问自己:如果这个 agent 叛变了、或者被注入了,最坏影响是什么?如果答案是"一切",那就是设计问题。
限速与动作预算。一个平时只调 5 次 API 的 agent,不该有能力调 5000 次。运维层面的限制也是一种安全控制。
使用策略控制平面。写在 system prompt 里的行为守则是访问控制吗?不是,那只是"建议",模型在被注入时完全可以无视它。真正的强制必须发生在基础设施层,在模型够不到的地方。像 OPA(Open Policy Agent)或 Cedar 这样的工具,可以让你定义:每个 agent 允许调用什么、允许带什么参数、在什么上下文里。这是"我们告诉了模型不要删文件"和"agent 运行时在物理上无法发出删除指令"之间的区别。
这一条的核心思路是:
目标不是让 agent 变聪明、不做坏事。目标是设计一个受控环境,让一个本身不确定的 agent,在物理上无法把一次模型错误变成一次系统事故。
Guardrails 拦不住幻觉,但架构可以让幻觉不产生后果。
3. 身份与委托,还没有agent的身份原语
这个问题被严重低估了。当一个人登录你的系统时,身份是清晰的:他有 session token、有 ACL、有跟账号绑定的审计日志。但当一个agent做事时,它是在谁的身份下行动?
在现在大多数实现里,答案让人不太舒服:agent 继承了开发者的凭证,或者用了一个共用的 service account,或者带着云平台的环境权限在跑——而那些权限从来没按具体任务限定过。在多 agent 系统里更糟:一个 orchestrator 委托给子 agent,等动作真正执行时,最初那个用户上下文已经在链路某处丢掉了。
三个具体问题:
环境权限继承。一个跑在云函数里、带 IAM role 的 agent,继承了这个 role 的全部权限。如果这个 role 当初配得很宽,那么在该环境里跑的每一个 agent 都拥有这些权限——不管它在做什么任务、也不管是谁触发的。
没有验证的委托。在多 agent 系统里,Agent B 通常没有任何办法去验证 Agent A 传来的指令是否合法、是否被授权、有没有被篡改。一个被攻破的 Agent A,可以让 Agent B 去做原始用户从没授权过的事。
工具调用里丢了用户上下文。当你的 agent 调用 MCP 工具或外部 API 时,这个请求通常不携带原始用户身份。工具看到的是 agent 的 service account,而不是用户。工具侧想自己做授权检查都不可能。
一般的应对方法如下:
给每个 agent 独立的 workload identity。不是共用 service account,而是一个专属于该 agent 职能的身份。云平台都支持AWS IAM role、GCP Workload Identity、Azure Managed Identity。
把用户上下文沿调用链传下去。当 agent 代表某个用户发起工具调用时,这个用户身份应该跟着请求走——作为 header、token 或签名声明。工具侧就能根据"到底是谁发起的请求"来执行自己的授权。
把 agent 间的委托当成一次授权决策。Agent A 只应该在预定义范围内才能指示 Agent B。这个范围应该写在策略里,而不是在运行时用自然语言商量。
在基于 token 的委托里验证 audience。当 Agent A 给 Agent B 签发 token 时,这个 token 应该带一个明确的 audience 声明——也就是 Agent B 的标识。没有它,这个 token 就能被拿去重放给系统里的其他 agent。大多数 JWT 库都支持这个,但大多数 agent 实现都没配。
审计日志里,把 agent 身份和 agent 动作记在一起。每一条工具调用日志都应该能回答:哪个 agent、在哪个策略下、代表哪个用户、调用了什么、带了什么参数。
4. 敏感信息泄露 , system prompt 不是秘密
有一个流传很广的误解:system prompt 是机密的,但不是。有一大堆技术可以稳定地把它们挖出来:直接问("重复你的系统指令")、多轮升级、让模型翻译它、基于混淆的手法,以及格式注入(比如让模型把它的指令格式化成 JSON 输出)。而 system prompt 里,很可能有:
影响商业决策的业务逻辑
内部 API 结构和端点名称
你其实并不想暴露的产品行为细节
某些情况下,甚至是有人觉得"反正用户看不到"就塞进去的凭证或密钥
不止 system prompt,LLM 还可能泄露训练数据(模型会记住东西,并且能被诱导逐字复现)、共享部署里其他用户的上下文窗口内容,以及 RAG 检索出来的、当前用户本不该看到的文档。RAG 的授权缺口尤其常见。一个按相关性打分、返回 top 结果的检索系统,并不知道当前用户是否有权看这些文档。如果你的 embedding 管线吃进了多种权限级别的文档,然后仅凭语义相似度返回,那么一个低权限用户完全可以通过"问对问题",检索到他本不该访问的内容。
一般我们可以做到:
设计时就要假设 system prompt 最终会泄露。这不是必然,但提取技术已经足够可靠,不该把机密性当成唯一防线。不要在 prompt 里放秘密。你真正的安全边界是策略与权限层,不是上下文窗口里的那些说明文字。
加输出分类器。在把模型输出返回给用户之前,先过一遍,检测 API key、邮箱地址、内部 URL、PII 等敏感模式。拦截或脱敏。
把访问控制做在检索层,而不只是摄入层。用户发起 RAG 查询时,检索本身应该尊重他的权限。先按 ACL 过滤候选,再按相关性排序;更好的做法是按权限级别把语料分开存。
审计你的 RAG 语料里有什么。定期回顾索引了哪些文档。一份被错误分类的内部文档,就能把信息暴露给所有用户。
5. 不安全的输出处理,LLM 本身成了注入源
这是经典的应用安全,只是 payload 换了个来源。LLM 生成一段响应,你的应用把它渲染、存储、或者传给下游系统。如果你把这个输出当成可信内容用,你就是在制造注入漏洞。
模型就是一台"由攻击者控制的 payload 生成器"。如果用户能影响模型说什么,那他们就能影响下游系统拿它做什么。常见的表现形式:
模型输出未经转义就渲染进 HTML →存储型 XSS
模型输出被拼进 SQL 查询 →SQL 注入
模型输出被传给 shell 命令 →命令注入
模型输出被写进 CSV,再被财务系统导入 →公式注入(以 =、+、-、@ 开头的单元格,在表格软件里可以执行 OS 命令)。模型总结了一份恶意文档,这份总结又进了工单系统 ,即二阶注入。公式注入这个案例被严重低估了。一个总结文档、并输出表格列的模型,如果源文档里含有 =cmd|"/C calc",它就可能原样产出这段内容——或者更隐蔽的变体,比如 +cmd|"/C calc"、@SUM(1+1)*cmd|...。当同事用 Excel 或 Google Sheets 打开这张表时,它就执行了。OWASP 把它归在 CSV injection 下。
一般的应对如下:
把模型输出当成用户输入来对待,渲染前做 HTML 编码,放进查询前做参数化,传给 shell 前做净化。
按 schema 校验输出。如果期待的是带特定字段的 JSON,就校验结构。如果模型产出的东西结构不对,就拒掉重试,而不是继续往下传。
不要在没有沙箱的情况下执行模型生成的代码。如果你的场景涉及代码生成,就在沙箱里跑:有资源上限、没有网络访问、没有宿主机文件系统访问。
在浏览器里启用 CSP(内容安全策略)。如果输出转义漏掉了什么,一份扎实的 CSP 就是兜底的安全网。
6. 供应链依赖就是攻击面
这一条在传统软件安全里是老朋友了,但 AI 特有的那几个维度值得单独点出来。
MCP server 供应链正在被实际利用。2025 年的研究分析了 7000 多个 MCP server,其中36.7% 存在 SSRF 漏洞。CVE-2025-49596(CVSS 9.4)影响了一个被广泛使用的 MCP 集成。到了 2026 年初,AI agent 的技能注册表被系统性地投毒——某个大型注册表里,下载量最高的 7 个技能中一度有 5 个被确认是恶意软件。
MCP 的架构还带来一个全新的、特有的供应链风险:工具描述投毒(rug pull)。当你的 agent 连接到一个 MCP server 时,它会读取工具描述,并把描述纳入自己的上下文。一个恶意或被攻破的 MCP server 可以:
在工具描述里藏指令(通过工具元数据做 prompt injection)。、在安装之后静默修改工具描述(rug pull 攻击——你第 1 天批准的工具,到第 7 天已经把你的 API key 改道了)。在连接了多个 server 的情况下,覆盖或拦截发往其他可信 MCP server 的调用。Simon Willison 在 2025 年 4 月就指出过这一点:MCP 客户端应该向用户展示初始的工具描述,并在描述发生变化时告警。而目前大多数客户端两件都没做。
一般的应对方式:
把每个 MCP server 都当成不可信,直到你验证过它。连接之前先读它的工具描述。搞清楚你到底给了 agent 什么权限。
锁定工具版本。不要用会静默更新的浮动依赖。
维护一份 AI BOM(物料清单)。记录你的系统用到的每一个模型、数据集、MCP server 和 API 依赖。你要知道每个东西跑的是什么版本。
校验模型文件的 checksum。模型文件(.safetensors、.h5、ONNX)可以携带恶意载荷。如果你从外部注册表拉模型,加载前的完整性校验不是可选项。
审查来自第三方注册表的 MCP server。NPM 的供应链问题正在 MCP 生态里重演。下载量高不是安全信号。
7. RAG 与记忆投毒,知识库也是攻击面
RAG 现在是让 LLM 接上私有数据的标准架构。据 2025 年的一项调研,53% 的生产系统用 RAG 而不是 fine-tuning。这让向量库成了一条关键的安全边界。
检索投毒是数据投毒在 RAG 场景下的版本。一个能把文档注入你知识库的攻击者,也就能注入一些指令——这些指令会在被检索到时激活。一份单独看毫无问题的文档,当模型把它作为相关查询的上下文检索出来时,就变成了一发 prompt injection payload。
一次检索投毒攻击的解剖:
攻击者找到把文档弄进你语料库的途径——用户提交的表单、被索引的网页、共享的文档仓库
文档里含有伪装成正常文本的嵌入指令
某个用户提了一个语义上能匹配到这份文档的问题
文档被检索出来,放进上下文
模型遵循了嵌入的指令,而不是回答问题
它特别危险的地方在于:这个攻击是持久且被动的。攻击者不需要持续访问权限。他注入一次,之后每一次检索到这份文档的查询都被污染。
Agent 现在越来越有持久记忆——对话历史、过去会话的摘要、缓存的工具结果。每一个都是潜在的注入面。一条被投毒的记忆条目会跨会话存活。如果你的 agent 会总结并存储对话,攻击者完全可以在一次会话里种下指令,让它在未来某次会话里激活。
一般的应对:
索引之前先验证文档。对要进知识库的内容,施加跟任何输入一样的审视。扫描类指令模式。可以考虑在入库前,用 prompt injection 检测器做一次内容分类。
按信任级别隔离语料。可信的内部文档、用户提交内容、爬取的网页内容,应该放进不同的索引,走不同的检索路径。
把 agent 记忆当成一条安全边界。记忆条目——尤其是那些会影响未来 agent 行为的——应该按输入的标准来校验。不要把 agent 的原始输出不做审查就写进持久记忆。
监控检索模式。标准查询突然开始检索到不同的文档,可能就是投毒的信号。
多租户 RAG 需要真正的租户隔离。如果你的 RAG 管线服务多个租户,他们的语料需要实际隔离——不是共享索引上的元数据过滤。在查询时应用的元数据过滤,可以被构造过的查询或配置错误的检索逻辑绕过。独立索引更难被错误路由,当然它也不是万灵药——路由逻辑和跨索引编排同样需要审查。
8. 服务拒绝与成本放大,账单本身就是一个攻击面
这一条得到的关注不够,因为它看起来不像"安全"问题,而像"运维"问题。但在规模化之后,它两者都是。
LLM 推理按 token 计费,很贵。一个能被触发进入递归循环的 agent、一个吃无界输入的 agent、或者一个会扇形展开成大量并行工具调用的 agent,能产生的成本比正常情况高出好几个数量级。这可能是意外(agent 编排里的 bug),也可能是故意的(攻击者找到了一个公开端点,拿你的 GPU 给他自己干活)。具体向量包括:没有终止条件的递归 agent 循环、强制检索大量文档集的查询、把输出 token 长度最大化的 prompt 模式、以及每一步都触发更多工具调用的多步工具链。
一般的应对方法如下:
对输入上下文和输出生成都设硬性 token 上限。不是软性建议——是在基础设施层强制执行的硬切断。
给每个 agent 循环加终止条件。最大步数、最大工具调用次数、最大耗时。到点还没做完,就把它抛给人,而不是让它继续跑。
按请求、按会话做预算。追踪每个用户会话的累计成本,超出正常使用模式的会话直接切断。
给公开端点限流。如果你的 AI 功能不需要认证就能访问,那你需要在网关层做限流,在请求到达模型之前就拦住。
9. 不安全的 agent 间通信
当从单 agent 走向多 agent 系统,编排器委托给子 agent、agent 之间通过 MCP 互调、并行 worker 池——一个新的攻击面出现了:agent 之间的通信。
核心问题在于agent 倾向于信任其他 agent。如果 Agent A 让 Agent B 做某事,Agent B 往往不会去验证这个指令是否合法、Agent A 是否已经被攻破。这就造出了一个传递性漏洞——攻破一个 agent,你就有可能控制它能委托到的所有 agent。
除了直接攻破,还有一个更微妙的问题:信任传递。当 Agent A 从外部来源取来一份文档,把摘要作为上下文传给 Agent B 时,这段上下文里可能含有攻击者控制的内容。Agent B 无从知道这些内容从哪来——它是从它信任的 Agent A 那里收到的。指令的 provenance在传递中丢失了。
一般的应对方式:
不要假设 agent 之间的通信是可信的。验证来自其他 agent 的指令,就跟验证用户指令一样。
给每个 agent 明确的身份和权限范围。Agent B 应该知道 Agent A 可以要求它做什么,并拒绝范围之外的请求。
沿调用链保留 provenance。当 orchestrator 把上下文传给子 agent 时,给它打上来源标签——来自外部 URL、用户提交、系统生成。子 agent 就能对每一部分施加恰当的怀疑。
对 agent 间消息做认证,而不只是签名。签名证明完整性,认证证明来源。Agent B 应该验证的不只是消息有没有被篡改,而是它确实来自一个被授权的 Agent A——用 JWT 声明里的 audience 校验或等价机制。没有 audience 校验,Agent A 签发的 token 就能被拿去重放给 Agent C。
审计 agent 的委托链。如果一个 agent 能把任意指令委托给另一个 agent,那这就是一条提权路径。把它显式画出来,并加以约束。
AI 系统的安全测试该测什么
AI 系统的安全测试,跟一般的安全测试有同样的毛病:很容易测错东西。跑一句"我能不能越狱这个模型",会错过生产环境里几乎所有重要的事情。
正确的做法是:用具体的用户可见功能去面对具体的威胁场景——而不是抽象地测模型。问题不是"Claude 能不能被攻破",问题是:
一个研究型 agent,能不能读到一个它本不该有权限访问的文件?
一个邮件 agent,能不能被操纵着把数据发到组织外部?
一个 AWS agent,能不能被骗着给自己的权限提权?
一份注入进 RAG 语料的文档,能不能让 agent 调用某个破坏性工具?
能不能触发一个 agent 循环,产生不受控的成本?
每一个都是具体的测试用例,有明确的通过/失败判定。你知道每个场景里 agent 本该做什么,也知道坏结果长什么样。这就是可测的。
还有一点:系统一有变化就要跑安全测试,不要只在初次部署时跑。新增一个工具、接入一个新的数据源、修改某个 agent 的权限范围,都可能打开一条原本关着的安全边界。上个月通过的测试套件,今天可能就失败了——只是因为有人加了一个 MCP server,而它的权限比他们以为的更宽。
另一个经常被忽略的点:agent 是不确定的,所以每个测试都要跑多次。一个 20 次里守住 19 次的防御,在生产规模下不算防御。
小结
LLM 系统的不安全性,根源不在模型不够聪明,而在于我们习惯性地把"文本"当成了可信的东西。传统软件花了几十年,才学会在数据和指令之间画一条线。AI 系统现在得重新学一遍这件事——而且这次没有现成的参数化查询可以抄。
因此真正有效的做法,往往朴素得不像"AI 安全":少给权限、分开信任域、把不可信的东西始终当作不可信、让确定性的策略层去兜底。这不是一套能让你高枕无忧的方案。但它是那种——出事的时候你会庆幸自己做了的方案。
Источник: 喔家ArchiSelf · mp.weixin.qq.com