Skip to content

dsh 的沙箱本质是针对文件影响策略的进程隔离——沙箱接口把一份确切的 argv 包装在一套主机的路径允许/拒绝规则之下。它属于"同世界"能力接口(与宿主共享内核与文件系统;容器与微虚拟机则替换周围的接口)。本页勾勒整个区域:服务定义、本地 runner 链、策略所有者,以及接入的各强制提供方。Linux 的 Landlock runner 有专门页面,见 Landlock:原生 Linux runner

角色
packages/sandbox/sandboxctx.sandbox 服务定义 + 升级(escalation)词汇
packages/sandbox/sandbox-local本地后端:选择平台 runner 链
packages/sandbox/sandbox-policyctx.sandboxPolicy:唯一的策略中心(模式 + 工作区根)
packages/sandbox/sandbox-windows-aclWindows 受限令牌 / ACL 后端
packages/fs/fs-sandboxctx.fs 的沙箱强制实现
packages/shell/bash-sandboxctx.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

ts
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.tsSandboxPolicyService(注册为 ctx.sandboxPolicy)拥有部署默认模式、回退工作区根与会话级解析。其插件配置是唯一的共享策略配置:

ts
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

LocalSandboxProviderpackages/sandbox/sandbox-local/src/index.ts)是默认后端。它先按平台、再按功能探测选择 runner:

平台链(偏好顺序)声明的强制级别
linuxbwrap → Landlock launcherfull / 依探测而定
darwinsandbox-exec(Seatbelt)full
win32Windows ACL 受限令牌 runnerpartial

单一候选的链无需探测即被选中(其执行期拒绝仍会 fail-closed);多个候选则用功能探测来仲裁(defaultProbeBwrap、Landlock 的 probedefaultProbeSeatbeltdefaultProbeWindowsAcl),每个至多一次并在提供方生命周期内缓存。没有链的平台抛出 SandboxUnavailableError

是的——sandbox-local 会调用 landlock-run。在 Linux 上,当 bwrap 被证明不可用时,landlockProfileArgspackages/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 授权参数。

writableRootspackages/sandbox/sandbox/src/roots.ts)是"workspace-write 可写什么"的唯一出处:规范化后的工作区根、/tmpos.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。它在两个变更操作(writeTexteditText)上增加逐调用的策略围栏;读取原样放行。被拒绝的变更抛出结构化 FS_SANDBOX_DENIED,由工具层映射为面向模型的 [sandbox: …] 标记。这是可信代码模型控制路径的进程内围栏——对不可信代码的内核隔离仍是 ctx.shell 的职责。
  • bash-sandbox——SandboxBashExecutor 继承 LocalBashExecutor 并注册为 ctx.shell。它把 ['bash', '-c', command] 经由 ctx.sandbox.confine 包装,再对照后端方言的 denial/runner-failure 签名对结果的 stderr 分类。它要求 ctx.sandboxctx.sandboxPolicy
  • pwsh-sandbox——PowerShell 采用同一模式的对应实现。

两个执行器都继承本地后端的机制,但沙箱策略从 ctx.sandboxPolicy 解析;runner 的选择仍是 ctx.sandbox 提供方的配置。工具层(tool-fstool-bash)拥有批准权并传入完整的逐调用策略——它就是下一节升级词汇的决策点。

升级:sandbox_permissions 阶梯

escalation.ts 模块是每个强制工具家族共享的编排:严格更宽的阶梯、参数校验,以及 approveEscalation——在任何执行之前,通过 ctx.approval 解决一次 sandbox_permissions 请求的按序 fail-closed 序列。

ts
export const WIDER_MODES: Record<string, readonly SandboxMode[]> = {
  'read-only': ['workspace-write', 'danger-full-access'],
  'workspace-write': ['danger-full-access'],
}
ts
export const ESCALATION_TARGETS: readonly SandboxMode[] = ['workspace-write', 'danger-full-access']

validateEscalationArgs 强制 sandbox_permissionsjustification 同来同去(只有理由而无加宽,或加宽而无理由,都是畸形请求)。sandboxDenialMarker(mode) 返回模型所见的确切拒绝行([sandbox: file access denied under <mode> mode]),bash 与 fs 共享之,使模型无论内核拒绝 bash 文件影响、还是文件系统围栏拒绝变更都识别为同一拒绝;escalationHintMarker(subject) 返回同轮重试提示。加宽始终在执行期校验,绝不打进工具 schema(schema 枚举是封闭目标词汇;有效模式才是逐调用的真相)。

平台支持矩阵

平台主要 runner强制级别备注
Linuxbwrap;Landlock 兜底full(旧 Landlock ABI 为 partial)bwrap 需要可用的非特权命名空间/mount 配置
macOSsandbox-exec(Seatbelt、SBPL)fullApple 随系统附带该 CLI;一旦消失,探测即 fail-closed
WindowsACL 受限令牌 runnerpartialEveryone-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——LocalSandboxProvider runner 链及其探测。