Agent 要运行一个下载来的报表脚本。即使用户只要求生成报告,这个脚本仍可能读取环境变量、遍历主目录或访问网络。模型的任务意图不能限制操作系统向进程开放的能力。
Sandbox 的价值是约束执行环境,让错误代码能影响的范围与任务需要相匹配。它不能保证输出正确,也不能让不可信依赖自动可信。理解隔离应从“某次系统访问在哪里被拒绝”开始。
临时目录为什么不是隔离
TemporaryDirectory() 创建并清理目录,但进程的用户身份没有改变。运行其中的脚本仍可能读取同一用户可读的其他文件。cwd 只改变相对路径的解析起点,也没有把进程限制在这个目录里。
受控文件工具可以只接受固定逻辑资源名,并在服务端映射到路径,这能缩小接口范围。但只要同一个 Agent 又获得任意命令工具,命令就可能绕过文件工具。设计隔离必须考虑全部出口,不能只审查一个函数。
文件系统的边界怎样建立
容器可以只挂载任务需要的输入,并把输入挂载为只读,另给一个输出可写卷。这样脚本即使尝试修改输入,系统也会拒绝。若把整个宿主主目录挂进去,容器里仍能看到本不需要的数据。
只读根文件系统可以减少任意修改,但应用还可能需要临时目录。应显式提供大小受限的临时空间,而不是为了修复一个写入错误就把整个根目录重新开放。宿主设备、容器管理套接字和敏感挂载尤其会改变隔离强度。
符号链接与路径竞态说明了应用层校验的局限。程序检查完一个路径之后,另一个进程可能替换路径指向。可信单进程演示可以使用简单路径检查;执行敌对代码时,应使用更强的操作系统边界及适合平台的文件访问方式,不能把演示函数当成完整防逃逸实现。
进程与资源是另一组边界
低权限用户减少进程可执行的操作;系统调用过滤减少可用内核接口;进程数限制防止无限派生;CPU、内存和磁盘限额限制资源消耗。这些约束解决的问题不同。
例如墙钟超时设为三十秒,脚本仍可能在一秒内分配大量内存导致宿主压力,所以还要有内存限额。只限制内存,脚本仍可持续耗尽 CPU;只停止父进程,后台子进程可能继续运行。回收需要面向整个执行单元,而不是只关闭一条等待请求。
容器共享宿主内核,虚拟机通常提供独立内核边界;具体强度还取决于配置与漏洞面。不要写成“用了容器就安全”,也不要把某一种隔离方案规定为所有任务的最低配置。应依据是否运行不可信代码、是否多租户以及需要访问哪些资源选择。
网络权限不仅是能不能联网
订单报告可能只需要读取已准备好的输入,运行阶段就可以不提供网络。若需要访问业务 API,可以通过可信代理限制目标和请求,避免把通用网络与长期密钥一起交给脚本。
域名允许列表仍需考虑重定向、DNS 解析、内网地址和目标变化。允许 example.com 并不意味着应跟随它返回的任意重定向。代理负责验证实际连接目标,业务服务继续验证租户和对象权限,两者不能互相替代。
安装依赖也属于网络与代码执行。构建环境可以预先根据锁文件准备依赖,再运行不联网任务;如果每次运行都下载最新包,就难以复现,包管理器安装脚本也可能执行代码。环境版本应包含基础镜像、依赖和工具版本。
用生命周期解释孤儿环境
环境不是启动成功就结束管理。任务通常经历创建、装载输入、执行、导出、终止。每一步失败都需要决定清理方式。
若 Worker 崩溃,进程内 finally 不一定有机会执行;若网络中断,控制器可能不知道远端沙箱是否还存在。因此应记录实例 ID、归属任务和过期时间,并由独立回收机制核对。回收前要判断是否还需保存失败日志和产物,清理后应避免复用残留凭证。
长时间复用一个环境节约启动成本,却需要处理前次任务留下的文件、后台进程和身份。多租户场景不能只更换 Prompt 就把同一环境交给另一个用户。
配置应该怎样读
下面是架构契约示意,不是任何容器工具可直接接受的配置:
# 每个字段都必须由所选执行平台落实;解析 YAML 不会产生隔离。
identity: unprivileged-task-user
inputs: read-only-snapshot
outputs: dedicated-writable-volume
network: disabled
limits:
wall_seconds: 30
memory_mib: 256
process_count: 32
cleanup: whole-execution-unit
可以用它逐项询问实际部署:谁限制了内存?谁拒绝网络?谁负责杀掉后台进程?如果答案只有“提示词要求这样做”,约束还没有落地。
Docker 的资源限制和安全说明可用于进一步核对平台机制。本系列不会启动容器或运行不可信代码;配套 Python 只展示可信本地文件操作。下一篇讨论即便环境已经隔离,一次业务动作仍然需要什么授权。