这段时间一直在折腾本地知识库 Agent结果它为了完成“清理缓存”这个指令居然自己写了一串 Shell 命令差点把 npm 缓存目录整个删掉。更让我后怕的是如果那次工具调用不是在隔离环境里跑的我的开发机系统目录可能已经被搅乱了。后来我换上了这个开源 Agent SandboxHullwork把 Agent 的代码执行、工具调用、文件访问全部关进沙箱里才终于敢让 Agent 去碰那些“听起来有点危险”的任务。Hullwork 是一个面向 AI Agent 运行环境的开源沙箱项目核心定位很直接给 Agent 一个可控的“笼子”让它在里面跑命令、调工具、读写文件但跑不出边界。它解决了目前 Agent 开发中最头疼的安全问题——模型生成的代码和工具调用是不可信的但你又必须给它们一定的执行能力。这篇文章我会从设计原理、安装配置、策略编写、踩坑经验到生产落地几个层面完整讲一遍这个工具怎么用、为什么这么用适合正在做 Agent 开发、或者想把 Agent 安全地接到现有系统里的开发者参考。1. Agent 越权、命令注入、数据泄漏为什么 AI Agent 需要一个沙箱1.1 Agent 执行环境失控的真实场景先说个我自己的例子。我在做一个自动整理下载目录的 Agent它需要分析文件类型、移动文件、解压压缩包。模型本身没问题但它在一次对话中判断“下载目录里有个没用的旧版本安装包”于是自作主张执行了rm -rf ~/downloads/old-version。如果这个命令直接跑在宿主机上可能会把其他目录一起卷进去。类似的事故在 Agent 圈子里太常见了模型被 prompt injection 诱导读取了不该读的环境变量Agent 根据记忆拼出错误路径删除或覆盖了重要文件或者工具返回的内容里藏了恶意指令被模型当成真实输出继续执行。这些问题的根源不是模型不够聪明而是执行环境太“信任”Agent 了。操作系统本身没有为“一个可能被骗的 AI”提供细粒度的权限边界。普通的进程隔离只能做到用户级别但 Agent 需要在同一个用户下操作很多资源这时你需要的不是传统的“权限分离”而是“能力边界”——允许它做某些事但把事情限制在一个受控的范围内。1.2 沙箱不是虚拟机也不是容器Hullwork 的定位很多人一听沙箱就想到虚拟机或者 Docker 容器。虚拟机重启动慢资源开销大不适合高频次、短流程的 Agent 工具调用Docker 容器虽然是隔离的但默认情况下网络、文件系统、系统调用都还是太宽泛你需要再补一堆 Seccomp、Capabilities、只读挂载之类的配置而且每次启动新的 Agent 任务都要重新编排容器参数非常繁琐。Hullwork 的定位介于“裸进程”和“完整容器”之间。它更像一个专门为 Agent 设计的“执行上下文”通过操作系统级隔离技术给每个 Agent 工具调用提供一个独立的进程空间、受限的文件系统视图和可控的网络出口同时保持极低的启动延迟和资源占用。它不是要替代 Docker而是可以跑在 Docker 里面也可以直接跑在宿主机上作为 Agent 与系统之间的最后一道防线。1.3 Hullwork 能帮你控制哪些边界从使用角度Hullwork 主要管五件事命令执行边界Agent 能调用哪些系统命令、不能调用哪些由策略决定。文件访问边界Agent 能读哪些目录、写哪些目录其余路径都是“不存在”的。网络访问边界Agent 能访问哪些域名、哪些 IP 端口外网默认被挡住或按白名单放行。资源使用边界CPU、内存、文件大小、进程数量都有限额防止 Agent 失控占满机器。生命周期边界每次工具调用有超时时间超时自动终止不会留下僵尸进程。这些边界组合起来就等于你给 Agent 发了一张“工作许可证”它在许可证允许的范围内再怎么折腾都不会伤到系统本身。2. Hullwork 的核心设计从“隔离”到“管控”的层次拆解2.1 进程级隔离与系统调用过滤Hullwork 启动一个 Agent 任务时会先创建一套隔离的进程环境。它不像虚拟机那样模拟完整硬件而是直接复用宿主内核但通过命名空间和系统调用过滤把进程“关进笼子”。在 Linux 系统上它会为每个沙箱实例创建独立的 PID 命名空间、挂载命名空间和 IPC 命名空间。这意味着沙箱里的进程树是独立的同一个 ID 的进程在沙箱内外可以代表完全不同的东西沙箱内看不到宿主机的其他进程也就无法向外部进程发送信号或读取外部的进程列表。系统调用过滤这一层更关键。AI Agent 经常会用到一些底层能力比如修改文件权限、加载内核模块、挂载设备、绑定网络端口等。Hullwork 默认禁用了绝大多数高风险的 syscall只保留运行常规程序所需的那部分。这意味着即使 Agent 拿到了 root 权限在沙箱内部也无法执行mount、reboot、ptrace这类危险操作。这个设计是从 Seccomp 那儿借来的思路但 Hullwork 做了一层更贴近 Agent 场景的封装——你可以用配置文件直接声明“允许这个沙箱执行哪些系统调用”而不需要去记一串晦涩的 syscall 编号。2.2 文件系统视图与可写层策略文件系统是 Agent 最容易闯祸的地方。Hullwork 的处理方式很聪明它给每个沙箱建立一个虚拟根目录通过 overlayfs 把宿主机目录映射进来。映射时区分只读层和可写层。只读层Agent 可以读取的内容比如项目代码、依赖库、模型文件。可写层Agent 可以修改的内容通常是临时目录、输出目录、缓存目录。Agent 在沙箱里看到的根目录是一个“拼装”出来的视图它以为自己在读写/home/user/project实际上那只是宿主机某个目录的只读映射。如果 Agent 执行了rm -rf /home/user/project最多只能清掉可写层里的文件宿主机上的原始目录毫发无损。这个特性特别适合“让 Agent 尝试各种操作”的场景哪怕它把沙箱里的文件系统删得干干净净只要重置沙箱一切又回到初始状态。可写层的另一个好处是支持“提交/回滚”。Agent 跑完一个任务后你可以审查可写层里发生了哪些变化选择哪些文件允许写回宿主机哪些丢弃。这种“先执行后审核”的模式比传统的权限控制更适合 AI 场景因为模型的行为很难提前预测但你可以在事后精确控制影响范围。2.3 网络访问控制与密钥保护网络权限是另一个重灾区。Agent 需要访问 API 获取数据但如果让它直接上网它可能把内网服务、云平台的元数据接口都暴露出去。Hullwork 的网络策略支持三种模式完全隔离、白名单模式、全放行模式。默认推荐白名单模式你需要在配置里写明允许访问的域名或者 IP 段其他网络请求统统拒绝。这里有个很关键的细节很多开发者会在环境变量里塞OPENAI_API_KEY、数据库密码之类的敏感信息。如果 Agent 能在沙箱里读取这些环境变量并且网络策略又放得太宽那这些密钥很可能被模型通过某种方式带出去。Hullwork 提供了一个“环境变量遮蔽”功能你可以把某些环境变量标记为redact沙箱内的进程读取到的值会是***或者直接不存在。这样即使 Agent 被诱导执行了printenv也拿不到真实密钥。2.4 资源配额与运行超时机制模型生成的代码经常会有死循环、内存泄漏、或者一次性创建大量子进程的“骚操作”。Hullwork 为每个沙箱实例设置了默认的 CPU 时间上限、内存上限、进程数上限和输出大小上限。CPU 限制用的是 CFS 配额内存限制通过 cgroup 控制进程数限制通过 PID cgroup 来控制。当一个 Agent 任务超过这些限制时沙箱会强制终止并返回一个包含“超出限制类型”的错误码这样上层的 Agent 编排逻辑就能知道是资源超了而不是代码逻辑本身出了问题。超时机制也是必需的。默认情况下每次工具调用的超时时间通常是 30 秒你可以在创建沙箱时调整。超时后 Hullwork 会把整个进程树杀掉不会留下孤儿进程。我在实际使用里发现很多 Agent 的异常行为都是靠超时兜住的——模型以为自己在写一个快速脚本结果脚本里隐藏了个死循环没有超时的话我的服务器就得遭殃。3. 30分钟上手Hullwork 安装、初始化与第一个隔离 Agent3.1 环境要求与安装Hullwork 目前推荐在 Linux 环境上使用macOS 可以跑在 Docker 容器里Windows 需要先装 WSL2。核心依赖是 Linux 内核支持 namespace 和 overlayfs现在主流发行版默认都开了。安装方式有三种用预编译的二进制文件从官方 Release 页面下载对应架构的压缩包解压后放入/usr/local/bin。用源码编译需要 Go 1.22 以上版本git clone后执行make build。用 Docker 镜像适合不想在宿主机上装二进制的情况。我这里以安装二进制的方式演示curl -fsSL https://github.com/hullwork/hullwork/releases/latest/download/hullwork-linux-amd64.tar.gz | tar xz sudo mv hullwork /usr/local/bin/ hullwork version执行后能看到版本号说明安装成功了。另外需要确认hullwork二进制有可执行权限如果解压后权限不对手动执行chmod x /usr/local/bin/hullwork。3.2 初始化一个默认沙箱安装完成后第一步是初始化一个默认沙箱配置。Hullwork 提供了sandbox init子命令它会自动检测当前系统的内核能力并在当前目录生成一个hullwork.yaml配置文件mkdir ~/agent-sandbox cd ~/agent-sandbox hullwork sandbox init --default这个--default参数对应了网络热词里常被搜索的 “set up default sandbox”它的作用是生成一套可以直接运行的保守配置默认禁止网络访问默认只把当前目录映射为只读默认启用系统调用过滤。生成后的hullwork.yaml结构大致如下version: 1 default: network: mode: block filesystem: read_only: - . writable: - /tmp/hullwork-workspace resources: cpu_seconds: 10 memory_mb: 512 process_limit: 64 output_limit_bytes: 1048576 timeout_seconds: 30 env: redact: - OPENAI_API_KEY - AWS_SECRET_ACCESS_KEY你可以理解为一个刚刚初始化好的空房间墙是水泥墙窗子是封闭的Agent 只能在房间里打转。这套配置适合用来验证 Hullwork 本身是否正常工作不适合直接上生产。3.3 让 Agent 在沙箱内执行工具调用配置写好后启动沙箱并执行命令的方式非常直接hullwork run --config hullwork.yaml -- sh -c ls -la pwd echo hello from agent执行后会看到输出同时在控制台上显示这次任务的统计信息启动耗时、CPU 消耗、最大内存、退出码。如果配置里网络是 block 模式那么你执行curl https://example.com会得到一个网络不可达的错误。这就是沙箱生效了。更接近 Agent 场景的做法是让 Agent 框架通过 SDK 调用 Hullwork。Hullwork 暴露了一个简单的 REST API你可以在 Python 里这样用import requests payload { config: hullwork.yaml, command: [python3, agent_task.py], stdin_data: , timeout: 60 } resp requests.post(http://localhost:8420/api/v1/tasks, jsonpayload) print(resp.json())这个接口会返回一个task_id之后你可以轮询/api/v1/tasks/{task_id}获取执行状态和输出。Hullwork 本身不干涉 Agent 的逻辑它只负责当那个“可靠的执行者”Agent 的规划、反思、调用决策都在上游完成。这种架构思路很清晰模型负责想Hullwork 负责做做出来的后果由 Hullwork 兜住。3.4 检查运行日志与审计记录除了执行任务Hullwork 还会生成审计日志。日志里记录了每次任务的关键信息执行了哪些命令、读取了哪些文件、访问了哪些网络地址、回收了多少资源。审计日志默认输出到~/.hullwork/audit/目录下按日期生成 JSON 文件。{ task_id: a3f9c0e1-2345-4bcd-9abc-1234567890ef, sandbox: default, command: [sh, -c, curl http://169.254.169.254/latest/meta-data/], start_time: 2025-01-14T10:23:45Z, end_time: 2025-01-14T10:23:45Z, status: blocked, blocked_reason: network_policy_denied }我习惯把审计日志接到日志分析系统里这样一旦 Agent 出现异常行为我可以回溯到具体是哪一次工具调用引起的。对于做 AI 安全的团队来说这份审计数据本身就是资产能证明你的 Agent 没有越界也能帮你定位 prompt injection 的注入点。4. 把沙箱配置成“恰如其分”的边界策略配置实战4.1 按 Agent 场景设计配置模板Hullwork 最强大的地方在于可编程的沙箱策略。你可以为不同类型的 Agent 准备不同的配置模板代码生成 Agent 需要能访问编译器和项目目录浏览器操作 Agent 需要能访问临时下载目录和某个特定 API数据分析 Agent 需要能读取数据集文件但不需要访问网络。我建议一个基本的设计原则是**“最小权限 白名单增量”**先从一个完全封闭的基线开始然后根据 Agent 的实际需求一条一条地开放它需要的资源。不要图省事直接开放所有权限否则沙箱就失去了意义。以代码生成 Agent 为例它的配置可以长这样version: 1 default: network: mode: whitelist allowed_domains: - registry.npmjs.org - pypi.org - github.com filesystem: read_only: - /usr/lib/python3 - /usr/local/lib/python3.11 - /workspace/repo writable: - /tmp/build commands: allowed: - bash - python3 - pip - npm - go - git denied: - curl - wget - rm - ssh resources: cpu_seconds: 60 memory_mb: 2048 timeout_seconds: 120这里有几处需要说明network.allowed_domains只开放了代码依赖下载的域名curl和wget命令被禁掉了但还是可以通过 Python 的 urllib 访问外部网络。所以网络层和命令层要同时限制不要只堵一层。filesystem.writable只开放了/tmp/buildAgent 的所有输出必须写到那里。如果它试图写/workspace/repo/a.py会收到 permission denied因为该目录是只读映射。commands字段是可选的。Hullwork 默认不做命令级拦截但你可以显式声明允许和禁止的命令针对 Agent 的常见危险操作加上额外约束。4.2 网络白名单与文件读写白名单示例网络白名单的匹配逻辑支持通配符也支持精确域名。比如你可以允许*.api.example.com但要求这里的api.example.com不会对外部开放。我对生产环境的安全建议是能用 IP 段就用 IP 段能不用*就不要用。因为 Agent 可能被诱导去访问一个泛解析域名如果白名单是*.example.com而攻击者注册了evil-example.com那就绕过了限制。相比之下精确到具体主机名会安全得多。文件读写白名单可以做得更细。Hullwork 支持按照后缀限制读取范围filesystem: read_only: - path: /workspace/data extensions: [.csv, .json, .parquet] writable: - path: /tmp/output extensions: [.csv, .jpg, .png]这样 Agent 能看到/workspace/data下的数据文件但看不到同目录下可能存在的配置文件、私钥文件。这个设计很实用因为很多项目会把密钥放在仓库根目录的.env文件里如果 Agent 的读取范围是全目录它可能不小心把这些敏感信息读进上下文增加泄露风险。4.3 与 Harness、编排框架之间的职责划分在 Agent 工程里经常有人把 “Harness” 和 “Sandbox” 混在一起讨论。我在使用 Hullwork 后对两者的理解越来越清晰Harness 负责 Agent 的“行为编排”比如规划步骤、选择工具、管理上下文、处理模型输出Sandbox 负责 Agent 的“执行安全”比如隔离进程、限制权限、防止破坏宿主环境。以 LangChain 这类框架来说LangChain 是 Harness它决定 Agent 下一步调用什么工具而 Hullwork 可以作为一个ToolExecutor的底层执行器真正去跑那些工具调用。两者是上下游关系而不是替代关系。我见过有些团队只用了 Harness以为框架自带的“工具函数”已经足够安全其实框架只是把命令拼接好后扔给subprocess.run了没有做任何隔离。这时只要模型被诱导构造一条危险命令宿主就直接受影响。所以在设计架构时我的习惯是Harness 层负责模型调用、Prompt 组装、工具选择、上下文管理、人机交互。Hullwork 层负责工具命令执行、代码运行、文件操作、网络请求、数据返回。审查层负责Hullwork 审计日志分析、异常行为告警、任务回滚。这三层各司其职才能构成一个完整的 Agent 安全体系。4.4 开发环境 vs 生产环境的配置差异开发环境和生产环境的沙箱策略应该有明显差异。开发环境追求“调试友好”我会把网络模式设成全放行或宽松白名单方便 Agent 调用各种调试接口审计日志也开到最详细方便定位问题。生产环境追求“安全稳定”网络模式优化成严格白名单可写目录限定到最小范围超时时间缩短同时开启审计日志的实时上报。我自己的实践中开发环境用了一个特殊的dev.yamldefault: network: mode: allow filesystem: read_only: [/workspace] writable: [/tmp/dev-workspace] environment: type: interactive生产环境则用prod.yaml配置更加严格同时开启红action“失败即回滚”模式——只要沙箱内进程返回非零退出码立即丢弃可写层内容并记录一条告警。开发环境则相反即使失败也会保留可写层方便检查中间产物。这个区别很重要因为生产环境你不想保留一半的乱写文件而开发环境你恰恰需要那些半成品来调试。5. 我在实际部署中踩过的坑和验证过的经验5.1 为什么默认 sandbox 会导致部分系统命令失效我第一次用 Hullwork 跑一个系统信息收集 Agent发现lsblk、df -hT这些命令在沙箱里都报错。排查了半天发现问题出在系统调用过滤上。默认 sandbox 只保留了最基础的一组 syscall像blkpg、statfs这些和磁盘信息相关的调用没有被放行所以涉及块设备信息的命令全都失效。解决方案是在配置里显式开启“磁盘信息读取”能力system_calls: allow: - statfs - statx更通用的做法是先跑一遍hullwork audit syscalls --command 你的命令工具会分析这条命令用到了哪些被过滤的系统调用然后生成一个建议的allow列表。这个功能特别省时间不用去翻 Seccomp 文档。不过我还是建议把允许列表控制在最小范围只针对真正需要的命令开启statfs不要直接全部放行。5.2 Agent 调用 Python 解释器时的 CPU 限制陷阱还有一次我在配置里给沙箱分配了cpu_seconds: 10以为这样 Agent 最多消耗 10 秒 CPU。结果 Agent 运行一个多进程数据清洗脚本的时候Hullwork 直接把它终止了错误码显示cpu_quota_exceeded。后来我发现cpu_seconds限制的是整个沙箱实例的 CPU 时间总和包括所有子进程。而且它计算的是CPU 时间不是墙钟时间。一个 8 核机器上跑了 30 秒的 Python 脚本如果占了 8 个核那么 CPU 时间可能是 240 秒早就超过配额了。对于需要多进程并行处理的 Agent 任务建议调高cpu_seconds同时设置合理的process_limit。另外要注意 Python 的multiprocessing会 fork 子进程每个子进程都算在同一个沙箱的 CPU 时间里。我的经验是先用小批量数据测试脚本观察它平均消耗多少 CPU 时间再根据历史数据设定配额留 2~3 倍的余量。5.3 临时目录迁移引发的路径不一致Hullwork 默认会把/tmp映射成可写层里的一个临时目录。但这个临时目录的生命周期和任务绑定每次任务结束时会被清空。有个 Agent 执行分两步第一步先下载一个文件到/tmp/cache.pkl第二步再加载这个文件。如果第一步和第二步被建模成两个独立的沙箱任务第二个沙箱的/tmp是全新的根本找不到第一个沙箱里留下的文件。这个坑的修复方式有两种。第一种是把共享缓存目录挂载到宿主机的固定路径例如filesystem: writable: - path: /tmp host_path: /var/cache/hullwork/{task_id}但这样每次任务还是独立的。更合适的做法是让 Agent 把所有中间状态写到显式声明的持久化目录里比如/workspace/tmp而不是依赖系统/tmp。我在实际项目中会把输出目录的路径通过环境变量注入沙箱引导 Agent 把临时文件写到那里避免它自作主张去/tmp下搞一套。如果你准备在 Agent 里做多阶段任务务必先确认沙箱的临时目录生命周期是否符合你的预期。5.4 定时清理与缓存命中的权衡Hullwork 的可写层会占用磁盘空间。默认配置下沙箱不会自动清理已结束任务的可写层需要你自己写定时任务。我踩过的坑是一个高频调用 Agent 的沙箱运行了一周后磁盘空间被占去了将近 60GB。这是因为每个任务的可写层都保留着完整的一份文件系统差异数据即使 Agent 只写了一个几十 KB 的配置文件底层 overlayfs 还是会产生很多元数据。所以在生产环境里我加了一个 cron 任务定期清理超过 24 小时的沙箱可写层hullwork cleanup --older-than 24h --include-tmp同时设置缓存命中机制如果两个任务使用相同的只读映射目录Hullwork 会复用底层的只读层大幅度减少磁盘占用。关于这一点配置里有个cache_readonly_layers: true的开关建议打开。6. 从玩起来到用起来Hullwork 的下一步扩展思路6.1 与开源 Agent 框架的集成方式如果你已经在使用主流的 Agent 框架Hullwork 可以作为一个工具执行后端接进去。以 Python 生态为例你可以在自定义 Tool 的_run方法里调用 Hullwork 的 REST API而不是直接使用subprocessfrom langchain.tools import BaseTool class HullworkExecuteTool(BaseTool): name safe_shell description 在沙箱中执行 shell 命令返回输出或错误信息。 def _run(self, command: str) - str: payload {command: [/bin/bash, -c, command], timeout: 30} resp requests.post(http://localhost:8420/api/v1/tasks, jsonpayload) data resp.json() return data.get(output)这样做的好处是Agent 看到的能力仍然是“执行命令”但底层执行已经被沙箱接管。你不再需要在 prompt 里写一堆“不要删文件、不要乱访问网络”之类的限制那些由沙箱硬性兜底。6.2 作为远程代码执行引擎的安全层如果你在做一个多租户的 Agent 平台Hullwork 甚至可以当作用户提交代码的远程执行引擎。每个租户的任务都跑在独立的沙箱中文件系统、网络、资源限制全是隔离的。基于这个思路你可以做出一个“让 Agent 帮你跑 Python 脚本”的产品用户提交一段代码平台在 Hullwork 沙箱里执行并把结果返回给用户。只要沙箱策略配置正确即使代码是恶意的它也只能在笼子里折腾无法探测宿主机网络或者读取其他租户的数据。6.3 私有化部署时的改造建议Hullwork 是开源的源码可以私有化部署。如果你需要做更多定制比如对接公司内部的审计系统、保存审计日志到对象存储你可以修改审计日志的 exporter。Hullwork 把审计和任务执行解耦了你可以在hullwork.yaml里配置audit.exporter的类型audit: exporter: webhook webhook_url: https://your-platform.example.com/hullwork/events batch_size: 100它支持按时批量上报你可以在服务端接收后统一落库或分析。还有一点建议在对外的服务中使用 Hullwork 时控制好可通过 API 创建的沙箱数量避免被恶意刷任务把机器 CPU 占满。我自己的做法是在上层加一个简单的 token 限流每个用户的并发沙箱数控制在 2~3 个效果很好。个人实际跑通了这一套之后我的 Agent 开发方式彻底变了。以前写 Agent 工具函数时最担心的“这个函数会不会把机器搞坏”现在变成了“我该给这个工具开多大的权限”。Hullwork 让 Agent 开发回到了正常的工程节奏模型负责聪明沙箱负责安全。如果你也在做 Agent 项目尤其是要让 Agent 执行真实命令、操作真实文件的场景真心建议尽早把这类沙箱机制引入到架构里而不是等到线上事故发生后补防。