Files
deepseek-harness/docs/core-data-structures/sandbox.md
Yichen Jiang e7894f4152 docs(credentials): record the third-review contracts across READMEs, catalogs, and a new Agent Note
Both provider READMEs state what actually holds: credentials-local now
documents the physical-line editor, the read-modify-write under the
writer lock, and a Security boundary section saying plainly that the file
mode stops other OS users and not the model. sandbox-policy documents
readDenyPaths and its per-backend enforcement. The llm READMEs carry the
registration handle, pi-ai's credential-miss semantics, and DeepSeek's
same-generation snapshot; app-boot and the CLI README stop describing
$DSH_HOME/.env as an environment layer.

A new Agent Note records the round (and the prior seam note cross-links
it); the sandbox and core catalog pages gain readDenyPaths and
AdapterRegistrationHandle with their manifest entries. The headless
missing-credential snapshot re-records for the reworded guidance, pi-ai
gains the Loader-composition guard its twin already had, and the
deliberate provider symmetry is marked for the clone detector.
2026-07-30 16:37:28 +08:00

8.2 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 writes except the required /dev/null sink; 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. readDenyPaths names paths a confined execution must not read whatever its mode permits — the harness credential document by default — and backends that cannot express such a denial report partial enforcement rather than claiming a boundary the process lacks.

/**
 * 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
  /**
   * Absolute paths a confined execution must not READ, whatever the mode
   * otherwise permits — the harness's own credential document is the
   * motivating case, which is why these are exact paths rather than roots:
   * denying the whole harness home would also take away the model's
   * documented access to its own session log. Not every backend can express
   * a read denial (a Landlock allow-list granting `/` cannot subtract from
   * itself), so {@link ConfinedArgv.enforcement} drops to `partial` when a
   * denial is requested and the selected backend cannot apply it. Never a
   * boundary under `danger-full-access`, which confines nothing at all.
   */
  readDenyPaths?: readonly 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

ConfinedArgv is what the consumer spawns. Besides the replacement argv, it carries the backend's enforcement fact and two orthogonal stderr dialects. denialSignatures identify the confined command being blocked while the sandbox works correctly. runnerFailureSignatures 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[]
  /**
   * Case-insensitive signatures for runner failure before command execution.
   * Consumers check these before denial signatures: runner failure means the
   * command never ran, while denial means confinement worked and blocked it.
   */
  runnerFailureSignatures: readonly string[]
}

An operator-configured local runner must supply at least one runnerFailureSignatures entry for its own pre-exec refusal dialect; the provider adds outer-shell missing and unexecutable forms automatically. This makes an executable custom runner rejecting its profile distinguishable from the wrapped command exiting with the same status.

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. A selected runner can also fail closed at execution time, in which case its failure signature carries the same infrastructure meaning. Silent unconfined passthrough is never legal for a confined policy.

Provider probing arbitrates between multiple candidates and is cached for the provider lifetime. A platform with one candidate may select it directly; execution-time refusal retains the safety property. The local provider reports bwrap and Seatbelt as full and preserves the Landlock launcher's full/partial kernel verdict.