两起漏洞揭示 Agent 工具参数的安全边界
文章以 Semantic Kernel 的表达式执行和任意文件路径漏洞说明:模型生成的工具参数必须视为不可信输入。作者提供 AST 白名单与路径规范化校验代码,并用小规模自建样本展示其相对字符串拦截的优势。
完整中文译文
模型读了一封邮件、一个网页,就可能被其中的指令带偏。真正需要追问的是:它接下来调用工具时,能不能把一段表达式送进 eval(),或者把文件写到任意目录?
这篇文章借两个 Semantic Kernel 漏洞给出的结论很明确:模型生成的参数,应当像互联网上提交的表单一样被检查。作者用一组小样本演示了黑名单和白名单的差别,也划清了边界:参数校验能限制破坏范围,却不能保证 Agent 的行为符合用户意图。
两个 CVE,暴露的是同一条信任链
原文讨论的两个漏洞均在 2026 年 2 月披露,CVSS 评分都是 9.9。Microsoft Defender Security Research 团队于 2026 年 5 月 7 日发表分析。原文没有列出受影响版本和修复版本,不能据此判断某个具体安装是否安全。
CVE-2026-26030 出在向量存储的过滤逻辑:程序把字符串拼成 Python lambda,再交给 eval()。其中的搜索参数由模型生成,插入代码前没有得到充分校验。攻击者可以通过提示注入影响模型输出,让参数逃出原来的字符串位置。
原实现有黑名单,但攻击者能沿着 Python 类型体系访问进程中的其他对象,不必直接写出被禁止的词。Microsoft 描述的修复包含 AST 节点白名单、危险属性名黑名单等多层检查。
CVE-2026-25592 则从工具暴露开始。内部辅助方法 DownloadFileAsync 被加上 [KernelFunction],成了模型可调用的工具,还允许模型指定本地目标路径。攻击链先向沙箱写脚本,再向 Windows Startup 目录写文件,使代码在下次登录时运行。
公告提供的临时措施,是用函数调用过滤器检查两个文件传输函数的参数,确保 localFilePath 位于白名单中。
一个是字符串拼接代码,一个是路径未经约束。提示注入负责把危险参数送进来,漏洞则让参数获得了执行代码、越界写入的能力。
模型可以提出操作,但不能替系统批准操作。
原文引用 Unit 42 在 2026 年 3 月的报告:其收录案例中,14.2% 涉及数据破坏意图,85.2% 使用社会工程式越狱。这是该报告的样本分布,不能直接当成所有 Agent 流量的攻击比例,但足以说明外部文本需要按不可信输入处理。
表达式为什么要检查语法,而不是搜索危险词
作者先做了一个字符串黑名单,拦截 import、exec、eval、os.、system、open(。结果,14 个对抗性过滤表达式中有 11 个通过。
原因在于,危险行为不只有一种写法。属性访问、类型继承关系、传给 getattr 的拼接字符串,都可能避开这些词。作者在干净的 Python 3.12.10 解释器中演示,从空元组出发便能枚举到 164 个类;应用加载更多库后,可达对象还会增加。
AST(抽象语法树)白名单反过来规定:这里只接受行变量 x、指定字段、比较与布尔运算,以及指定类型的常量。
下面这段代码解决的是「过滤表达式允许长成什么样」:
import ast
ALLOWED_FIELDS = {"price", "category", "rating"}
ALLOWED_NODES = (ast.Expression, ast.BoolOp, ast.And, ast.Or, ast.Not, ast.UnaryOp,
ast.Compare, ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE,
ast.Constant, ast.Name, ast.Load, ast.Attribute)
def gate_filter(expr: str) -> bool:
try:
tree = ast.parse(expr, mode="eval")
except SyntaxError:
return False
for node in ast.walk(tree):
if not isinstance(node, ALLOWED_NODES):
return False # 禁止调用、lambda、下标、推导式和 f-string
if isinstance(node, ast.Name) and node.id != "x":
return False # 只允许行变量
if isinstance(node, ast.Attribute):
if not (isinstance(node.value, ast.Name) and node.value.id == "x"
and node.attr in ALLOWED_FIELDS):
return False # 只允许已知字段,禁止 x.__class__
if isinstance(node, ast.Constant) and not isinstance(node.value, (int, float, str, bool)):
return False
return True
重点是 node.attr in ALLOWED_FIELDS:允许属性节点还不够,必须限制属性属于谁、名字是什么。
作者的测试中,这道检查拦住了全部 14 个对抗表达式,同时接受全部 4 个正常过滤条件。

但通过 AST 检查,不等于查询符合权限要求。原文中的 "' or 1 or '" 会解析成一个字符串常量,作为条件始终为真,可能返回全部记录。它没有执行代码,却仍可能造成数据越权。行级授权必须另外落实。
作者也建议,即使保留 eval(),仍应清空 builtins、限制作用域。能把过滤条件改成「字段、操作符、值」的结构化数据,并自行构造判断逻辑,就优先避免 eval()。
路径要按目录关系检查,工具要默认拒绝
文件路径的黑名单同样容易漏。只检查 ..,挡不住 /etc/passwd;检查 startswith(root),又会把 /sandbox/session-42-evil/x 当成 /sandbox/session-42 的内部路径。
作者用下面的代码比较这几种检查:
import posixpath
ROOT = "/sandbox/session-42"
def _full(p: str) -> str:
return posixpath.normpath(p if p.startswith("/") else posixpath.join(ROOT, p))
def naive_dotdot(p): return ".." not in p
def naive_startswith(p): return _full(p).startswith(ROOT)
def canonical_gate(p): return posixpath.commonpath([ROOT, _full(p)]) == ROOT
关键在最后一行:commonpath 比较路径组件之间的包含关系。测试包含 4 个合法路径和 7 个非法路径;.. 检查与前缀检查各漏过 2 个,规范化后的包含检查漏过 0 个。

这仍只是字符串层面的检查。posixpath.normpath 和 commonpath 不解析符号链接,真实文件系统还需要考虑 os.path.realpath 或 Path.resolve();原文也提到 pathlib 的 Path.relative_to 可用于包含判断。.NET 修复则先使用 Path.GetFullPath() 规范化路径。
检查之后如何使用,也决定边界是否有效。out/..%2f..%2fetc/x 在演示中是合法的字面目录名,如果后续组件又做 URL 解码,先前检查的就不是最终使用值。
校验、授权和实际操作,必须针对同一个最终值。
作者进一步主张,把检查放进所有工具调用必经的统一入口。示例用 POLICY 为 search_products.filter 配置 gate_filter,为 write_report.path 配置 canonical_gate,未列出的工具一律拒绝。这样,误注册的辅助方法也无法直接被调用。
CI 中还能检查 set(TOOLS) == set(POLICY),让新增工具却未配置策略的变化尽早暴露。
我的判断
文章最有价值的地方,是把提示注入问题落到了程序员熟悉的输入边界:参数类型、表达式语法、路径范围、工具权限。Prompt 中写「禁止越界」,可以辅助模型行为,但安全判断需要由工具入口的确定性代码承担。
不过,这些数字只能证明示例中的失败方式。14 个表达式、7 个路径都由作者编写,而且大部分表达式刻意避开黑名单词汇。作者自己也明确说,这不是基准测试。没有生产流量、独立攻击集或长期运行数据,不能把「0 个漏过」解释成已经证明安全。
路径演示尤其不能直接照搬成生产方案。它采用 POSIX 路径语义,没有覆盖完整 Windows 环境;解析符号链接后,也仍要考虑检查与使用之间文件系统是否变化。原文提醒了检查与使用漂移,但没有给出完整的并发文件操作实现。
统一入口的方向我赞同,但示例 invoke 只检查策略表里列出的参数,再把整个 kwargs 传给工具,并不等于验证了所有参数。生产实现还得明确多余参数、类型错误以及跨参数约束如何处理。
此外,允许写入沙箱不代表允许执行其中的脚本。文件范围、执行权限、行级授权各自解决不同问题,需要共同约束工具的实际能力。
能拿来做什么
- 给日常 AI 编程 Agent 清点真实注册的工具,重点查内部辅助方法是否意外暴露,并在调用入口配置默认拒绝的工具白名单。
- 如果搜索工具接受模型生成的过滤条件,优先改成结构化的字段、操作符和值;确需表达式时,再限制 AST,并独立执行数据授权。
- 为文件工具统一完成路径解析、范围检查和实际使用,检查中间层是否还会解码、展开或重新拼接路径,并针对真实文件系统处理符号链接。
- 把原文涉及的类型链访问、同名前缀目录、绝对路径和编码路径加入回归样本;记录被拒绝的工具名与参数名,再持续补充真实绕过案例。