@deepseek-ai/dsh-bash-sandbox
English | 中文
Sandbox-consuming implementation of the @deepseek-ai/dsh-bash 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
BashRunResult.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). - Runner failures are sandbox failures, never command failures. Before a process starts, a rejection is attributed to the runner only when the caller-owned workdir is independently usable and the error carries Node spawn provenance for provider argv[0] plus an executable-class code. This covers missing, non-executable, and unloadable runners, including a script whose shebang interpreter is unavailable; an invalid or unusable workdir, resource failure, unrelated syscall, or unstructured rejection retains the local executor's command-start failure semantics. Foreground execution and a synchronous background start throw
SANDBOX_UNAVAILABLEwith the original spawn detail, while asynchronous background settlement stampsrunnerFailed: trueanddenied: false. After a process starts, a rule's optional exit-code gate and a remaining fatal stderr line must both match after exact informational-line exclusions. A match outranks denial; foreground execution throwsSANDBOX_UNAVAILABLEwith the matched fatal line, while a settled background process stampsprocess.sandbox.runnerFailed, which the bash producer renders through generictask_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.bash.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'
The keyless consumer-integration proofs are tests/bwrap.e2e.ts, tests/landlock.e2e.ts, and tests/seatbelt.e2e.ts (the real provider + real runner driven through ctx.bash, world-verified, each self-skipping where its runner is absent). The agent-spine e2e additionally drives two concurrent sessions in one Cordis context and proves each real bash tool call can write only its own project. See the acp-agent example's default composition for the runnable demo.
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 executable-class argv[0] evidence remains an ordinary command-start error. A settled runner failure supplies the matched fatal stderr line and preserves the original stderr collection.
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.
- A 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
task_output. danger-full-accessdeliberately bypassesctx.sandbox— it is an explicit unconfined mode, not a wider sandbox profile.