dsh 的沙箱本质是针对文件影响策略的进程隔离——沙箱接口把一份确切的 argv 包装在一套主机的路径允许/拒绝规则之下。它属于"同世界"能力接口(与宿主共享内核与文件系统;容器与微虚拟机则替换周围的接口)。本页勾勒整个区域:服务定义、本地 runner 链、策略所有者,以及接入的各强制提供方。Linux 的 Landlock runner 有专门页面,见 Landlock:原生 Linux runner。
| 包 | 角色 |
|---|---|
packages/sandbox/sandbox | ctx.sandbox 服务定义 + 升级(escalation)词汇 |
packages/sandbox/sandbox-local | 本地后端:选择平台 runner 链 |
packages/sandbox/sandbox-policy | ctx.sandboxPolicy:唯一的策略中心(模式 + 工作区根) |
packages/sandbox/sandbox-windows-acl | Windows 受限令牌 / ACL 后端 |
packages/fs/fs-sandbox | ctx.fs 的沙箱强制实现 |
packages/shell/bash-sandbox | ctx.shell 的沙箱强制 bash 执行器 |
packages/subprocess/subprocess | 提供方依赖的 ctx.subprocess 服务 |
native/landlock-run | 预编译 landlock-run 二进制家族(entry + -linux-x64/-linux-arm64) |
沙箱接口:一个服务,多个提供方
服务定义位于 packages/sandbox/sandbox/src/index.ts。它声明了注册为 ctx.sandbox 的抽象 SandboxProvider,以及三方共享的环境类型。它刻意保持"进程形态":唯一的动词就是 confine。
export abstract class SandboxProvider extends Service {
constructor(ctx: Context) { super(ctx, 'sandbox') }
abstract confine(argv: readonly string[], policy: SandboxPolicy): ConfinedArgv
}confine 接收调用方即将 spawn 的确切 argv(绝非 shell 字符串——shell 形态的消费者传入 ['bash', '-c', command]),返回替代原 argv 的 argv(前缀加上 runner),并附带分类事实。其契约是 fail-closed:必须得到强制,否则 confine 不得返回未隔离的 argv。当没有任何可用后端时,它抛出 SandboxUnavailableError,错误码为 SANDBOX_UNAVAILABLE。
ConfinedArgv 结果携带三类供消费者解读失败运行的事实:
enforcement: 'full' | 'partial'——所选后端的强制完备度(较旧 Landlock ABI 或 Windows ACL 后端为 partial);denialSignatures——被拒绝的文件影响在本后端方言下产生的 stderr 子串(bwrap 的 EROFS 文本、Landlock 的permission denied、Seatbelt 的operation not permitted、Windows 的access is denied);runnerFailureRules——证明 runner 在执行前死亡的证据(例如bwrap:或带版本号的 exit-125 行),使"命令从未运行"与"被隔离所阻止"永不被混淆。
模式词汇
SandboxMode 是三种文件影响模式的封闭联合:
| 模式 | 含义 |
|---|---|
read-only | 只有必需的下游流(如 /dev/null)可写;沙箱进程不得写入其他任何内容 |
workspace-write | 额外允许在工作区根及后端定义的临时区域下写入 |
danger-full-access | 完全绕过隔离 |
ConfinedSandboxMode 是不含 danger-full-access 的子集。网络访问与进程可见性刻意被排除在词汇之外——模式只承诺文件影响。
沙箱覆盖什么
- 进程执行——确切的包装 argv,由内核 runner 或进程内围栏隔离。
- 文件系统——读取自由通过;写入由模式管控(见 文件系统观察与沙箱策略)。
- 网络:这里不隔离。文档明确指出网络访问不属于该模式的文件影响承诺,因此依赖其他接口/凭证。
按调用解析的策略,而非全局开关
sandbox-policy 的核心思想:策略每次能力调用解析一次(ctx.sandboxPolicy.resolve()),而非固定在提供方身上。同一时刻两个消费者可在不同策略下隔离,一次经批准的升级就是一次携带更宽策略的新调用。
策略中心位于 packages/sandbox/sandbox-policy/src/index.ts。SandboxPolicyService(注册为 ctx.sandboxPolicy)拥有部署默认模式、回退工作区根与会话级解析。其插件配置是唯一的共享策略配置:
export interface Config {
mode?: SandboxMode // 默认 'read-only'——fail-safe 默认值
workspaceRoot?: string // 回退根目录,默认 process.cwd()
}解析优先级(高者优先):某次调用经批准的显式 mode 覆盖 → 会话的最近一条 sandbox/mode 事件 → 部署默认值。workspace-write 根是会话不可变的 cwd;配置的根是 agentless 调用时的回退。
会话级覆盖以 sandbox/mode 会话事件的形式存在于会话日志中(packages/sandbox/sandbox-policy/src/session-mode.ts)——setSandboxMode(session, mode) 追加事件,effectiveSandboxMode(events) 折叠它,重放日志即状态本身。会话之间互不共享状态,也没有外部配置存储。
策略服务还会贡献一份运行时上下文快照(sandbox:policy,顺序 110),使模型无需重写系统提示即可在上下文中看到当前模式与根目录。
本地 runner 链(sandbox-local)
LocalSandboxProvider(packages/sandbox/sandbox-local/src/index.ts)是默认后端。它先按平台、再按功能探测选择 runner:
| 平台 | 链(偏好顺序) | 声明的强制级别 |
|---|---|---|
linux | bwrap → Landlock launcher | full / 依探测而定 |
darwin | sandbox-exec(Seatbelt) | full |
win32 | Windows ACL 受限令牌 runner | partial |
单一候选的链无需探测即被选中(其执行期拒绝仍会 fail-closed);多个候选则用功能探测来仲裁(defaultProbeBwrap、Landlock 的 probe、defaultProbeSeatbelt、defaultProbeWindowsAcl),每个至多一次并在提供方生命周期内缓存。没有链的平台抛出 SandboxUnavailableError。
是的——sandbox-local 会调用 landlock-run。在 Linux 上,当 bwrap 被证明不可用时,landlockProfileArgs(packages/sandbox/sandbox-local/src/profiles.ts)会构造 --ro /,并在 workspace-write 下追加 --rw /tmp 与 --rw <workspaceRoot>,随后 confine 以 [launcher, ...grants, '--', ...argv] 的形式 spawn。launcher 路径、探测与授权参数构造都来自 @deepseek-ai/node-addon-landlock-run,详见 Landlock。
每个 runner 使用各自的配置方言(profiles.ts):bwrap 用 --ro-bind / / + --dev /dev + --proc /proc + --die-with-parent,在 workspace-write 下再加 --tmpfs /tmp + --bind <workspaceRoot> <workspaceRoot>;Seatbelt 生成一份 SBPL 配置,其可写根来自共享的 writableRoots;Windows runner 接收 --workspace、--temp、--mode,以及(有会话时)--write-sid/--temp-write-sid 授权参数。
writableRoots(packages/sandbox/sandbox/src/roots.ts)是"workspace-write 可写什么"的唯一出处:规范化后的工作区根、/tmp 与 os.tmpdir()。Seatbelt 配置与进程内 fs 围栏都从这里派生各自的允许列表,使写入工具与 bash 对 /tmp 是否可写永不起分歧。
Windows 故事
windows-acl 授权由 sandbox-local 以 seam 管理。工作区写 SID 按工作区派生(workspaceWriteSid,一个在服务生命周期内缓存、永不撤销的常驻 ACE);每个存活的会话/工作区对都获得一个随机私有临时目录及其专属 SID(tempWriteSid),在提供方销毁时被撤销。runner 报告 partial 强制,因为 WRITE_RESTRICTED 必须在限制列表里保留 Everyone,而 NTFS 硬链接能把同一底层文件对象别名到不同路径。
强制提供方在消费者边界接入
三个消费者在自己的后端之前挂上沙箱接口,并通过 cordis.yml 组合替换:
fs-sandbox——SandboxedFileSystem继承LocalFileSystem并注册为ctx.fs。它在两个变更操作(writeText、editText)上增加逐调用的策略围栏;读取原样放行。被拒绝的变更抛出结构化FS_SANDBOX_DENIED,由工具层映射为面向模型的[sandbox: …]标记。这是可信代码对模型控制路径的进程内围栏——对不可信代码的内核隔离仍是ctx.shell的职责。bash-sandbox——SandboxBashExecutor继承LocalBashExecutor并注册为ctx.shell。它把['bash', '-c', command]经由ctx.sandbox.confine包装,再对照后端方言的 denial/runner-failure 签名对结果的 stderr 分类。它要求ctx.sandbox与ctx.sandboxPolicy。pwsh-sandbox——PowerShell 采用同一模式的对应实现。
两个执行器都继承本地后端的机制,但沙箱策略从 ctx.sandboxPolicy 解析;runner 的选择仍是 ctx.sandbox 提供方的配置。工具层(tool-fs、tool-bash)拥有批准权并传入完整的逐调用策略——它就是下一节升级词汇的决策点。
升级:sandbox_permissions 阶梯
escalation.ts 模块是每个强制工具家族共享的编排:严格更宽的阶梯、参数校验,以及 approveEscalation——在任何执行之前,通过 ctx.approval 解决一次 sandbox_permissions 请求的按序 fail-closed 序列。
export const WIDER_MODES: Record<string, readonly SandboxMode[]> = {
'read-only': ['workspace-write', 'danger-full-access'],
'workspace-write': ['danger-full-access'],
}export const ESCALATION_TARGETS: readonly SandboxMode[] = ['workspace-write', 'danger-full-access']validateEscalationArgs 强制 sandbox_permissions 与 justification 同来同去(只有理由而无加宽,或加宽而无理由,都是畸形请求)。sandboxDenialMarker(mode) 返回模型所见的确切拒绝行([sandbox: file access denied under <mode> mode]),bash 与 fs 共享之,使模型无论内核拒绝 bash 文件影响、还是文件系统围栏拒绝变更都识别为同一拒绝;escalationHintMarker(subject) 返回同轮重试提示。加宽始终在执行期校验,绝不打进工具 schema(schema 枚举是封闭目标词汇;有效模式才是逐调用的真相)。
平台支持矩阵
| 平台 | 主要 runner | 强制级别 | 备注 |
|---|---|---|---|
| Linux | bwrap;Landlock 兜底 | full(旧 Landlock ABI 为 partial) | bwrap 需要可用的非特权命名空间/mount 配置 |
| macOS | sandbox-exec(Seatbelt、SBPL) | full | Apple 随系统附带该 CLI;一旦消失,探测即 fail-closed |
| Windows | ACL 受限令牌 runner | partial | Everyone-in-restricting-list + 硬链接边界 |
在无链的平台(或所有候选探测都失败)上,除非消费者显式选择 danger-full-access,隔离一律以 SANDBOX_UNAVAILABLE fail-closed——命令绝不在未隔离的情况下运行。另见 Landlock 的支持矩阵与回退。
延伸阅读
- Landlock:原生 Linux runner——Linux
landlock-run二进制、CLI 契约及其支持矩阵。 - 文件系统观察与沙箱策略——写入/编辑围栏、
fs/*事件,以及策略如何回答"此工具能否写入这里"。 - 权限与审批——升级如何触发
ctx.approval请求,审批管线如何被解析。 - 守卫:超时与重复提醒——与沙箱并存的终止/注入类可协作管线。
packages/sandbox/sandbox/src/index.ts——SandboxProvider服务定义与模式词汇。packages/sandbox/sandbox-local/src/index.ts——LocalSandboxProviderrunner 链及其探测。