Overview
dsh-bash-sandbox
@deepseek-ai/dsh-shell executor seam. Load it instead of @deepseek-ai/dsh-bash-local, together with a ctx.sandbox provider (e.g. @deepseek-ai/dsh-sandbox-local) and a ctx.sandboxPolicy (which owns the default mode + workspace root, shared with the sandboxed filesystem) — no alternate tool plugin is needed; dsh-tool-bash detects the executor's sandboxMode capability and adds the escalation fields.This is an atomic module already shipped with Harness, not a standalone profile layer.
Capabilities
What it contributes
README / EN
Package documentation
@deepseek-ai/dsh-bash-sandbox
English | 中文
Sandbox-consuming Service Provider for the @deepseek-ai/dsh-shell executor seam. Load it instead of @deepseek-ai/dsh-bash-local, together with a ctx.sandbox provider (e.g. @deepseek-ai/dsh-sandbox-local) and a ctx.sandboxPolicy (which owns the default mode + workspace root, shared with the sandboxed filesystem) — no alternate tool plugin is needed; dsh-tool-bash detects the executor's sandboxMode capability and adds the escalation fields.
The package root exports the default and named SandboxBashExecutor plugin plus its Config; result-classification helpers stay internal.
Every command is confined by handing the provider the exact ['bash', '-c', command] argv this executor is about to spawn and spawning the returned argv directly. With the shipped native runners, the inner Bash retains shell semantics and evaluates BASH_ENV only after the runner establishes confinement. WHICH platform runner confines it — and whether one is usable at all (fail closed with a structured SANDBOX_UNAVAILABLE error, never a silent unconfined run) — is the provider's concern; this package owns the bash side only.
| 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 workspaceRoot + /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. Foreground results carry sandbox: { mode, denied: false }; background process handles carry no sandbox facts. |
Semantics:
- Denials are result facts. A failed run whose stderr carries the selected backend's own denial dialect — the signatures the provider stamps on every wrap (EROFS text under bwrap, EACCES under Landlock, EPERM under Seatbelt) — is reported as
ShellRunResult.sandbox.denied: true(conservative classification, read from the collected stderr tail); every CONFINED run also carries the mode it executed under (result.sandbox.mode) and the provider's enforcement completeness (result.sandbox.enforcement:full, orpartialon an older Landlock ABI). - The runner path or syscall must match. Before a process starts, a rejection is attributed to the runner only when the caller-owned workdir is independently usable and Node reports
ENOENTorEACCESwith either anerror.pathequal to provider argv[0] or, whenerror.pathis absent, an exactsyscall: 'spawn <runner>'. A present path also requiressyscall: 'spawn'or the exactspawn <runner>. This covers a missing runner, a non-executable runner, or an executable script whose shebang interpreter is unavailable. A baresyscall: 'spawn'without an exact error path, any other code, an invalid or unusable workdir, a resource failure, an unrelated syscall, or an unstructured rejection retains the local executor's command-start failure semantics. Foreground execution throwsSANDBOX_UNAVAILABLEwith the original spawn detail, while asynchronous background settlement stampsrunnerFailed: trueanddenied: false. If aSubprocessRuntimesynchronously throws the same runner-identifyingENOENT/EACCESshape, background start throwsSANDBOX_UNAVAILABLE; other synchronous errors propagate unchanged. After a process starts, a rule's optional exit-code check and a remaining fatal stderr line must both match after exact informational-line exclusions. A match takes priority over denial; foreground execution throwsSANDBOX_UNAVAILABLEwith the matched fatal line, while a settled background process stampsprocess.sandbox.runnerFailed, which the bash producer renders through genericjob_output. Confined background handles retain their mode/enforcement facts and release per-process accounting in either path. - Deployment fallback, per-call policy.
ctx.sandboxPolicyresolves a completeSandboxExecutionPolicyfor every tool call: the calling session supplies its mode override and immutable cwd root, while deployment config supplies the fallbacks for agentless calls. An approved escalation changes only that policy's mode; its session root stays attached.resolve()carries the policy onto the spec, so overlapping commands from different projects run, classify, and report under their own roots and modes. The capability factctx.shell.sandboxModereports the configured default so the tool layer advertises escalation only when this executor is mounted; the static bash tool description separately owns denial and escalation guidance. - File effects only. Network and process visibility are deliberately not restricted — the mode vocabulary does not pretend to cover what the backend does not enforce.
- Process mechanics (spawn, process-group kills, output collection/spill, background handles, credential scrub) are inherited from
dsh-bash-local; runner selection lives indsh-sandbox-local.
Deny-only at the seam: a denial is a reported fact, and this executor never negotiates permissions itself — the approval question lives in the tool layer (dsh-tool-bash), which drives the override this package honors.
- 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'
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 using enum workspace-write | danger-full-access and with 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 owned by dsh-sandbox. 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. When present, the appended Runner failure: <detail> is the authoritative diagnosis; the preceding backend-install text is 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
- Confinement covers file effects only — network access and process visibility are unchanged, 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 synchronousSubprocessRuntimethrow that names the runner path instead failsstart()immediately. danger-full-accessdeliberately bypassesctx.sandbox— it is an explicit unconfined mode, not a wider sandbox profile.
LIMITATIONS
Known limitations
- **Confinement covers file effects only** — network access and process visibility are unchanged, 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 `SubprocessRuntime` 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.
