进程沙箱

English | 中文

dsh-sandbox 的进程沙箱 seam 将与配套子进程提供方共享执行环境的子进程 argv 包装在文件效果策略中,而不将消费方耦合到特定平台运行器。dsh-sandbox-local 提供 Linux bwrap/Landlock、macOS Seatbelt 与 Windows ACL 受限令牌后端;dsh-bash-sandbox 和 dsh-pwsh-sandbox 是其消费方。dsh-sandbox-ssh 通过与 SSH 文件系统及子进程提供方配套的远端后端执行同一策略。

源码:packages/sandbox/sandbox/src/index.ts

模式与强制执行

SandboxMode 仅管控文件系统效果。read-only 要求后端拒绝写入——POSIX runner 还会授予其 shell 所需的 /dev/null 接收器,而 Windows ACL runner 不授予任何显式可写根目录,并因环境 ACL 缺口报告部分强制执行;workspace-write 允许在工作区根目录及后端承诺的临时区域下写入;danger-full-access 绕过隔离。网络与进程可见性不在此处的定义范围内。

``ts type-equiv /** * File-effect policy for confined processes.read-onlypermits only required * sinks such as/dev/null;workspace-writealso permits the workspace and a * backend-defined temp area;danger-full-access` bypasses confinement. Network * and process visibility are outside this vocabulary. */ type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access'


只有前两种模式可以发送给提供方。`danger-full-access` 的消费方直接 spawn 原始 argv,不调用 `ctx.sandbox`。

```ts type-equiv
/** A confining (non-`danger-full-access`) mode — the modes a {@link SandboxPolicy} can carry. */
type ConfinedSandboxMode = Exclude<SandboxMode, 'danger-full-access'>

强制执行完整性是后端报告的事实。full 表示后端管控了该模式承诺的所有文件效果;partial 表示活跃后端或较旧的内核 ABI 仅管控其中一个子集,因此要求绝对保证的消费方必须拒绝或向上暴露这一区别。当前的部分强制执行情形包括较旧的 Landlock ABI,以及 Windows ACL runner 的硬链接、读取不受限与 AppContainer ACL 边界。

``ts type-equiv /** * Enforcement completeness for this host.partialmeans an active backend or * older kernel ABI cannot govern every promised file effect; callers requiring * an absolute boundary must not treat it asfull`. */ type SandboxEnforcement = 'full' | 'partial'


## 逐调用策略

完整执行策略会按每次能力调用解析并携带。它包括 `danger-full-access`,因此消费方可以只解析一次策略,再决定是否绕过约束。普通工具调用从调用会话的不可变 cwd 派生 `workspaceRoot`;部署配置是没有 agent(智能体)时的回退值。解析器保留执行环境中的绝对路径写法。执行限制的提供方在文件实际存在的位置规范化根目录,因此包含 `symlink/..` 的 cwd 会标识配套子进程提供方实际运行的目录。

```ts type-equiv
/**
 * The complete file-effect policy resolved for one capability call. The root
 * is carried even under modes that do not consume it so callers can resolve
 * policy once before choosing the enforcement path.
 */
interface SandboxExecutionPolicy {
  /** The file-effect mode this execution runs under. */
  mode: SandboxMode
  /** Absolute root directory `workspace-write` may write under. */
  workspaceRoot: string
  /**
   * Opaque identity of the calling session (the branded `dsh-session`
   * SessionId). Backends key per-session state off it (e.g. windows-acl gives
   * each live session/workspace pair a random private temp directory and SID,
   * while the workspace SID and standing grant remain per-workspace); absent
   * for agentless calls, which fall back to per-call backend state.
   */
  sessionId?: SessionId
}

ctx.sandboxPolicy.resolve() 接收活跃会话;对于已批准的重试,还接收显式模式。该服务拥有优先级与 root 回退规则,使 bash 和 fs 不必重复实现。

```ts type-equiv / Inputs that select the sandbox policy for one capability call. */ interface SandboxPolicyRequest { / Calling session; its immutable cwd becomes the workspace boundary. / session?: Session / Explicit approved mode override, which outranks session policy. / mode?: SandboxMode }


只有受约束的执行会到达 `ctx.sandbox`;传给提供方的策略在保留同一 root 的同时收窄模式。这使并发会话、消费方与一次性提权重试可以向同一提供方请求不同边界,而无需改变提供方状态。

```ts type-equiv
/**
 * What one confined execution is allowed to touch — carried PER CALL, not
 * fixed on the provider: two consumers may confine under different policies
 * at the same instant (bash under `read-only` while a confined child agent
 * needs its state directory writable), and an approved escalated retry is a
 * new call with a wider policy. Defaulting/resolution is an explicit step at
 * the consumer boundary; the provider treats the policy as fully specified.
 */
interface SandboxPolicy extends SandboxExecutionPolicy {
  /** The file-effect mode this execution runs under. */
  mode: ConfinedSandboxMode
}

包装后的 argv 与分类方言

RunnerFailureRule 汇集用于判定 runner 在执行命令前失败的证据。消费方要求进程以非零状态退出,并同时满足可选的允许退出码门控,以及余下某一 stderr 行中不区分大小写的致命签名。系统会先按不区分大小写的整行精确匹配移除信息性排除项,因此无害的 runner 通知本身不能证明失败。匹配到的行仍可用作错误详情;分类过程不会重写 stderr。

```ts type-equiv / * Evidence that identifies a sandbox runner failing before it executes the * wrapped command. A consumer first applies {@link allowedExitCodes} when * present, removes {@link informationalLines} by case-insensitive exact line * equality, then matches {@link fatalSignatures} case-insensitively within * each remaining stderr line. Exit status alone never proves runner failure. */ interface RunnerFailureRule { / Nonzero process exit codes on which this rule may match; omitted permits any nonzero exit. / allowedExitCodes?: readonly number[] / Non-empty substrings identifying a fatal runner diagnostic on one stderr line. / fatalSignatures: readonly string[] /* Benign stderr lines excluded by exact full-line equality before fatal matching. / informationalLines?: readonly string[] }


`ConfinedArgv` 是消费方实际 spawn 的内容。除了替换后的 argv,它还携带后端的强制执行事实和两种正交的 stderr 分类器。`denialSignatures` 用于识别沙箱正常工作时受限命令被阻止的情况。`runnerFailureRules` 用于识别沙箱 runner 在执行命令之前拒绝或失败的情况;消费方应先检查后者,将其作为沙箱基础设施故障上报,而非普通任务失败。

```ts type-equiv
/**
 * A {@link SandboxProvider.confine} result: the argv to spawn in place of
 * the caller's own, plus the enforcement completeness the selected backend
 * achieves for it.
 */
interface ConfinedArgv {
  /** The wrapped argv (runner, profile, separator, then the caller's argv). */
  argv: string[]
  /** How completely the selected backend enforces the policy's file effects. */
  enforcement: SandboxEnforcement
  /**
   * The selected backend's denial DIALECT: the case-insensitive stderr
   * substrings a file effect denied by THIS backend produces (EROFS text
   * under bwrap's read-only binds, EACCES under Landlock, EPERM under
   * Seatbelt). A consumer that infers denials from a failed run's stderr
   * matches against exactly these rather than a cross-backend union — the
   * union claims denials a given backend never produces.
   */
  denialSignatures: readonly string[]
  /**
   * Structured runner-failure evidence rules. Consumers require a matching
   * fatal stderr line (after informational exclusions) and any rule-specific
   * exit-code gate before checking denial signatures: runner failure means the
   * command never ran, while denial means confinement worked and blocked it.
   */
  runnerFailureRules: readonly RunnerFailureRule[]
}

本地提供方拥有运维配置,并将其 runner 方言映射到这些规则。沙箱化 bash 消费方拥有 spawn 与结果归因。

提供方与 fail-closed 错误

await ctx.sandbox.confine(argv, policy, signal) 在执行环境中解析策略路径并返回 ConfinedArgv,没有可用后端时以 SandboxUnavailableError(错误码 SANDBOX_UNAVAILABLE)拒绝。可选信号可在启动前取消解析。消费方也可以在 spawn 或观察所返回的 argv 时对失败进行分类;该归因属于消费方约定。对于受限策略,静默的无隔离透传永远不合法。

提供方选择、探测、缓存和后端特定的强制执行报告归本地提供方所有。

Cordis API

Generated from source by scripts/gen-cordis-catalog.ts (verified fresh by pnpm run verify-cordis-catalog in doc-sync; regenerate with pnpm run gen-cordis-catalog) — the language sides differ only in locale-specific paired document paths. Signature blocks use a ts cordis-catalog fence and keep the original source JSDoc; dispatch modes are defined in the primer, and the framework-inherited ctx API lives in cordis-api/inherited.md.

ctx.sandbox — SandboxProvider (abstract seam)

Abstract process-sandbox service. confine must return enforcing argv or fail closed at wrap or runner-execution time; silent unconfined passthrough is forbidden. Functional probes arbitrate multi-runner chains and may be skipped for a sole candidate, whose own refusal remains the fail-closed end.

``ts cordis-catalog /** * Wrapargvso it executes confined underpolicyon this host; the * caller spawns the returned argv in place of its own. * @param argv - the exact argv the caller is about to spawn (program plus * arguments), NOT a shell string — a shell-shaped consumer passes *['bash', '-c', command]`. * @param policy - the file-effect policy this execution runs under, * carried per call (see {@link SandboxPolicy}). * @param signal - cancellation while the provider resolves the policy and runner. * @returns the argv to spawn instead, plus the enforcement completeness * the selected backend achieves for it. */ abstract confine(argv: readonly string[], policy: SandboxPolicy, signal?: AbortSignal): Promise


Source: [`packages/sandbox/sandbox/src/index.ts`](../../packages/sandbox/sandbox/src/index.ts)

<a id="ctxsandboxpolicy--sandboxpolicyservice"></a>

### `ctx.sandboxPolicy` — `SandboxPolicyService`

The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode, fallback workspace root, and current request-time policy section. Tool layers call resolve for each execution so a session's mode log and immutable cwd travel together to every enforcing capability.

```ts cordis-catalog
/**
 * Resolve the complete policy for one capability call. An approved explicit
 * mode outranks the session's last `sandbox/mode` event, which outranks the
 * deployment default. A session cwd is its workspace-write boundary; the
 * configured root is the fallback for agentless calls and sessions without a
 * cwd.
 * @param request - optional session and approved mode override.
 * @returns the fully resolved per-call mode and absolute workspace root.
 */
resolve(request: SandboxPolicyRequest = {}): SandboxExecutionPolicy

/**
 * Read the session override without applying the deployment default.
 * @param session - session whose log supplies the override.
 * @returns the last logged mode, or `undefined` without one.
 */
overrideOf(session: Session): SandboxMode | undefined

Types: Session

Source: packages/sandbox/sandbox-policy/src/index.ts