| description | The sandbox-consuming Bash executor for deployments and maintainers choosing, configuring, or debugging confined command execution with denial and escalation facts. |
|---|---|
| kind | package-reference |
English | 中文
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.
- Use this package
- Understand the implementation
- Further Exploration
- Model Experience
- Known Limitations and Deferred Work
- Dev Note
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.
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.
| 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 } |
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'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.
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.
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.
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.
| 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 |
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.
- 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.
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
bashtool and its escalation surface. - Sandbox Agent Note — the sandbox design, escalation, and switching contract.
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.
Small fixed schema increment on requests where bash is visible, plus the current-policy clause owned by dsh-sandbox-policy.
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.
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].
Zero additional tokens on an unremarkable allowed run beyond ordinary output. Denial or failure adds the quoted conditional marker, retained until compaction.
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
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.
Conditional error text is visible for that call and retained in history until compaction.
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
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 failsstart()immediately. danger-full-accessdeliberately bypassesctx.sandbox— it is an explicit unconfined mode, not a wider sandbox profile.
Working context for maintainers — click to expand
None.