随着大型语言模型(LLM)在企业级应用中的广泛部署,AI Agent 执行由模型生成的代码已成为提升任务完成效率的关键手段。然而,传统的代码执行方案 —— 无论是直接使用 exec() 还是依赖完整的容器隔离 —— 都面临着严峻的安全挑战。Pydantic 团队推出的 Monty 项目提供了一种创新的解决方案:它使用 Rust 重写了一个受限的 Python 解释器子集,通过严格的外部函数调用白名单机制,在微秒级启动时间内实现安全的代码执行环境。本文将深入剖析 Monty 的架构设计、参数注入防御策略以及在 AI 沙箱场景中的工程实践参数。
AI Agent 代码执行的安全困境
在传统的 AI Agent 架构中,让 LLM 执行代码通常有两种路径。第一种是依赖 Agent 逐一调用预定义的工具函数(Tool Calling),这种方法虽然安全,但当任务涉及复杂的数据转换、多步骤算法或动态逻辑时,LLM 需要进行大量的推理步骤来协调工具调用,不仅延迟高,而且容易在长上下文推理中出现错误累积。另一种方案是让 Agent 直接编写并执行 Python 代码,这种方式被称为 "Code Mode" 或 "Programmatic Tool Calling",能够显著提升 Agent 的表达能力上限。例如,Agent 可以编写一段循环来处理列表数据、调用多个 API 并对结果进行聚合计算,而无需在每次操作之间都与外部环境进行交互。
然而,直接执行 LLM 生成的代码带来了不可忽视的安全风险。传统的 exec() 或 eval() 函数会授予生成的代码对宿主环境的完整访问权限,包括读取和写入文件系统、访问环境变量、建立网络连接甚至执行系统命令。攻击者如果通过提示词注入(Prompt Injection)或精心构造的代码生成逻辑,让 Agent 执行恶意代码,可能导致敏感数据泄露、服务器被植入后门,甚至整个基础设施被攻陷。即使采用容器化隔离方案,启动一个完整的 Docker 容器也需要数百毫秒的延迟,并且需要管理复杂的容器生命周期,这在需要频繁执行短代码片段的 Agent 应用场景中显得过于笨重。
Monty 的设计目标正是填补这一空白:提供一个既足够安全以防止任意代码执行危害,又足够轻量以支持高频代码执行场景的解决方案。根据 Pydantic 团队的基准测试,Monty 的启动时间仅为微秒级别,相比 Docker 容器的约 195 毫秒启动延迟和 Pyodide WebAssembly 的约 2800 毫秒冷启动,性能优势显著。这种轻量级特性使其非常适合在 Agent 对话循环中频繁触发代码执行的使用模式。
Rust 重写与受限语言子集的安全基础
Monty 采用了从零开始用 Rust 重写 Python 解释器的技术路线,这一选择带来了多层次的安全收益。Rust 语言本身的内存安全特性(Ownership 系统和 Borrow Checker)从根本上消除了 C/C++ 解释器中常见的内存泄漏、缓冲区溢出和悬垂指针等问题,这些漏洞在处理不可信的 LLM 生成代码时尤为危险。其次,由于 Monty 不依赖 CPython 解释器,它没有继承 Python 标准库中那些可能被滥用的功能模块,也没有全局解释器锁(GIL)的限制,这使得其在资源控制和并发安全性方面具有天然优势。
更为关键的是,Monty 有意识地设计为一个受限的 Python 子集,而非完整的 Python 实现。当前版本明确不支持类定义(Class Declarations)、Match 语句、大部分标准库模块以及任何第三方库。这种设计并非技术上的妥协,而是经过深思熟虑的安全策略。Python 语言的灵活性也意味着其攻击面广泛:类的 __init__ 方法可以访问系统资源,__getattr__ 等元方法可以拦截任意属性访问,正则表达式模块 re 可能被用于 ReDoS 攻击,而 subprocess、os、sys 等模块则直接提供了系统级操作能力。通过限制语言特性,Monty 将代码的攻击面压缩到最小范围,仅保留表达式、控制流、基本数据结构操作和函数调用等核心能力。
根据 Simon Willison 在其技术博客中的实测体验,虽然当前版本的 Monty 连类定义都不支持,但其目标使用场景 —— 让 AI Agent 执行代码 —— 并不会因此受到根本限制。这是因为 LLM 具备强大的迭代能力:当 Agent 编写的代码因缺少类支持而报错时,它可以接收错误消息并迅速调整策略,使用更简单的数据结构(如字典和列表)来替代类的封装。Pydantic 团队的设计哲学是提供 "足够用于 Agent 表达意图的 Python 子集",而非追求语言完整性。这种取舍使得安全审计和攻击面分析变得更加可控,同时也降低了整个解释器的维护复杂度。
白名单机制与外部函数调用控制
Monty 的安全架构核心在于其对外部函数调用的严格白名单控制机制。在 Monty 的执行模型中,生成的代码既不能直接访问文件系统,也不能调用网络 API,更无法读取环境变量 —— 所有这些通常由 Python 标准库提供的功能都被完全屏蔽。代码如果需要与外部世界交互,唯一合法的途径是通过开发者预先定义并显式授权的 "外部函数"(External Functions)。
在工程实践中,这一机制通过 Monty 初始化参数进行配置。开发者需要在创建 Monty 实例时,明确指定代码可以调用的外部函数名称列表(external_functions 参数),以及这些函数在宿主环境中的实际实现。例如,在一个天气查询 Agent 中,开发者可能只授权 fetch_weather 和 fetch_population 两个函数,生成的代码只能通过这两个入口与外部 API 通信。任何试图调用未授权函数的尝试 —— 无论是显式调用如 open("secret.txt") 还是通过 __import__("os").system("rm -rf /") 这样的动态注入攻击 —— 都会被 Monty 解释器立即拒绝。
这种设计本质上实现了基于能力的访问控制(Capability-Based Security)。与传统的基于权限列表(Allowlist/Denylist)的安全模型不同,Monty 的白名单不依赖于对危险函数名的黑名单过滤(这种方法容易被各种变形和混淆技术绕过),而是采用了更为根本的策略:默认拒绝所有,只放行明确声明的函数入口。由于 Monty 生成的代码运行在完全隔离的沙箱环境中,它甚至无法动态导入模块或访问全局命名空间,因此不可能绕过白名单机制去调用那些未被授权的函数。这种设计从根本上消除了参数注入攻击的可能性,因为即使 LLM 生成的代码中包含了恶意参数,攻击也无法触及宿主系统的任何敏感资源。
为了进一步增强安全性,Monty 还支持外部函数的类型检查功能。开发者可以在 type_check_stubs 参数中提供函数签名定义,Monty 会在代码执行前验证传入的参数类型是否符合预期。例如,如果授权的 fetch 函数签名定义为接收一个字符串 URL 参数,Monty 会确保代码中对该函数的调用都传递字符串类型的参数,防止类型混淆攻击。这一特性与 Pydantic 的数据验证能力一脉相承,为 AI Agent 的代码执行提供了额外的安全防护层。
资源限制与沙箱可观测性
除了访问控制,Monty 还提供了精细的资源使用限制机制,防止恶意或失控的代码消耗过多的计算资源。开发者可以配置执行时间上限(防止无限循环攻击)、内存使用上限(防止内存耗尽攻击)和栈深度限制(防止栈溢出攻击)。当代码超出预设的资源配额时,Monty 会立即中断执行并返回错误,而非继续消耗宿主系统的资源。
这种资源控制机制在 AI Agent 场景中尤为重要,因为 LLM 生成的代码质量存在不确定性。即使没有恶意意图,代码中的逻辑错误(如死循环、指数级递归或过大的数据结构)也可能导致服务崩溃。Monty 的资源限制为这些边界情况提供了可预测的安全边界。此外,Monty 还支持捕获代码的标准输出(stdout)和标准错误(stderr),并将其作为执行结果的一部分返回给调用方,这使得代码执行过程对开发者完全可见,便于调试和审计。
Monty 的快照(Snapshot)功能进一步增强了其在生产环境中的可用性。开发者可以在外部函数调用点暂停代码执行,将当前的解释器状态(包括所有局部变量和执行上下文)序列化为字节串并持久化存储。当需要恢复执行时,只需加载快照字节串并调用 resume 方法,传入外部函数的返回结果即可。这一特性使得 Monty 可以与分布式任务队列或持久化工作流系统无缝集成。例如,一个需要调用多个外部 API 的复杂 Agent 代码,可以在每次 API 调用之间将状态快照存入数据库,即使服务重启也能从上次中断的地方继续执行,而无需重新运行整个代码逻辑。
工程落地参数与监控建议
在生产环境中部署 Monty 时,以下工程参数值得关注。启动时间方面,Monty 的冷启动时间在微秒级别,这意味着即使在高频代码执行场景下,也不会成为性能瓶颈。内存占用方面,整个 Monty 解释器的二进制文件约为 4.5MB,作为 Python 包安装后总大小可控,且在运行时不会产生额外的解释器进程开销。序列化效率方面,Monty 的快照功能支持毫秒级的状态存取,适合与外部存储系统集成。
对于安全监控,建议开发者关注以下指标:每次代码执行的平均耗时(检测可能的无限循环)、外部函数调用频率分布(识别异常调用模式)、以及代码执行拒绝率(检测可能的攻击尝试)。由于 Monty 的执行过程完全封闭,建议在外部函数实现层面添加详细的审计日志,记录每次调用的参数、时间戳和调用方上下文,以便事后追溯。
需要注意的是,Monty 的安全性最终依赖于白名单函数的正确实现。如果开发者错误地将 subprocess.run 这样的危险函数加入白名单,生成的代码仍然可以执行任意系统命令。因此,在设计 Agent 的外部函数 API 时,应遵循最小权限原则,每个函数只暴露完成特定任务所必需的最小权限,并对其参数进行严格的输入验证和清理。
总结
Monty 为 AI Agent 的代码执行场景提供了一个独特的安全方案:通过 Rust 重写受限的 Python 子集、严格的外部函数白名单控制和精细的资源限制,它在保持代码执行能力的同时,将安全风险控制在可接受的范围内。其微秒级启动时间和快照序列化能力,使其特别适合需要在 Agent 对话中频繁执行代码的生产环境。虽然当前版本在语言完整性上有所限制,但这种约束恰恰是其安全设计的重要组成部分。随着项目的持续迭代,Monty 有望成为 AI 应用安全代码执行的标准基础设施之一。
参考资料:本文部分技术细节参考了 Monty 官方 GitHub 仓库(https://github.com/pydantic/monty)以及 Simon Willison 关于 Monty WebAssembly 移植的深度技术博客(https://simonwillison.net/2026/Feb/6/pydantic-monty/)。