资讯动态

Agent开发必知:沙箱是什么?如何搭建安全代码执行环境

发布时间:2026/10/15 11:30:37 来源:尧图企业网站定制
Agent 天天挂在嘴边的沙箱到底是个啥在 Agent 开发圈子里泡久了你会发现一个高频词——沙箱。做 Agent 的聊安全聊隔离要提它做代码生成的聊执行环境也要提它就连做数据分析类工具的处理用户上传的可疑文件时还是离不开它。不少刚开始接触 Agent 开发的朋友问我这沙箱到底是个啥为什么人人都要挂在嘴边简单说沙箱就是一种受限的执行环境。它把不信任的代码、文件、进程统统圈在一个独立空间里跑在空间里随便折腾出了这道物理或逻辑的墙外部系统不受任何影响。听起来像给代码套了个玻璃罩对吧。你要是写过插件系统或者处理过不可信输入应该深有体会不加沙箱一个模型生成的代码一旦带着恶意或者写崩了可就不是报错那么简单可能直接把宿主进程拖垮甚至删掉本地文件。这篇文章我不打算念概念就从一个 Agent 开发者的实际视角把沙箱这东西讲透它解决什么问题主流方案怎么选自己搭一个多语言代码执行沙箱要怎么做以及我踩过的那些坑。不管你是刚入门 Agent 开发还是已经在自己搭建工具链这篇都能给你一些可直接抄走的思路。1. 先弄明白Agent 为什么离不开沙箱1.1 Agent 的“手”伸得太长了现在主流的 Agent 框架都在干一件事让大模型调用工具、执行代码、操作文件、访问网络。这听起来很强但隐患也非常明显——模型的输出本质上是概率性的它写的代码可能包含严重 bug可能调用危险系统接口甚至可能被恶意注入的提示词带偏输出一段破坏性脚本。举个例子让 Agent 帮忙处理一批 CSV 文件它能拿下文件内容、写个 pandas 脚本、在本地跑一遍、输出结果。这段脚本如果只是读读文件没问题可要是模型错误地调用了os.remove()删了原始数据或者写了个死循环把 CPU 打满就是一场灾难。而你根本没法在模型生成代码的时候提前判断它有没有问题因为代码是动态生成的不是人一行行审查过的。这就是沙箱存在的根本逻辑把代码执行这个高风险动作搬到可控环境里去。环境内随便跑环境外毫发无损。1.2 沙箱到底在隔离什么搞清楚沙箱的保护对象才能理解它的价值。我总结下来主要是三层隔离文件系统隔离。沙箱内的程序看不到宿主机上的真实文件只能访问一个虚拟的、临时的文件视图。这样哪怕脚本执行rm -rf /删的也是沙箱里的虚拟根目录不是真实系统。网络隔离。沙箱里的网络访问可以被完全切断或者被代理到可控通道。这对防范数据泄露很关键沙箱内的程序默认就偷不走宿主机的敏感数据。系统资源隔离。CPU、内存、进程数量都要受限防止失控程序把宿主机拖死。这三个维度缺一个沙箱的安全性就打了折扣。你在评估任何沙箱方案时先问自己三个问题它能挡住路径穿越吗它能限制网络外联吗它能杀掉失控进程吗1.3 为什么不能直接裸跑有些人会觉得沙箱麻烦直接在本地跑不就完了反正模型生成的代码大概率没问题。我只能说这种想法很危险“大概率没问题”恰恰是最容易被利用的地方。以前我在本地直接跑过一个代码生成模型输出的脚本模型想统计一个 JSON 文件里所有年龄字段结果脚本里有个递归遍历逻辑写错了加上业务数据里正好有个循环引用结构直接栈溢出进程崩溃。这还算温和的至少只是崩溃。更麻烦的是那些“静默搞破坏”的脚本偷偷读了你电脑上的 SSH 私钥然后外传到某个服务器——这种你根本察觉不到。直到后来我养成了所有 Agent 代码执行都扔进沙箱的习惯才真正睡得着觉。2. 主流沙箱技术方案对比到底选哪种2.1 Docker 容器最常用的“标准答案”目前 Agent 领域用得最多的沙箱方案就是 Docker 容器。它容器化一个完整运行环境代码在容器内跑宿主机基本不感知。Docker 的好处是生态成熟、隔离性好、镜像即环境重装机器后迁移也方便。但 Docker 做沙箱有一些细节要注意。默认情况下 Docker 容器和宿主机是共享内核的虽然文件系统、进程、网络有隔离但内核级漏洞可能导致容器逃逸。所以更严谨的做法是加--security-opt no-new-privileges禁掉提权挂载只读的根文件系统或用gVisor这类用户态内核做第二层防线。资源限制也是必须做的--cpus限制 CPU--memory限制内存--pids-limit限制进程数量。不要嫌麻烦这是标准的保命手段。2.2 云函数 / 无服务器平台免运维但要注意冷启动如果你不想自己维护 Docker 基础设施云函数类平台也可以当作一种“托管沙箱”。每次执行就是一次函数调用平台负责隔离和资源回收。这类方案的核心问题是冷启动和超时限制。有些平台冷启动要几百毫秒到几秒如果你的 Agent 要频繁、小粒度地执行代码片段延迟会很难受。超时限制也一样某些平台函数最长执行时间可能只有几分钟跑长任务得自己拆。另外平台不是为“沙箱”设计的它本质上是弹性计算里面的临时文件系统、网络策略都需要你专门配置。如果你的场景是生产环境的 Agent 服务云函数是省心的选择如果是本地开发调试还是老老实实装 Docker。2.3 Wasm 和 Firecracker轻量与强隔离的走向除了 Docker另外两个方向值得关注。一个是 WebAssemblyWasm运行时比如 Wasmtime、Wasmer它们提供进程内的高性能隔离启动时间以微秒计内存占用极低。适合执行小的纯计算任务但系统调用支持、多线程支持都不如容器成熟。另一个是 Firecracker它是轻量级虚拟化每个实例一个小虚拟机隔离级别和云服务器持平启动时间在百毫秒内适合大规模多租户场景。但运维成本更高性能开销也比容器大。选型时有个基本判断你的业务在隔离强度、性能、资源开销、运维复杂度之间最在乎哪一头。没有完美的方案只有当前场景下最合适的方案。我这里给一个参考表格方案隔离强度启动速度资源开销运维复杂度适用场景Docker中等偏强百毫秒级中低大多数 Agent 代码执行首选gVisor Docker强几百毫秒中中多租户、高安全要求场景Firecracker最强百毫秒级高高大规模隔离需求安全优先Wasm强受限微秒级极低低纯计算、小函数、插件系统云函数强托管数百毫秒~数秒低极低快速上线、不想运维2.4 我最终的选型思路做 Agent 工具链这几年我自己的标准流程是默认 Docker 沙箱按任务类型调整。如果只是执行python -c算个哈希、做个小测试就直接用docker run --rm临时跑一个容器秒启秒杀。如果要支撑正式的 Agent 服务就维护一个常驻的沙箱集群配合任务队列做代码执行的编排。真正的核心逻辑是把“代码执行”当成一项基础设施来对待而不是一个临时函数。需要机制化、策略化要能监控、能限制、能审计。后面第三部分讲到我实际搭建的方案就是按这个思路来的。3. 手把手搭建一个多语言代码执行沙箱3.1 明确需求你的沙箱需要支持什么在动手前先定义清楚需求范围。我的场景是一个辅助数据分析的 Agent它需要执行用户上传的 Python 脚本来处理表格数据偶尔也要执行一些简单的 Node.js 脚本。这个场景决定了几个硬性约束沙箱内必须预装 Python 和 Node.js 环境以及常见的数据分析库。超时控制必须严格默认 Python 脚本超时 30 秒长任务超过 5 分钟就强制终止。内存限制单次执行最多 512MB防死循环吃满内存。临时文件每次执行产生的结果文件要能取回但沙箱内文件系统必须隔离。这些约束听起来简单但每一个都对应具体实现细节。3.2 准备持久化镜像而不是每次现装老手都知道Docker 沙箱最痛苦的是环境初始化如果每次跑都docker run python:latest然后pip install pandas光环境准备就得几十秒。正确做法是做一个持久化镜像把常用依赖预装好。我的 Dockerfile 大致长这样FROM python:3.11-slim RUN apt-get update apt-get install -y --no-install-recommends \ nodejs npm curl \ rm -rf /var/lib/apt/lists/* RUN pip install --no-cache-dir pandas numpy scipy scikit-learn \ requests beautifulsoup4 WORKDIR /workspace CMD [/bin/bash]构建好后应用就固定围绕这个镜像跑。这样沙箱启动时间就能压缩到几百毫秒以内体验和裸跑本地脚本几乎没差别。3.3 核心执行逻辑Docker SDK 与资源限制真正的执行逻辑用 Docker SDK 做容器编排。步骤大概是根据任务选择镜像。创建容器时设置资源限制、网络限制、只读挂载。将用户代码和输入文件挂载进容器。运行容器并等待结果。超时则kill容器取回输出。核心代码片段import docker import os import uuid client docker.from_env() def run_in_sandbox(code, input_filesNone, timeout30): run_id uuid.uuid4().hex[:8] host_workspace f/tmp/sandbox_workspace/{run_id} os.makedirs(host_workspace, exist_okTrue) # 写入代码文件 with open(f{host_workspace}/main.py, w) as f: f.write(code) # 复制输入文件 if input_files: for src, name in input_files: with open(f{host_workspace}/{name}, wb) as f: f.write(src.read()) container client.containers.run( imagemy-agent-sandbox:latest, commandfpython /workspace/main.py, working_dir/workspace, volumes{host_workspace: {bind: /workspace, mode: rw}}, network_disabledTrue, # 默认禁止网络 mem_limit512m, # 内存上限 cpu_period100000, cpu_quota50000, # 限制 50% CPU pids_limit256, # 限制进程数 detachTrue, removeFalse, ) try: result container.wait(timeouttimeout) logs container.logs(stdoutTrue, stderrTrue).decode(utf-8) output_files _collect_outputs(container, host_workspace) finally: container.remove(forceTrue) shutil.rmtree(host_workspace, ignore_errorsTrue) return { exit_code: result[StatusCode], logs: logs, outputs: output_files }这段代码里几个参数值得展开讲network_disabledTrue直接切断容器网络。数据分析场景绝大多数不需要联网如果真的需要我会专门提供一个开了网络的白名单镜像用--networkbridge但配 iptables 规则限制只能访问特定域名。mem_limit512m限制内存防止脚本吃满宿主内存。这个值要测试调优太小了跑正常脚本也 OOM太大了失控风险高。pids_limit256限制进程数防 fork bomb。提示如果你打算在生产环境接这个逻辑一定要把宿主机的/tmp/sandbox_workspace放到独立磁盘分区并开启挂载点的nosuid、nodev选项避免容器利用文件系统的特殊权限位提权。3.4 超时与清理的双保险代码执行最怕的就是失控。超时控制必须同时有两层Docker SDK 的container.wait(timeout...)是一层但它不够可靠因为高负载下wait可能延迟返回更稳的是配合 Python 层的threading.Timer做强制 kill。import threading def _watchdog(container, timeout): timer threading.Timer(timeout, _force_kill, args(container,)) timer.start() return timer def _force_kill(container): try: container.kill() except Exception: pass实际我在跑 Agent 服务时还会在容器外部加一层系统级的监控每个沙箱容器带一个固定的name前缀比如ag-sandbox-系统每 5 分钟检查一次发现有超过 15 分钟还存活的任务直接docker rm -f。这是一条兜底的兜底因为你永远不知道上层逻辑会不会出 bug。清理方面removeFalse是为了在容器异常退出时还能保留日志排查问题。一旦确认任务结束finally块里做强制清理加删除宿主机临时目录一条都不能少。磁盘被占满是生产事故最常见的原因我吃过这个亏。3.5 取回结果文件Agent 执行完脚本后往往要取回脚本生成的结果文件比如处理后的 CSV、绘制的图表。实现思路是输出文件统一写到/workspace/output/目录宿主机上对应的是{host_workspace}/output/。容器跑完后直接扫描宿主机的目录把文件按二进制流读到内存返回给调用方。def _collect_outputs(container, host_workspace): output_dir os.path.join(host_workspace, output) if not os.path.exists(output_dir): return [] results [] for fname in os.listdir(output_dir): with open(os.path.join(output_dir, fname), rb) as f: results.append({name: fname, content: f.read()}) return results有一点要提醒如果脚本生成的文件很大比如几十 MB你直接全量读进内存会爆。更稳妥的做法是给输出文件做个大小限制超过阈值就不读了或者在容器内把文件分块传到对象存储。4. 沙箱落地必踩的坑与排查思路4.1 路径隔离容器内的hidden文件陷阱刚开始做沙箱时我犯过一个低级错误把宿主机的某个工作目录直接挂载进容器结果发现容器内的脚本可以读到宿主机上的其他文件。原因很简单——挂载的是整个父目录而不是子目录。比如你有/home/user/projects/agent_sandbox/tmp你不想让容器访问项目的其余部分结果挂载时写成了/home/user/projects/agent_sandbox那容器里就能看到里面所有文件。排查思路构建容器后主动跑一个ls -la /workspace/..看看能不能看到外部目录结构。不能只靠“我挂载了这个目录”来推论安全要实际测试“我没挂载的目录是否真的不可见”。4.2 网络策略你以为禁了其实还能连network_disabledTrue这个参数在 Docker 里确实能禁掉网络但有几个例外。第一如果你构建镜像时执行过需要网络的步骤这些步骤产生的 DNS 缓存等不会影响容器运行时但镜像层里的网络工具如curl仍然可用第二如果容器的网络模式是host或者bridge但未正确禁用网卡依然可能外联。我实际的经验是单靠 Docker 网络参数不可靠还要配合防火墙规则。在宿主机上用 iptables 给 Docker 容器网络做白名单默认禁止所有容器访问外部网络只放行特定网段或域名。具体做法是给容器的 IP 段单独建一条 FORWARD 链规则。这属于“宁可多做一层不要少做一层”的事情。4.3 镜像膨胀依赖越装越多启动越来越慢持久化镜像一开始只有 300MB装了几个库后膨胀到 1GB启动时间从 300ms 涨到 800ms。这还不算最糟糕的。更麻烦的是时间一长镜像里堆了一堆“当时可能用到”的工具镜像根本不记得自己装过什么但每个工具都可能带来额外的攻击面。我的做法是多阶段构建 分版本管理。基础镜像只装语言运行时和系统依赖数据分析库放在一个 secundary 镜像层特定项目需要的库再放第三层。每次更新镜像都记录在案用类似 Docker tag 的版本号管理方便回滚。另外定期用docker image ls查看哪些镜像长期未用默默清理掉。镜像管理也是安全的一部分这一点容易被忽略。4.4 高并发下的资源竞争当你的 Agent 服务同时跑 20 个沙箱容器时资源问题就会浮出来。Docker 的内存限制是容器级的但如果你不设置--pids-limit和 CPU 配额20 个容器可以吃满宿主机所有内核和内存。更隐蔽的是磁盘 I/O 竞争20 个沙箱同时读写宿主机的/tmp目录磁盘 IOPS 会飙到很高其他服务跟着遭殃。我的解决方案是给每个容器挂载独立的临时目录并且在宿主机上用ulimit限制容器的文件系统写入限额比如--storage-opt size2G。同时沙箱集群的调度要控制最大并发数在入口处接一个简单的信号量比如同时最多跑 10 个容器剩下的排队等待。毕竟沙箱是消耗资源的必须像管理数据库连接池一样管理它。5. 进阶经验从单机沙箱到沙箱服务化5.1 抽象一层沙箱接口当你从小规模实验转向正式产品直接调用 Docker SDK 的方式会显得不够优雅。更好的做法是抽象出一个沙箱服务层统一提供“执行代码”和“获取结果”的接口。这样后期就算你把底层从 Docker 换到 gVisor 或云函数上层 Agent 逻辑完全不用动。接口大致长这样class SandboxExecutor: def execute(self, language: str, code: str, inputs: list, timeout: int) - SandboxResult: 语言只支持 python / nodejs 返回 SandboxResult包含 exit_code, stdout, stderr, output_files 在接口内部语言参数映射到不同镜像。超时参数从配置读取默认 30 秒任务类型调整超时值。这样一来Agent 层的代码执行调用方式变得很统一也方便以后加监控和日志。5.2 多语言支持Node.js 和动态工具链上面讲的都是 Python但 Agent 场景里 JavaScript/TypeScript 的需求也不少。Node.js 沙箱的思路和 Python 基本一致只是镜像基础换成node:20-slim命令改成node /workspace/main.js。这里有个值得注意的问题Node.js 沙箱里装 npm 包比 pip 麻烦得多因为 npm 依赖树很膨胀而且很多包依赖原生编译工具。我的做法是预先在镜像里装好常用的通用 npm 包比如axios、lodash、fast-csv项目的特定依赖由用户在沙箱内自行安装——但会加一道限制禁止在沙箱内执行npm install时的网络请求既然已经禁网了。这对某些场景来说是限制但安全优先。5.3 监控与审计沙箱不能黑盒生产中沙箱必须可观察。每个容器执行要记录日志谁在什么时间发起了执行执行了哪段代码可以做脱敏容器的退出码和资源使用情况是多少。这不仅是为了排查故障更是安全审计的基础——一旦有异常操作你要能追溯。我实践中会在宿主机的/var/log/agent-sandbox/按日期记录 JSON 格式的日志{ timestamp: 2025-01-18T10:00:00.123Z, run_id: a1b2c3d4, image: my-agent-sandbox:latest, language: python, timeout_seconds: 30, container_id: ab12cd34ef56, exit_code: 0, cpu_percent: 12.5, memory_bytes: 104857600, logs_tail: Processing complete... }日志记录本身占用不了多少存储但关键时刻能救命。另外容器的 stdout/stderr 日志只保留尾部防止沙箱里脚本狂打日志把磁盘写满。5.4 一键部署沙箱服务的 Compose 方案如果你要部署一套自己的沙箱服务可以借鉴我的 docker-compose 基础模板version: 3.8 services: sandbox-api: build: . ports: - 8080:8080 volumes: - /var/run/docker.sock:/var/run/docker.sock - /tmp/sandbox_workspace:/tmp/sandbox_workspace - /var/log/agent-sandbox:/var/log/agent-sandbox environment: - MAX_CONCURRENCY10 - DEFAULT_TIMEOUT30 restart: unless-stopped注意把宿主机 Docker socket 挂载进 API 容器这本身就是个风险点——一旦 API 服务被打穿攻击者相当于拿到了 Docker 管理权限。更稳妥的方案是 API 服务只通过 TCP 远程连接 Docker API并开启 TLS 认证不让 socket 文件直通业务容器。6. 最后再分享几个私藏经验沙箱这套东西技术上不复杂但工程细节多得让人头疼。我最后分享几个自己实践中的心得都是普通文档里不太会写的东西。第一不要在容器内跑 root 用户。在 Dockerfile 里加一行USER nobody或者容器创建时指定user1000:1000。很多破坏性操作在非 root 权限下会直接失败这比其他安全机制都来得粗暴有效。第二小心“镜像内命令也被注入”。如果你的沙箱只是执行 Python 代码风险相对可控但如果你的 Agent 直接把用户/模型生成的 shell 命令拼进bash -c那隔离再强也架不住注入。任何时候都不要把不可信字符串直接拼进 shell 命令要用参数数组形式传给容器命令。这条是从代码生成工具踩坑得来的教训——模型生成的命令里经常混着奇怪字符。第三沙箱内的临时文件也要保密。沙箱虽然隔离了文件系统但如果沙箱内跑了恶意代码它完全可以在容器内读取/workspace里的所有输入文件。所以传给沙箱的敏感数据要做好脱敏处理——这些代码可能“看到”数据你就得假设它“会看到”数据。安全不是靠沙箱一堵墙而是靠分层防御。第四沙箱不是万能的。它能挡住恶意代码直接伤害系统但挡不住所有间接攻击。比如模型被注入恶意提示词它可能通过“间接”方式让 Agent 进行钓鱼攻击、传播虚假信息——这些是沙箱解决不了的需要在 Agent 的任务设计、工具权限、输出审查等更高层面防范。把沙箱当成 Agent 系统的最后一道防线而不是唯一防线心态会健康很多。第五性能预期要定好。Docker 沙箱单次执行大约有 100~300ms 的固定开销这在大多数场景可以接受但如果你的 Agent 要在一个对话里执行几十次代码片段累积延迟会非常明显。好的做法是尽量合并代码执行次数——你甚至可以设计一个“批量执行”接口让 Agent 一次性提交多个脚本到同一个沙箱里跑。这比来回启停容器高效得多。沙箱确实是个又基础又深的东西。基础到每个 Agent 开发者都要面对深到可以延伸出 gVisor、Kata、Firecracker 这些专门做隔离的运行层面。如果这篇文章能帮你搞明白它到底在解决什么问题、为什么 Agent 离不开它那就达到目的了。实际动手搭一个自己的沙箱服务然后把 Agent 接进去跑一遍很多纸上谈兵的困惑自然就解开了。我个人觉得做 Agent 开发最大的乐趣就是把这种“危险动作”控制得滴水不漏的过程那种踏实感是写好一个 prompt 替代不了的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑