Sandbox mode is read-only (the fail-safe default), workspace-write, or danger-full-access, resolved per call by ctx.sandboxPolicy. A per-session override is one append-only sandbox/mode event, so it survives restart by replay. Local backends are bwrap or Landlock on Linux, Seatbelt on macOS, and an ACL restricted token on Windows; unsupported platforms fail closed with SANDBOX_UNAVAILABLE rather than running unconfined.
Most agent tools describe their sandbox in marketing terms. DeepSeek Harness documents where its sandbox is partial, which is a better signal about the engineering than any claim of complete isolation would be.
Three packages
| Package | Role | Key |
|---|---|---|
dsh-sandbox | Defines the process-sandbox service and escalation vocabulary | ctx.sandbox |
dsh-sandbox-local | Local platform confinement backends | registers on ctx.sandbox |
dsh-sandbox-policy | Resolves durable per-session policy | ctx.sandboxPolicy |
The family covers same-world subprocesses. Isolated environments replace complete capability implementations rather than registering here — a distinction worth holding onto, because it means "sandbox" here is about confining local process execution, not about running your agent somewhere else.
The three modes
| Mode | Effect |
|---|---|
read-only | The default. No file modification through the DSH file sandbox. |
workspace-write | Writes permitted under the session workspace root. |
danger-full-access | No confinement. |
read-only is the deployment default and the source calls it fail-safe — the correct default for
a system that can execute shell commands in your repositories.
Why policy has one home
Filesystem tools, one-shot bash commands and terminal sessions may all enforce the same mode vocabulary in different combinations. If each resolved its own mode and workspace root, they could drift into a split world — one tool believing it is read-only while another writes.
So ctx.sandboxPolicy is the single owner. Every enforcing capability receives one resolved
mode-and-root policy per call:
ctx.sandboxPolicy.resolve({ session?, mode? })
Precedence: an explicit approved mode outranks the session's last sandbox/mode event, which
outranks defaultMode. The session's immutable cwd is canonicalized with filesystem semantics
before becoming workspaceRoot, otherwise the configured fallback applies.
Config
| Key | Default | Meaning |
|---|---|---|
mode | read-only | Deployment default mode, validated at load |
workspaceRoot | process.cwd() | Fallback write root for agentless calls or sessions with no cwd |
A normal agent call uses its session header's immutable cwd instead of the fallback.
A mode switch is an event
setSandboxMode(session, mode) // appends exactly one sandbox/mode event
effectiveSandboxMode(events) // pure fold; last switch wins
This is the harness's event-sourcing discipline applied to permissions. The switch IS its event — nothing mutates the mode out of band. So:
- an override survives restart by replay
- two sessions never see each other's state
- the exact policy input stays reconstructable, because the agent loop logs the assembled runtime
context snapshot as a sourced
user/message
The optional ./invariant companion rejects a forged durable sandbox/mode event whose value
falls outside the closed vocabulary.
What the model is told
The sandbox:policy contribution appears in every agent session's runtime-context snapshot. It
states the mode's capability-neutral file-effect contract — and deliberately does not enumerate
mounted capabilities.
The read-only text is instructive:
Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.
That last sentence is careful prompt engineering. Told only "you are read-only", a model tends to pre-emptively refuse work it could actually do. The instruction pushes it to attempt and read the denial rather than reason from the policy label.
The backends
dsh-sandbox-local selects and caches one platform runner:
- Linux — a working
bwrap, then Landlock - macOS — Seatbelt
- Windows — an ACL restricted-token runner
Multiple candidates are probed in order; a sole candidate is selected directly.
Each wrap reports enforcement completeness plus backend-specific denial signatures and
runner-failure rules, so consumers can distinguish a broken sandbox from a command failure.
Landlock requires exit 125 and a landlock-run: fatal line; bubblewrap and Seatbelt remain
signature-only because neither public contract reserves a launcher-failure status.
The macOS Seatbelt profile is allow-default with (deny file-write*) plus write allow-lists.
read-only grants the /dev/null literal alone; workspace-write adds the workspace root, /tmp
and the per-user darwin temp dir — every root canonicalized, because Seatbelt matches resolved paths
(/tmp is /private/tmp).
The limits the project states about itself
This is the part worth reading before you rely on any of it.
Windows ACL enforcement is partial. The restricted token must retain Everyone for process
initialization, so external objects granting Everyone write access remain writable; NTFS hard links
also alias one file object across workspace and external paths. The provider reports
enforcement: 'partial' rather than overstating the boundary.
Landlock may be partial. Older supported kernel ABIs confine only the access classes they
expose, again reported as partial.
Seatbelt depends on deprecated sandbox-exec. Apple marks the CLI deprecated but ships it on
every macOS; the functional probe is what fails closed if that changes.
Runner selection is cached for the provider lifetime. Installing, removing or repairing a runner requires reloading the plugin before selection changes.
runnerCommand is an operator assertion. A configured custom runner skips functional probes and
is assumed to implement the bwrap-compatible profile honestly. And a sharp detail: if that runner
is itself a Bash script, its interpreter startup runs before the script applies confinement.
What to actually do with this
In CI, set the mode deliberately. An unattended agent with repository write access is a
deployment decision. Sandbox and approval policy are patchable rows from dsh-base, which cuts both
ways — see patching plugin config and put the patch that encodes
your choice in version control where a reviewer sees it.
Do not treat workspace-write as containment on Windows. The partial-enforcement note is
explicit, and hard-link aliasing is not theoretical.
Do not rely on the workflow worker thread as a security layer — it is not one, and the sandbox seam is a different mechanism entirely.
Frequently asked
What is the default sandbox mode?
read-only, chosen as the fail-safe default. workspace-write and danger-full-access are the other two values, validated at load.
What happens on an unsupported platform?
It fails closed with SANDBOX_UNAVAILABLE. Execution never silently falls through unconfined — that is the property that makes the seam trustworthy.
Is the Windows sandbox complete?
No, and the project says so. The restricted token must retain Everyone for process initialization, so external objects granting Everyone write access remain writable, and NTFS hard links alias one file object across paths. The provider reports enforcement: 'partial' rather than overstating it.
How is a per-session mode change stored?
As exactly one append-only sandbox/mode event on that session. The switch IS its event — nothing mutates the mode out of band, and the effective value is explicit grant, then the fold of events, then the deployment default.