第 1 卷
张嘉熙 · DeepSeek Harness 前沿工程实践



你好,我是张嘉熙。
上一讲结尾我们把“程序执行本身的隔离”留成了另一道题。这一讲我们就来讲它,不过得先打个预防针:上一讲的 worker 线程只是“围住”,算不上安全边界,这一讲的进程沙箱同样不是
强隔离。
但开头得先破一个误解。说到“沙箱”,很多人脑子里浮现的是 Docker、虚拟机。不过这俩压根不是一回事:虚拟机自带内核,是真的“另一个世界”;Docker 用的仍是宿主内核,只是拿
namespace 把进程树、挂载点、网络这些视图隔开了,内核还是共享的那一个。
而 dsh 的进程沙箱比 Docker 还要再弱一档。官方对它的概括是:把同世界的子进程 argv (也就是要跑的那条命令)包进一层文件效果策略。同世界是说它与宿主共享文件系统和内
核,没被挪进另一个世界;文件效果划的是管辖边界:它只管对文件系统的改动,在这个范围内具体约束的就是能往哪儿写。重点在子进程:被约束的是一个个被启动起来的子进程,不是
关于环境:本讲新增三个包——@deepseek-ai/dsh-sandbox(seam 定义)、 @deepseek-ai/dsh-sandbox-local(本地实现)、@deepseek-ai/dsh-
sandbox-policy(策略解析),已加入本项目的 package.json,npm install -- legacy-peer-deps 装上即可。
关于示例平台:这一讲的运行输出来自 macOS。在 Linux 上你会看到 bwrap 或 Landlock,在 Windows 上会看到 ACL runner——机制不同,结论一致。想在一台机器上
看全三个平台的表现,跑 npm run start:ch07:platforms。
沙箱管什么,不管什么
它只管对文件系统的改动,约束程度分三种模式:
| 模式 | 能往哪儿写 | 典型用途 |
|---|---|---|
| read-only | 只有/dev/null这一个必需的“下水道” | 只读探索、搜索、跑 |
| workspace-write | 再加上工作区根目录,以及提供方承诺的临时区 | 正常的编码任务 |
| danger-full-access | 不限制 | 显式提权后的重试 |
表格里冒出个新词——提供方。沙箱是分两层做的:一层只定规矩(要包装 argv、要报告执行结果,至于怎么做它不管),真正把规矩变成现实的,是另一个包,就是本讲开头装的
@deepseek-ai/dsh-sandbox-local。用哪个 runner、怎么包装 argv、执行得怎么样,全由它说了算;消费方只拿结果,不关心内部。所以表里说的“提供方承诺的临时区”,就是它
答应给你的那块可写地方。
表头里的“能往哪儿写”是广义的写:创建、删除、重命名、改属性,凡是让文件系统发生变化的操作都算,不只是往文件里写数据。删个文件、给文件改个时间戳,一样受这张表约束。
会一直见到它)。
这里值得停一下:在 dsh 里,“不套沙箱”只有一条来路,就是策略被显式指定成了这个模式(比如用户批准了一次提权重试)。它不是“沙箱没配好、干脆裸跑”的兜底路径,真到沙箱不
可用时,提供方会直接抛异常,也就是后面要讲的“失败关闭”。所以每一次没套沙箱的执行,都能在策略里追溯到出处。
而不在这个词汇表里的东⻄,得记牢:
不管网络:沙箱里的进程照样能联网、照样能往外发数据。
不管进程可见性:它能看见宿主的其他进程。
也不管读:它只拦写、不拦读——宿主上的密钥、登录凭据,进程照样读得到。
换句话说,这是一个文件效果围栏,不是一个通用沙箱。把它当后者用,就会在“为什么它没拦住网络请求”上栽跟头。
沙箱怎么用:命令一个字不改,前面加半截
用法就一句话:
复制代码 1const confined = ctx.sandbox.confine(argv, policy)
argv 就是一条命令拆成的数组,第一个元素是要运行的那个程序(严格说是程序名,比如 bash,真正的文件路径由系统去查),后面是传给它的参数。注意它不是 shell 字符串:要
跑 shell,得传 ['bash', '-c', command],而不是 'bash -c ...'。这句约束看着琐碎,其实是安全前提:一旦传字符串,谁来解释、边界在哪儿就全乱了。第二个参数 policy
是第一节说的模式加工作区根目录,它怎么定,下一节单独讲。