Files
deepseek-harness/packages/bash/bash-sandbox

@deepseek-ai/dsh-bash-sandbox

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) — the model-facing tool layer (dsh-tool-bash) is untouched; that swap is exactly what the seams exist for.

Every command is confined by handing the provider the exact ['bash', '-c', command] argv this executor is about to spawn and spawning the returned (wrapped) argv instead. 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. Execution is dsh-bash-local's verbatim — foreground results still carry sandbox: { mode, denied: false } (no enforcement: nothing was confined), background tasks 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, or partial on an older Landlock ABI).
  • Runner failures are sandbox failures, never task failures. A failed run matching the wrap's runnerFailureSignatures (the runner's own error prefix — also what the shell prints for a missing runner) means the sandbox itself broke and the command NEVER RAN; the check outranks denial classification because a runner's error text can contain denial words. The foreground path re-throws it as the structured fail-closed SANDBOX_UNAVAILABLE error, with the runner's first stderr line as the cause; a settled background task stamps task.sandbox.runnerFailed instead (no error channel remains after settle), which bash_output renders as its own marker.
  • Config default, per-call override. resolve() stamps the configured sandbox mode onto each spec unless an approved request supplies a wider mode. That override affects only its call or background task. ctx.bash.sandboxMode reports the default so the tool advertises escalation only when supported; results report the effective mode.
  • 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 tasks, credential scrub) are inherited verbatim from dsh-bash-local; the runner ladder, probes, and the per-platform Landlock launcher packages live with dsh-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: bash
  name: '@deepseek-ai/dsh-bash-sandbox'
  config:
    mode: read-only
    workspaceRoot: !!js process.cwd()

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); see examples/sandbox-acp-agent for the runnable demo.