【智能体工具使用实战04】构建执行沙盒与安全边界 第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是否设置了deleteFalse因为需要在另一个进程中读取执行后是否用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:# 提取模块名partsline.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,None4.5 安全机制逐层拆解现在花一点时间理解沙盒每一层安全机制的设计逻辑。这不仅是审查代码更是培养设计安全系统的工程思维。第一层临时文件隔离每次执行代码都创建一个随机的临时文件如/tmp/tmpabc123.py把代码写进去执行完立刻删除。这样做的好处是即使代码里写入了某些文件也只会影响临时文件已经被删了不会污染你的项目目录。每次执行都是“全新的”上一次执行的残留不会影响下一次。第二层超时终止subprocess.run(timeout10)意味着如果代码在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正常计算)resultexecute_python(print(sum([1,2,3,4,5])))print(f结果:{result}\n)# 测试2死循环应该被超时终止print(测试2死循环)resultexecute_python(while True: pass,timeout3)print(f结果:{result}\n)# 测试3尝试导入 os应该被白名单拦截print(测试3尝试导入os)resultexecute_python(import os\nprint(os.getcwd()))print(f结果:{result}\n)# 测试4尝试执行系统命令应该被关键字扫描拦截print(测试4尝试执行系统命令)resultexecute_python(import os\nos.system(ls))print(f结果:{result}\n)# 测试5超长输出应该被截断print(测试5超长输出)resultexecute_python(for i in range(10000): print(fLine {i}))print(fstdout长度:{len(result.get(stdout,))}\n)# 测试6pandas 操作应该正常执行print(测试6pandas操作)resultexecute_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: truestdout为15测试2返回success: falseerror中包含“超时”测试3返回success: falseerror中包含“不允许导入模块os”测试4返回success: false要么被模块白名单拦要么被关键字扫描拦测试5返回success: true但stdout被截断到5000字符测试6返回success: truestdout中包含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——用subprocesstempfile 超时 模块白名单纯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。