代码执行与沙箱:让模型计算,但不把主机交给它
理解生成—执行—观察闭环的能力来源,以及进程、文件、网络、资源、凭据与人工授权组成的隔离边界。
- 将任务拆成最小可执行、可回退、可审计且可重放的步骤。
- 按风险配置隔离、权限和限额。
- 执行并捕获完整状态。
- 失败时只提供必要反馈并限制修正次数。
- 用测试、哈希或业务规则验收产物。
- 高影响外部动作在沙箱之外重新授权。
1代码工具补的是哪类能力直觉
模型能够流畅地解释一个算法,却可能在计算一个大数时给出错误答案,这并非偶然。语言模型擅长把目标转述成程序,而解释器擅长对明确的规则进行精确、可重复的执行。两者解决的问题不同:前者负责“意图的表述”,后者负责“规则的落地”。把计算、数据转换、绘图和测试这些确定性任务交给代码,本质上是用一次真实执行的结果替换掉“凭语言猜结果”,再用这个可观察的结果去校正下一步的判断。
代码执行因此是一种工具:它让解释器运行候选程序,并返回可观察的证据。它的输入是任务、代码、数据与运行环境;它的输出是运行结果、日志与产物。其工作方式遵循“生成—执行—观察—修正”的循环——先由模型生成候选程序,再由解释器执行,模型观察输出后修正自己的下一步。这个循环的价值在于留下了程序、输入、环境版本与输出,使整个过程可以被独立复核,也可以被确定性重放。
需要严格区分的是,一次执行结果能说明的只是“这个程序在这个环境中这次运行产生了什么”,它不能自动证明程序正确、安全,也不等于获得了发布授权。正确性需要另外的验证,安全性需要另外的隔离,发布授权需要另外的决策。代码执行补的是“确定性计算与可观察反馈”这一类能力,而不是对结论的最终背书。
2最小执行闭环工程
代码从生成到被信任,要依次经过生成、边界检查、隔离执行、观察记录、反馈修正和独立验收这几个状态,每个状态都由不同的主体负责,不能互相替代。
闭环的输入是候选代码、只读数据、运行策略和完成谓词。所谓完成谓词,是一个可以在执行之外独立判定“任务是否真正成功”的条件,而不是“进程是否正常退出”。模型提出代码、输入、预期产物和这个完成谓词之后,宿主先做静态检查:检查工具、依赖、权限与资源策略;一旦不满足策略,宿主应当拒绝执行,而不是让代码自行降级去迁就。随后,只读输入被复制进一个全新的沙箱,在非特权身份下运行,宿主捕获 stdout、stderr、退出码、信号、资源峰值、网络尝试和文件差异。这些观测结果回到 Agent;失败时只暴露必要的诊断信息,并限制修正次数,防止无限重试。
执行结束后是独立验收:宿主不把“进程成功退出”当作任务成功,只导出被允许的产物。整个闭环中,控制平面负责创建环境、注入策略和决定导出,数据平面只运行候选代码。候选进程既不能访问控制平面的令牌,也不能修改自身的限额;否则所谓沙箱就只是被隔离对象自愿遵守的约定,边界形同虚设。
理解这条闭环的要点在于:宿主先设边界,再执行、观察、验收;退出码 0 只表示进程正常结束,不代表任务完成。一旦控制令牌对候选进程可见,或者完成谓词无法在外部独立检查,闭环就失去了真正的边界。
3为什么必须假设代码有害威胁
模型没有恶意,生成出的代码却仍然要按攻击载荷来处理,原因是威胁不由生成者的意图决定,而由代码的能力和它实际可达的资源决定。提示注入、拼错或误装的依赖、从网页复制来的片段,乃至普通的编程错误,都可能导致删文件、读取秘密、扫描内网、进入无限循环或耗尽资源。作者“只是想算个平均值”并不降低这些后果的真实性。
因此威胁模型应当从“最坏可达效果”出发,而不是从“作者是否好心”出发。需要逐一确认:代码能读哪些路径、能连接哪些地址、以谁的身份运行、能创建多少子进程,以及它的输出是否会触发下游解析器。即便主任务只是计算平均值,只要运行时继承了云凭据和内网访问权限,它就同时拥有了与任务无关的巨大攻击面。最小权限原则要求的是先删除不需要的能力,而不是在提示里礼貌地请求代码“不要使用”。
eval 并不是 exec 的安全替代:两者都可能执行不可信表达式,安全边界必须建立在独立的执行环境之上,而不是靠挑选某个解释函数来获得。
这个威胁模型的输入,是代码可调用的能力、可达的文件与网络、运行身份和下游解析器;输出是最坏可达影响,以及应当删除的权限。它解决的是“作者没恶意是否就安全”这类误判。它产出的结论用于配置防护,而不是预测某次攻击一定发生;未知漏洞和配置错误仍可能穿透已知边界,所以防护结论是持续收紧的起点,而非一劳永逸的保证。
4沙箱要隔离哪些资源安全
只开一个新进程远远不够,因为威胁来自多个正交的维度,任何一个维度失守都足以造成损害。沙箱需要在五类资源上同时设立边界,并记录每类资源对应的控制证据。
文件方面,使用临时工作区、只读输入,禁止访问主机路径与设备,并记录挂载清单和最终的文件差异。网络方面,默认断网,或仅允许目标与协议的白名单,阻断内网元数据,同时记录 DNS 查询与连接尝试。资源方面,为 CPU、内存、磁盘、进程数和墙钟时间设上限,并记录峰值、终止原因与超限项。身份方面,使用非特权用户、不携带宿主凭据、限制系统调用,并记录有效 uid、capability 与策略版本。生命周期方面,每个任务重建环境,用后即销毁并清理产物,记录环境 id 与创建、销毁时间。
这些控制必须同时存在:只限制 CPU 并不能阻止秘密外传,只断网并不能阻止覆盖宿主文件,只用临时目录也不能防止 fork bomb。输出通道同样属于边界的一部分——stdout、图片、压缩包和电子表格都要限制大小、类型和解析方式,否则攻击只是从执行器被转移到了查看器。
使用这张隔离表时,输入是任务真正需要的资源以及可承受的损失,输出是五类隔离策略及对应的审计记录。宿主对文件、网络、资源、身份和生命周期分别设限,再做联合检查。日志显示未越界,只能解释本次已经观察到的行为;它不能证明共享内核或查看器不存在未知漏洞。
| 边界 | 控制 | 需要记录的证据 |
|---|---|---|
| 文件 | 临时工作区、只读输入、禁止主机路径与设备 | 挂载清单和最终文件差异 |
| 网络 | 默认断网或目标/协议白名单、阻断内网元数据 | DNS 与连接尝试 |
| 资源 | CPU、内存、磁盘、进程数和墙钟上限 | 峰值、终止原因和超限项 |
| 身份 | 非特权用户、无宿主凭据、最小系统调用 | 有效 uid、capability 与策略版本 |
| 生命周期 | 每任务重建,用后销毁并清理产物 | 环境 id、创建/销毁时间 |
5虚拟环境不等于安全沙箱消歧
Python 的 venv 或依赖容器并不会自动隔离主机。venv 主要隔离的是包版本,它不限制文件访问、网络和系统调用;容器虽然提供命名空间与资源控制,但错误的挂载、特权模式,或者共享内核中的漏洞,仍然可以破坏隔离。面对高风险场景,需要叠加更强的组合:非特权容器、只读根文件系统、系统调用过滤、微型虚拟机,以及外部网络策略。
“可复现”与“隔离”也不是同一件事。锁文件、固定运行时和确定性随机种子帮助重放结果,却不限制权限;微型虚拟机强化了边界,却仍可能因为未锁定依赖而得到不同的结果。可靠的执行必须同时回答两个独立的问题:这个程序能否逃逸或破坏环境,以及同一个输入能否被再次算出同样的结果。
做选择时,输入是复现要求、攻击风险,以及需要向宿主开放的能力;输出是依赖环境、容器或微型虚拟机的组合。venv 解决的是包版本冲突,沙箱解决的是越权和损失半径,两者的工作目标不同。容器本身不是安全结论,特权模式、危险挂载或过宽的出网权限都会让隔离失效。
6依赖与结果也不可信供应链
即使代码本身看起来安全,风险仍可能从依赖和产物这两条路径进入。依赖方面,安装包可能被拼写劫持——用一个名字相近但内容不同的包冒充目标包——也可能在安装时执行任意脚本。产物方面,生成的文件可能包含宏、公式注入或巨量数据。应对方式是使用允许列表、锁定版本和内部镜像来约束依赖,同时扫描产物并限制其类型与大小;只有经过验证的结果才被复制出沙箱。
供应链检查的输入是依赖名称、版本、来源和待导出文件,输出是允许或拒绝的决定以及扫描证据。它通过锁定版本、使用可信镜像、类型与大小检查来阻断依赖和产物夹带的风险。
需要清醒的是,通过扫描只说明已知规则没有命中,并不能保证绝对安全;未知的恶意包、宏和解析器漏洞仍然存在,因此最小权限和安全查看仍然必须保留。锁定版本与可信镜像降低了依赖被替换的概率,而类型与大小限制降低了产物在查看器中引爆风险的概率,两者叠加才构成对供应链这条隐蔽路径的有效约束。
7预算必须能算,而不是写“适量”推导
单轮限时 30 秒、最多重试 3 次,用户却仍可能等待两分钟,原因在于端到端等待时间不是单轮上限,而是整个修正循环的上界。最坏墙钟时间可以用公式表示为:
T最坏 ≤ (1 + N重试) × T单轮 + T排队与启动
公式输入三个量:N重试 是首次失败后最多再执行的次数,T单轮 是一轮允许占用的最长墙钟时间,T排队与启动 是等待资源与创建沙箱的额外预算;输出 T最坏 是用户端到端等待上界。这里的“最大重试数”不含首次执行。若单轮 30 秒、允许失败后再试 3 次、排队与启动共预留 12 秒,则最坏端到端预算是 (1+3)×30+12 = 132 秒,而不是 30 秒。
输出同样要能算。若最多允许 20 个文件、单文件 5MB,仅文件导出理论上就可达 20×5 = 100MB;因此还需另设一个总产物上限,避免用许多小文件绕过单文件限制。
预算需要沿多个轴分别设硬上限,并规定超限动作。计算上设 2 CPU、512MB、30 秒,超限即终止整组进程并记录超限项;并发上设 64 个进程或线程,拒绝继续创建以防 fork bomb;存储上设临时盘 1GB、导出总量 40MB,超限停止写入且不导出半成品;反馈上设 stdout+stderr 共 2MB,超限截断并保留哈希与尾部诊断;尝试上设首次加 3 次修正,超限停止自动循环,交由外部策略决定。
预算应当是双层的:沙箱内限额约束单次执行,控制器的端到端预算约束整个修正循环;缺了任何一层都可能造成资源失控。这个上界依赖配置假设,是约束性的上限,不是实际耗时的预测,也不覆盖外部人工审批的时间。
| 预算轴 | 示例硬上限 | 超限动作 |
|---|---|---|
| 计算 | 2 CPU、512MB、30 秒 | 终止整组进程并记录超限项 |
| 并发 | 64 个进程/线程 | 拒绝继续创建,防 fork bomb |
| 存储 | 临时盘 1GB、导出总量 40MB | 停止写入;不导出半成品 |
| 反馈 | stdout+stderr 2MB | 截断并保留哈希与尾部诊断 |
| 尝试 | 首次 + 3 次修正 | 停止自动循环,交给外部策略决定 |
8从意图到可信结果综合
让执行反馈提升能力而不是扩大损失半径,靠的是一条把意图逐步收紧成可信结果的链路。第一步是把任务拆成最小可执行、可回退、可审计且可重放的步骤;第二步按风险等级配置隔离、权限和限额;第三步执行并捕获完整状态;第四步在失败时只提供必要反馈并限制修正次数;第五步用测试、哈希或业务规则验收产物;第六步,高影响的外部动作要在沙箱之外重新授权。
这条综合链路的输入是任务意图、风险等级、候选代码和完成谓词,输出是通过验收的有限产物,或者带证据的停止结果。每一步都在缩小动作的范围,同时让下一步变得可验证:拆解限定了单步的破坏面,配置限定了运行的能力,捕获限定了可追查的状态,反馈限定了重试的规模,验收限定了可导出的产物,而重新授权则把真正危险的动作挡在沙箱的信任边界之外。
通过沙箱验收只表示产物满足了已声明的规则,不等于允许把它发布、转账或写入生产。发布、转账和写生产都是高影响的外部动作,必须重新授权。执行反馈提升的是模型逐步校正意图的能力,而损失半径由每一步的边界和最后的重新授权共同约束。
9一次缓存修复怎样在沙箱中被证明运行示例
模型提出一个复合缓存键的修复方案后,不能直接把补丁写进生产仓库并宣布完成,因为“提出方案”和“方案已被证明可行”之间隔着一整条验证链路。补丁要先经过宿主策略门,进入临时沙箱执行,产物通过验证之后,才由宿主决定是否导出。安全边界由宿主在执行前配置、执行后复核;沙箱内的进程不能自行扩大网络、文件或凭据权限。
观察证据和它能证明的内容需要逐项区分。退出码为 0,只能证明测试进程正常结束,不能证明测试覆盖完整、业务目标正确。测试通过 4/4,只能证明已声明的隔离与命中行为通过,不能证明隐藏的并发问题或跨版本问题不存在。资源峰值 310MB / 6.2s,只能证明本次低于 512MB / 30s 的限额,不能证明任意输入规模都安全。文件差异显示 2 个允许文件,只能证明本次未观察到越界写入,不能证明代码没有其他安全缺陷。
资源限额并不是“防坏人”专用:一个错误递归就可能耗尽内存。在这个例子里,允许的 3 次修正意味着最多执行 4 轮,单任务执行部分的上限是 4×30 = 120 秒;再加上排队与启动才是完整预算。
失败的边界在于:沙箱降低的是损失半径,它不会把恶意代码变成安全代码。共享内核漏洞、错误挂载、过宽的出网权限和宿主代理接口都可能穿透边界。高风险系统需要采用分层隔离、及时打补丁和外部策略检查,而不是依赖单一沙箱给出绝对保证。
| 观察 | 值 | 能证明什么 | 不能证明什么 |
|---|---|---|---|
| 退出码 | 0 | 测试进程正常结束 | 测试覆盖完整、业务目标正确 |
| 测试 | 4/4 | 已声明隔离与命中行为通过 | 隐藏并发、跨版本问题不存在 |
| 资源峰值 | 310MB / 6.2s | 本次低于 512MB / 30s | 任意输入规模都安全 |
| 文件差异 | 2 个允许文件 | 本次未观察到越界写入 | 代码没有其他安全缺陷 |
10怎样证明策略真的生效评测
“配置里写了断网”本身不是证据;要证明策略真的生效,应当让测试主动尝试越界,并从沙箱之外观察结果。对抗测试逐项验证隔离是否真正起作用,每一类测试都对应一个期望的观察结果,以及失败时优先排查的位置。
读取宿主秘密诱饵时,期望路径不可见,审计日志中没有内容泄漏;若失败,优先查挂载、环境变量和代理凭据注入。访问公网与云元数据地址时,期望连接被宿主网络策略拒绝并留下痕迹;若失败,优先查 DNS、IPv6、代理和旁路接口。fork bomb 或无限循环时,期望进程数或墙钟超限,整组进程被清理;若失败,优先查 cgroup/job object 和子进程回收。写满磁盘或巨量 stdout 时,期望配额触发且宿主服务仍然可用;若失败,优先查临时盘、日志与导出总量上限。符号链接或压缩包路径穿越时,期望导出器拒绝沙箱之外的目标;若失败,优先查路径规范化和解包器。正确程序与业务反例组合时,期望安全测试通过,但错误产物被业务谓词拦截;若失败,优先查验收覆盖而非隔离层。
这意味着存在两张必须分开看的成绩单:安全性评测问“越界是否被阻断”,任务评测问“结果是否正确”。某一张满分不能替代另一张。对抗测试只能增加对已测策略的证据,它不能证明不存在未知逃逸;安全测试通过也不等于任务做对,反之亦然。两张成绩单分别回答两个不同的问题,必须同时成立,策略才算真正生效。
| 对抗测试 | 期望观察 | 若失败,优先查哪里 |
|---|---|---|
| 读取宿主秘密诱饵 | 路径不可见,审计日志无内容泄漏 | 挂载、环境变量、代理凭据注入 |
| 访问公网与云元数据地址 | 连接被宿主网络策略拒绝并留痕 | DNS、IPv6、代理和旁路接口 |
| fork bomb / 无限循环 | 进程数或墙钟超限,整组进程被清理 | cgroup/job object、子进程回收 |
| 写满磁盘 / 巨量 stdout | 配额触发且宿主服务仍可用 | 临时盘、日志与导出总量上限 |
| 符号链接或压缩包路径穿越 | 导出器拒绝沙箱外目标 | 路径规范化和解包器 |
| 正确程序与业务反例 | 安全测试通过,但错误产物被业务谓词拦截 | 验收覆盖而非隔离层 |
- NIST SP 800-190:应用容器安全风险与控制。
- Agache et al., Firecracker:微型虚拟机的隔离设计与工程权衡。
- ToolEmu:工具型语言模型的风险模拟与评测。
- ReAct:推理、行动与环境观察闭环。