Files
deepseek-harness/packages/shell/bash-sandbox/README.md
T

11 KiB

description, kind
description kind
The sandbox-consuming Bash executor for deployments and maintainers choosing, configuring, or debugging confined command execution with denial and escalation facts. package-reference

@deepseek-ai/dsh-bash-sandbox

English | 中文

Summary

dsh-bash-sandbox is the sandbox-consuming Bash executor: every command runs as a fresh bash -c process confined through the ctx.sandbox capability instead of with the harness process's full file authority. Each settled result carries the mode the command ran under, whether the sandbox denied a file operation, and how completely the selected runner enforced the requested mode. When no runner can enforce a confined mode, the call fails closed with a structured SANDBOX_UNAVAILABLE error rather than running unconfined. It is the confining sibling of dsh-bash-local — sharing its process mechanics — and the tool layer's escalation fields appear only while it is mounted.

Table of Contents


Use this package

Mount this executor instead of dsh-bash-local when commands must not run with the harness process's full file authority. It registers as ctx.shell and requires a ctx.sandbox provider plus ctx.sandboxPolicy; the model-facing bash tool works over it unchanged and advertises the sandbox_permissions/justification escalation fields.

When to choose it

Choose it when a deployment needs file-level confinement for Bash commands: the configured policy decides the default mode and workspace root, and each session can run under a different mode per call through the tool's escalation flow. The modes govern file effects only — network stays unrestricted and process visibility is backend-specific. For unconfined execution, or when no sandbox backend is available on the platform, mount dsh-bash-local instead.

Modes and file effects

Mode File effects
read-only (default) No writes anywhere; of /dev, only the /dev/null node is writable, so >/dev/null keeps working
workspace-write Writes only under the policy's workspace root plus /tmp (ephemeral under bwrap, the host /tmp under Landlock, /private/tmp plus the per-user temp dir under Seatbelt)
danger-full-access No confinement; the provider is never consulted, and results carry sandbox: { mode, denied: false }

Minimal configuration

The executor takes no sandbox configuration of its own: the default mode and workspace root come from ctx.sandboxPolicy, and the runner choice belongs to the ctx.sandbox provider. Its own config is the local executor's knobs verbatim; the generated configuration catalog is the exhaustive source.

- id: sandbox
  name: '@deepseek-ai/dsh-sandbox-local'
- id: sandbox-policy
  name: '@deepseek-ai/dsh-sandbox-policy'
  config:
    mode: read-only
    workspaceRoot: !!js process.cwd() # fallback for calls without a session cwd
- id: bash
  name: '@deepseek-ai/dsh-bash-sandbox'

Denials are result facts

A denied command is reported, not retried silently: the result carries sandbox: { mode, denied: true } and the model-facing tool appends the denial marker. When escalation is available, the model may retry the exact command once with the narrowest wider mode and a one-sentence justification; the approval prompt asks the user, and nothing executes before approval. This executor never negotiates permissions itself — the tool layer drives the override.

Failures and recovery

If no runner can enforce a confined mode, the foreground call fails with SANDBOX_UNAVAILABLE and a background process records a runner-failure fact — never a silent unconfined run. A runner-attributable spawn failure carries the original spawn error as detail; other spawn rejections keep the local executor's ordinary command-start semantics.


Understand the implementation

Implementation internals — click to expand

This section explains the design of the executor and points at the code that realizes it; the observable behavior is fully covered in Use this package.

Design concept

The executor is the sandboxing Service Provider for the ctx.shell seam: it inherits dsh-bash-local's process mechanics and re-wraps each command's exact ['bash', '-c', command] argv through ctx.sandbox.confine(), spawning the returned argv directly. Which platform runner confines the command — and whether one is usable at all — is the provider's concern; this package owns the bash side only: the selected mode, enforcement completeness, and denial classification on results.

Source map

File Role
src/index.ts Plugin entry: SandboxBashExecutor, per-process fact retention, run/start wrapping
src/helpers.ts Denial, runner-failure, and runner-spawn-failure classification
src/invariant.ts Invariant companion (no runtime invariant; classification is observable in results)
tests/ Exercised behavior across the bwrap, Landlock, and Seatbelt runners

Main flow

For a confined mode, resolve() stamps the per-call policy (the session's mode override, or the deployment fallback); run and start wrap the bash argv through the provider and hand the confined argv to the inherited subprocess path. At settlement the executor classifies the outcome: a runner failure outranks a denial because the command never ran, a failed run whose stderr carries the backend's denial dialect is reported denied: true, and every confined run carries its mode and enforcement facts. danger-full-access bypasses the provider entirely and stamps denied: false.

Invariants

  • Fail closed — a confined mode with no usable runner throws SANDBOX_UNAVAILABLE; unconfined passthrough never happens for a confined policy.
  • Deny-only at the seam — this executor never grants permission; the approval flow lives in the tool layer.
  • Per-process facts — confinement facts are retained per handle until settlement, because a provider may vary enforcement between overlapping calls.
  • File effects only — the mode vocabulary claims only file effects.

Further Exploration

Read these pages when the executor contract is not enough. They move from the seam to the sandbox capability this executor consumes.

  • shell seam — the executor contract this provider implements, including the request/spec split.
  • bash-local — the process mechanics this executor inherits.
  • sandbox seam — the confinement capability, its modes, and its fail-closed contract.
  • sandbox-policy — the per-session mode and workspace root this executor honors.
  • sandbox-local — the shipped runner backends: bwrap, Landlock, and Seatbelt.
  • tool-bash — the model-facing bash tool and its escalation surface.
  • Sandbox Agent Note — the sandbox design, escalation, and switching contract.

Model Experience

Bash tool schema, indirectly

What the model sees

The generated dsh-tool-bash schemas are the baseline. By advertising a confining sandboxMode, this backend augments bash with sandbox_permissions (enum workspace-write | danger-full-access) and justification. The policy owner separately contributes the current capability-neutral sandbox:policy context.

Token effect

Small fixed schema increment on requests where bash is visible, plus the current-policy clause owned by dsh-sandbox-policy.

KV Cache effect

A standing-policy change appends a complete owner-rendered context snapshot after retained history, preserving the existing system/history prefix byte-for-byte. Changing executor capabilities alters the bash schema.

Bash tool result, indirectly

What the model sees

After ordinary bounded output, a denied call appends exactly [sandbox: file access denied under <mode> mode]. When escalation is available it next appends [sandbox: escalation available — retry this exact command once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]. A settled background runner failure instead appends [sandbox: the sandbox runner itself failed under <mode> mode — the command did not run; this is a sandbox problem, not a command failure].

Token effect

Zero additional tokens on an unremarkable allowed run beyond ordinary output. Denial or failure adds the quoted conditional marker, retained until compaction.

KV Cache effect

Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.

Bash tool error, indirectly

What the model sees

If no runner can enforce a confined mode, the foreground call propagates the SANDBOX_UNAVAILABLE error from the sandbox seam. A runner-attributable spawn failure supplies the original spawn error as detail; a rejection without ENOENT/EACCES path or syscall evidence that names argv[0] remains an ordinary command-start error. A settled runner failure supplies the matched fatal stderr line and preserves the original stderr collection; the appended Runner failure: <detail> is the authoritative diagnosis over the generic SANDBOX_UNAVAILABLE prefix.

Token effect

Conditional error text is visible for that call and retained in history until compaction.

KV Cache effect

Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.

Known Limitations and Deferred Work

These limits define when this executor is not a general security boundary. They are current package constraints, not a roadmap.

  • Confinement covers file effects only — network restriction and a uniform process-visibility guarantee are absent, so the modes are not a general-purpose security sandbox.
  • Denials are inferred from failed-command stderr — backend signatures make the inference portable, but a matching application error can be classified as a denial and a denial omitted from the retained tail can be missed.
  • An asynchronously observed background runner failure has no immediate error channel — it is recorded on the settled process and surfaces when the caller reads the generic task with job_output; a synchronous subprocess throw that names the runner path instead fails start() immediately.
  • danger-full-access deliberately bypasses ctx.sandbox — it is an explicit unconfined mode, not a wider sandbox profile.

Dev Note

Working context for maintainers — click to expand

None.