Files
deepseek-harness/docs/core-data-structures/sandbox.md
T
Huanqi Cao 7574769818 fix(bundle): drop fs-local from the Windows layer — duplicate ctx.fs registration failed load
The Windows platform layer re-enables the base fs-sandbox row (removing its disable), but still inserted dsh-fs-local: both extend FileSystem and provide ctx.fs, so every shipped win32 profile failed at load. Delete the insert; fs-sandbox stays the single fs provider exactly as on POSIX. Sync the roster specs, the base/reference README pairs, the sandbox core doc (read-only grants no sink on Windows), the windows-shell JSDoc, and re-record the i18n pairings.
2026-08-08 02:11:15 +08:00

8.5 KiB

Process Sandbox

English | 中文

The process-sandbox seam of dsh-sandbox wraps a same-world subprocess argv in a file-effect policy without coupling consumers to a platform runner. dsh-sandbox-local supplies the Linux bwrap/Landlock and macOS Seatbelt backends; dsh-bash-sandbox is the first consumer. Containers, microVMs, and remote execution are sibling implementations of whole capability seams, not providers of ctx.sandbox.

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

Modes and enforcement

SandboxMode governs filesystem effects only. read-only denies every write — the POSIX runners additionally grant the /dev/null sink their shells require, while the Windows ACL runner grants nothing; workspace-write permits writes under the workspace root and the backend's promised temp area; danger-full-access bypasses confinement. Network and process visibility are outside this vocabulary.

/**
 * File-effect policy for confined processes. `read-only` permits only required
 * sinks such as `/dev/null`; `workspace-write` also 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'

Only the first two modes can be sent to a provider. A danger-full-access consumer spawns its original argv and does not call ctx.sandbox.

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

Enforcement is a reported fact. full means the backend governs every file effect promised by the mode; partial means an active backend or older kernel ABI governs only a subset, so consumers that require the absolute promise must reject or surface that distinction.

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

Per-call policy

The complete execution policy is resolved and carried per capability call. It includes danger-full-access so a consumer can resolve policy once before deciding whether to bypass confinement. Normal tool calls derive workspaceRoot from the calling session's immutable cwd; deployment configuration is the agentless fallback. The root is canonicalized with filesystem semantics before lexical normalization, so a cwd containing symlink/.. identifies the directory where a spawned process actually runs.

/**
 * 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
}

ctx.sandboxPolicy.resolve() accepts the active session and, for an approved retry, an explicit mode. The service owns precedence and root fallback so bash and fs do not repeat it.

/** 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
}

Only a confined execution reaches ctx.sandbox; its provider policy narrows the mode while retaining the same root. This permits concurrent sessions, consumers, and one-shot escalated retries to ask the same provider for different boundaries without mutating provider state.

/**
 * 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
}

Wrapped argv and classification dialects

RunnerFailureRule combines evidence that a runner failed before executing the command. A consumer requires a nonzero exit, the optional allowed-exit-code gate, and a case-insensitive fatal signature within one remaining stderr line. Case-insensitive exact full-line informational exclusions are removed first, so a benign runner notice cannot prove failure by itself. The matched line remains available as error detail; classification does not rewrite stderr.

/**
 * 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 is what the consumer spawns. Besides the replacement argv, it carries the backend's enforcement fact and two orthogonal stderr classifiers. denialSignatures identify the confined command being blocked while the sandbox works correctly. runnerFailureRules identify the sandbox runner refusing or failing before it executes the command; consumers check these first and surface a sandbox infrastructure failure, never an ordinary task failure.

/**
 * 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[]
}

The local provider owns operator configuration and maps its runner dialect into these rules. The sandboxed bash consumer owns spawn and result attribution.

Provider and fail-closed errors

ctx.sandbox.confine(argv, policy) returns a ConfinedArgv or throws SandboxUnavailableError with code SANDBOX_UNAVAILABLE when no usable backend exists. Consumers may also classify a failure while spawning or observing the returned argv; that attribution belongs to the consumer contract. Silent unconfined passthrough is never legal for a confined policy.

Provider selection, probing, caching, and backend-specific enforcement reports belong to the local provider.