第4章 构建执行沙盒与安全边界
本章你将学到:
- 为什么不能让AI生成的代码直接在你的电脑上裸跑
- 用纯Python实现一个轻量级执行沙盒,不需要Docker
- 沙盒的五层安全机制:临时文件、超时终止、模块白名单、输出限制、网络隔离
- 如何测试沙盒本身是否可靠
- 将沙盒封装为Agent可调用的
execute_python工具本章你将产出:一个经过安全测试的执行沙盒模块
sandbox.py,以及一个新增了execute_python工具的Agent
全部章节:收录在专栏《AI应用工程化实战教程》之【智能体工具使用实战】
4.1 一个必须面对的问题
第3章结束时,你的Agent能读文件了。它成功读取了scores.csv,描述了数据的基本情况——有多少行、多少列、各列的名称。
但有一个问题它始终没解决:计算。
你让它分析成绩数据,它能告诉你“这份数据包含10名学生、4门课程”,但当你追问“高数的平均分是多少”时,它要么凭“感觉”估算一个数字,要么老老实实告诉你“我无法执行计算,建议你使用pandas的mean()函数”。
你在第1章就见过这个场景了。现在Agent有了read_file,但它依然缺一只真正能干活的“手”——执行代码的能力。
如果Agent能调用一个execute_python工具,它就可以自己写一段pandas代码,在数据上跑一遍,拿到精确的结果。这正是我们要添加的第二个工具。
但在动手之前,有一个必须正视的问题:AI生成的代码,你敢直接在自己的电脑上跑吗?
4.2 AI生成的代码不可信
2024年,一位开发者在让AI帮他写一段数据处理脚本时,AI生成的代码里包含了一行os.remove("/")。幸亏他在执行前看了一眼。
2025年,另一个开发者让AI写一段测试脚本,AI引入了一个while True的死循环。他的电脑风扇狂转了五分钟,直到强制关机。
这不是AI“故意作恶”。而是语言模型不理解“安全”——它只是根据训练数据中的模式生成文本。它不知道os.remove("/")意味着什么,不知道死循环会让CPU占满,不知道有些库你不希望它调用。
当你的Agent学会执行代码之后,它会自己写代码、自己执行。你不是那个在编辑器里写完代码、检查一遍、再点运行的人。Agent是一个自动化的流程——它收到任务,生成代码,调用execute_python,然后拿结果。
这意味着:你必须给Agent造一个受控的执行环境,让它在里面“随便搞”,搞不出事。
这个受控的执行环境,就是沙盒。
4.3 轻量级沙盒的设计思路
提到“沙盒”,很多人首先想到Docker容器——完整的文件系统隔离、网络隔离、资源限制。Docker确实是工业级沙盒的标准方案,但它有两个问题:
第一,学习曲线陡。装Docker、理解镜像、配置容器、处理权限——这对第一次接触容器化的本科生来说,认知负荷远超本章的教学目标。我们的重点是“让Agent安全地执行代码”,不是“学会Docker”。
第二,它太重了。你只是想让Agent执行一段pandas计算,就启动一个完整的Linux容器?光是容器启动的时间可能比代码执行本身还长。
好在,对于我们的场景——执行有限功能的Python代码片段——我们完全可以用纯Python实现一个足够安全的轻量沙盒。
设计思路很简单:
| 安全目标 | 实现方式 |
|---|---|
| 防止删除/修改你的文件 | 代码写入临时文件,执行完自动删除 |
| 防止死循环占满CPU | 设置超时时间,超时强制终止 |
| 防止调用危险函数 | 限制可用的模块,只允许pandas、numpy、math等安全模块 |
| 防止输出撑爆内存 | 限制stdout和stderr的最大长度 |
| 防止访问网络 | 在subprocess中不提供网络访问(或通过防火墙规则) |
核心实现就靠一个Python标准库模块:subprocess。它能启动一个独立的子进程,在这个子进程里执行代码。子进程有自己的内存空间,出问题不会影响主进程。你可以设置超时,超时了就杀掉子进程。你可以捕获它的输出,限制输出大小。执行完毕,子进程销毁,一切都消失。
4.4 沙盒的完整实现
4.4.1 用Trae生成沙盒代码
在Trae的AI对话面板中输入:
在项目>4.4.2 审查生成的代码Trae生成sandbox.py后,打开它,对照以下清单逐项检查:
核心实现检查:
- 是否使用了
tempfile.NamedTemporaryFile? - 文件后缀是否为
.py? - 是否设置了
delete=False(因为需要在另一个进程中读取)? - 执行后是否用
os.unlink()手动删除了临时文件? - 是否使用了
subprocess.run并传入了timeout? - 返回格式是否与要求一致?
安全机制检查:
- 是否定义了白名单模块列表?
- 是否在代码中扫描了
import语句并与白名单对比? - 是否定义了危险关键字列表并扫描了代码?
- stdout和stderr是否做了截断?
- 异常情况(超时、代码中有危险操作)是否返回了清晰的错误信息?
如果发现缺失或实现不当,在Trae对话面板中要求修正。例如:
sandbox.py 中,你只检查了 "import os" 这种形式,但 "from os import system" 这种形式不会被检测到。请同时检查两种 import 形式。
或者:
sandbox.py 中,白名单模块检查后如果发现非法模块,应该返回 "success": false 和具体的错误信息,而不是抛出异常。
以下是沙盒核心逻辑的参考实现(供对照,不是让你手写):
importsubprocessimporttempfileimportos# 模块白名单ALLOWED_MODULES={'pandas','numpy','math','statistics','json','csv','collections','itertools','datetime','re','string','decimal','fractions'}# 危险关键字DANGEROUS_KEYWORDS=['os.system','subprocess','eval(','exec(','__import__','shutil','sys.','os.','pathlib','requests','urllib','socket','ftp','http']defcheck_code_safety(code:str)->tuple:"""检查代码是否安全。返回 (is_safe, error_message)"""# 检查危险关键字forkeywordinDANGEROUS_KEYWORDS:ifkeywordincode:returnFalse,f"代码包含危险操作:{keyword}"# 检查 import 语句(简单字符串扫描)import_lines=[lforlincode.split('\n')ifl.strip().startswith('import ')orl.strip().startswith('from ')]forlineinimport_lines:# 提取模块名parts=line.strip().split()ifparts[0]=='import':modules=[p.split('.')[0]forpinparts[1:]ifp!='as']else:# from X import Ymodules=[parts[1].split('.')[0]]formodinmodules:ifmodnotinALLOWED_MODULES:returnFalse,f"不允许导入模块:{mod}(白名单之外的模块)"returnTrue,None
4.5 安全机制逐层拆解
现在花一点时间,理解沙盒每一层安全机制的设计逻辑。这不仅是审查代码,更是培养设计安全系统的工程思维。
第一层:临时文件隔离
每次执行代码,都创建一个随机的临时文件(如/tmp/tmpabc123.py),把代码写进去,执行完立刻删除。这样做的好处是:即使代码里写入了某些文件,也只会影响临时文件(已经被删了),不会污染你的项目目录。每次执行都是“全新的”,上一次执行的残留不会影响下一次。
第二层:超时终止
subprocess.run(timeout=10)意味着如果代码在10秒内没有执行完,子进程会被强制杀死。这防止了死循环永远运行下去。10秒是一个合理的默认值——大多数数据分析操作(计算平均值、筛选数据)都在1秒内完成。如果你的数据特别大,可以在调用时传入更大的timeout。
第三层:模块白名单
这是最核心的一层。代码在执行前会被扫描——它导入了哪些模块?如果发现不在白名单里的模块,直接拒绝执行。
白名单的设计原则是最小权限:只给Agent完成任务必需的模块,不给多余的。数据分析需要pandas和numpy,数学计算需要math,文件解析需要csv和json。os和sys不需要——Agent不应该有能力操作操作系统。requests不需要——Agent不应该能发起网络请求。
第四层:输出限制
如果Agent写了一段代码,生成了10万行输出,你的终端和内存都会撑爆。所以 stdout 和 stderr 各截断到5000字符——足够看到计算结果和错误信息,但不会造成资源问题。
第五层:危险关键字扫描
模块白名单能拦住import os,但如果Agent不写import而是直接调用内置的危险函数呢?危险关键字扫描就是为此设计的——它直接在代码文本中搜索os.system、subprocess、eval、exec等字符串。只要出现,就拒绝执行。
当然,这种基于关键字扫描的方式不是100%安全的(比如有人可以把危险代码混淆),但对于我们的教学场景已经足够。工业级沙盒会使用更复杂的技术(如seccomp、ptrace),但那超出了本书的范围。
4.6 【避坑指南】AI生成代码的五种危险操作
当Agent开始自己写代码并自动执行时,你可能会遇到以下情况。提前了解,能帮你快速诊断问题。
危险操作 表现 沙盒防御层 如果沙盒没有拦住 死循环 while True: pass超时终止(10秒后强制杀掉) CPU风扇狂转,Agent卡住不动 文件删除 os.remove("important.txt")模块白名单(os不允许)+ 临时文件隔离 项目文件被删除 网络请求 requests.get("恶意网址")模块白名单(requests不允许)+ 危险关键字 数据泄露 系统命令 os.system("rm -rf /")危险关键字扫描 + 模块白名单 灾难性后果 内存炸弹 [0] * 10**12或无限递归超时终止(内存耗尽前进程被杀死) 系统卡死
如果Agent生成的代码被沙盒拦截了怎么办?
这本身就是一个很好的学习机会。在终端日志里,你会看到Agent调用了execute_python,但沙盒返回了{"success": false, "error": "代码包含危险操作:os.system"}。Agent拿到这个结果后,通常会尝试修改代码——去掉危险操作,换一种安全的方式。这个过程就是“Agent在沙盒的约束下学会安全编程”。
如果你想让Agent更好地理解沙盒的限制,在它的系统提示词中加入:
你可以使用 execute_python 工具来执行Python代码。但请注意: - 只允许使用以下模块:pandas, numpy, math, statistics, json, csv, collections, itertools, datetime, re, string, decimal, fractions - 不要使用 os、sys、subprocess、requests 等模块 - 代码必须在10秒内完成执行 - 如果沙盒拒绝了你的代码,请检查是否有不允许的导入或操作,修改后重试
4.7 测试沙盒本身是否可靠
沙盒是给你Agent用的安全基础设施。在把它交给Agent之前,你需要先验证它自己是否可靠。
创建test_sandbox.py:
"""测试沙盒的安全性和功能"""fromsandboximportexecute_python# 测试1:正常代码print("测试1:正常计算")result=execute_python("print(sum([1,2,3,4,5]))")print(f"结果:{result}\n")# 测试2:死循环(应该被超时终止)print("测试2:死循环")result=execute_python("while True: pass",timeout=3)print(f"结果:{result}\n")# 测试3:尝试导入 os(应该被白名单拦截)print("测试3:尝试导入os")result=execute_python("import os\nprint(os.getcwd())")print(f"结果:{result}\n")# 测试4:尝试执行系统命令(应该被关键字扫描拦截)print("测试4:尝试执行系统命令")result=execute_python("import os\nos.system('ls')")print(f"结果:{result}\n")# 测试5:超长输出(应该被截断)print("测试5:超长输出")result=execute_python("for i in range(10000): print(f'Line {i}')")print(f"stdout长度:{len(result.get('stdout',''))}\n")# 测试6:pandas 操作(应该正常执行)print("测试6:pandas操作")result=execute_python(""" import pandas as pd data = {'name': ['Alice', 'Bob'], 'score': [90, 85]} df = pd.DataFrame(data) print(df.describe()) """)print(f"结果:{result}")
运行测试:
python test_sandbox.py
期望结果:
- 测试1:返回
success: true,stdout为15 - 测试2:返回
success: false,error中包含“超时” - 测试3:返回
success: false,error中包含“不允许导入模块:os” - 测试4:返回
success: false(要么被模块白名单拦,要么被关键字扫描拦) - 测试5:返回
success: true,但stdout被截断到5000字符 - 测试6:返回
success: true,stdout中包含pandas的描述统计
如果所有测试都通过,你的沙盒可以交付给Agent使用了。
4.8 将沙盒封装为Agent工具
沙盒就绪,现在把它添加到Agent的工具箱里。
4.8.1 设计 execute_python 工具描述
在Agent看来,execute_python是一个可以执行Python代码的工具。它需要知道:这个工具能做什么、什么时候该用它、有什么限制。
打开agent.py,在Trae对话面板中输入:
请修改 agent.py,增加第二个工具 execute_python。 工具描述: - 名称:execute_python - 用途:在安全沙盒中执行Python代码,返回执行结果。适用于需要计算、数据处理、统计分析的任务。 - 限制:只允许使用 pandas, numpy, math, statistics, json, csv, collections, itertools, datetime, re, string, decimal, fractions 模块。代码需在10秒内完成。不支持网络访问、文件系统操作(除了沙盒内部的临时文件)。 - 参数: - code(必填,字符串):要执行的Python代码 - timeout(可选,整数,默认10):超时时间(秒) 实现: - 导入 sandbox 模块中的 execute_python 函数 - 在 TOOL_MAP 中添加 "execute_python" - 在 tools 列表中添加新工具的定义 - 在系统提示词中补充:当需要计算、统计、数据处理时,使用 execute_python 工具
4.8.2 审查新增的工具代码
确认以下几点:
- 工具定义中的
name是否与TOOL_MAP的键一致? description中是否说明了限制(白名单模块、超时)?- 系统提示词中是否更新了
execute_python的使用指引? sandbox.execute_python的导入路径是否正确?
4.9 集成测试:读取数据并执行计算
现在agent.py有了两个工具:read_file和execute_python。
运行以下测试,观察Agent如何组合使用它们:
python agent.py
修改agent.py底部的测试请求为:
run_agent("请读取 scores.csv,然后计算高数的平均分。")
观察终端日志。你期望看到的流程是:
第1轮:Agent调用 read_file("scores.csv") 第2轮:Agent调用 execute_python("import pandas as pd\ndf = pd.read_csv('scores.csv')\nprint(df['高数'].mean())") 第3轮:Agent基于两次工具调用的结果,生成最终分析
如果Agent在第2轮写入的代码中尝试df = pd.read_csv('scores.csv'),这是可以的——pandas在白名单中,read_csv是pandas的正常功能。沙盒不会拦截它。
但注意:沙盒中的代码执行环境和Agent的主进程是隔离的。在沙盒里pd.read_csv('scores.csv')能读到文件,是因为scores.csv在项目根目录,而沙盒子进程的工作目录继承了主进程的目录。如果你想让沙盒更安全,可以在sandbox.py中切换工作目录到一个临时文件夹——但那样Agent就必须在代码中处理文件路径问题。当前方案在安全性和可用性之间取了一个平衡。
4.10 本章小结
- AI生成的代码不可信——语言模型不理解安全,它生成的代码必须放在受控环境中执行。
- 轻量级沙盒不需要Docker——用
subprocess+tempfile+ 超时 + 模块白名单,纯Python即可实现足够安全的执行环境。 - 五层安全机制:临时文件隔离、超时终止、模块白名单、输出限制、危险关键字扫描。每一层解决一类安全威胁。
- 沙盒本身必须先测试——在交给Agent使用之前,用各种攻击场景验证沙盒的可靠性。
- Agent现在有了两只手:
read_file读取数据,execute_python执行计算。组合使用,它就能完成第1章那个“分析成绩单”的任务。
下一章,我们将为Agent添加第三个工具——write_file,并构建一个完整的ToolManager类来管理工具箱。Agent将能够读取数据、执行计算、保存分析报告——一个完整的数据分析闭环。
课后练习
- 运行
test_sandbox.py中的全部六个测试,确认沙盒正常工作。如果某个测试失败了,分析是哪一层安全机制没有生效。 - 修改
sandbox.py,在白名单中增加matplotlib.pyplot(但matplotlib可能依赖os等模块——这会触发什么安全拦截?你如何解决这个矛盾?) - 运行集成测试——让Agent读取
scores.csv并计算高数平均分。如果Agent在代码中写了import os然后被沙盒拒绝,观察Agent的后续行为——它会怎么修改代码? - (进阶)在
sandbox.py中增加第六层安全机制:限制子进程的内存使用。提示:在Linux/macOS上可以使用resource模块,在Windows上可以研究job objects。