DeepSeek Harness permissions and sandbox: trace every tool call before granting access

Quick answer

DeepSeek Harness does not implement tool safety as one permission switch. At the source snapshot checked for this guide—default branch master, commit 141eb6fef83422698aef7a981029e843e8161534—a tool call moves through distinct policy, approval, execution, filesystem, and result stages:

tool/call is logged and shown as pending
  -> tools/pre-execute: allow | deny | ask
  -> ask: only allowed-once opens this call
  -> monotonic guards: deny or abstain
  -> tools/execute: timeout/retry/metrics wrappers
  -> tool body and any fs write/edit intent gate
  -> tools/post-execute: accept, replace, add context, or block
  -> normalize -> finalizeContent -> frozen tools/result
  -> durable tool/result and completed UI card

The practical default is workspace-write plus ask. It limits file effects to the session workspace and supported temporary areas, while operations marked ask need a one-shot decision. But the sandbox vocabulary covers filesystem effects only. It does not promise network isolation or restricted process visibility.

DeepSeek Harness remains a Developer Preview with compatibility-breaking changes expected. GitHub currently has the dsh-v0.1.0-rc.8 pre-release at this commit, while npm still reports @deepseek-ai/dsh@0.1.0-rc.7. Recheck and pin the exact artifact you test.

Who this is for

This guide is for agent-framework developers and security-minded teams evaluating how DeepSeek Harness approves tools, confines local commands, protects file edits, and records outcomes. If you have not isolated DSH_HOME and a disposable workspace yet, start with the Developer Preview install and rollback checklist. For why these layers are separate plugins, see the Cordis architecture guide.

Four controls that must not be collapsed into one

Control Values or decisions What it controls What it does not prove
Pre-execute policy allow, deny, ask Whether this call reaches dispatch OS-level confinement
Approval policy ask, never Whether an ask reaches an answerer That an allowed call is safe
Sandbox mode read-only, workspace-write, danger-full-access Filesystem effects for enforcing capabilities Network or process isolation
Filesystem observation unseen, absent, present-at-version Read-before-edit and no-clobber freshness General authorization or persistence across resume

The shipped permission-preset table bundles two independent knobs. workspace-write means sandbox workspace-write plus approval ask. danger-full-access means no file confinement plus approval never. That second preset does not mean “allow everything”: any policy that returns ask is automatically rejected, while calls already classified allow can run without filesystem confinement.

There is no shipped read-only named preset in the documented default table. A deployment can configure different presets or produce the derived custom state by changing the sandbox and approval knobs independently.

Why the approval path fails closed

An ask opens only when ctx.approval.request() returns allowed-once. Explicit rejection, cancellation, an unavailable channel, a missing agent, a missing approval service, a throwing answerer, or a non-conforming outcome all deny the call. The request and decision are appended as the log-only approval/asked and approval/decided pair inside the open turn.

After approval, registered guards still run. A guard can return a denial reason or abstain; it has no “allow” result. That monotonic design prevents later listener ordering from reopening a call an owner policy denied. Denied calls skip the tool body but still enter post-execute policy, so auditing or corrective feedback can observe a single final outcome.

The sandbox boundary is narrower than its name

The three modes describe file effects:

The local provider selects Linux Bubblewrap before Landlock, macOS Seatbelt, and a Windows restricted-token/ACL backend. Reported enforcement can be full or partial; older Landlock ABIs and the current Windows ACL boundary are documented partial cases. An unsupported platform or unusable runner must surface SANDBOX_UNAVAILABLE, not silently run the original command.

Treat this as file-effect containment, not an internet-deny control. If an untrusted repository must not reach the network, add a separate egress boundary and verify it independently, as described in the untrusted-repository sandbox checklist.

Filesystem edits add freshness, not broader permission

The filesystem observation plugin records whether the active session has seen a target absent or present at a version. Editing an unseen file returns FS_NOT_OBSERVED; editing a known-missing file returns FS_NOT_FOUND; editing a changed file fails the provider's compare-and-swap check. A write to an observed file uses replaceIfVersion, while an unseen or absent target uses createIfAbsent.

This state is session-owned and in memory. It is cleared on plugin disposal and does not survive resume, so resumed sessions must read again. A direct ctx.fs read does not emit fs/observed, and even a windowed read authorizes a full-file overwrite only if the version is unchanged. This is a no-clobber mechanism, not proof that the model reviewed every line.

Nine fail-closed canaries

Run these with harmless fixtures in a disposable workspace before touching a real repository.

Canary Expected evidence Stop condition
1. Preset projection New session reports workspace-write + ask UI label and effective knobs disagree
2. One-shot approval One allowed-once call runs and logs one matched audit pair Grant persists to a second call
3. Missing answerer ask becomes unavailable/denied and the body never runs Missing UI opens the gate
4. Never policy ask is rejected without dispatching an answerer Headless run waits or proceeds
5. Read-only mutation Workspace and temp write fixtures are denied Any ordinary file mutation succeeds
6. Workspace containment In-root write succeeds; parent and symlink-escape writes fail Canonical escape reaches the filesystem
7. Runner failure Missing/refused runner returns SANDBOX_UNAVAILABLE Command runs unconfined
8. Enforcement report Partial backend remains labeled partial Partial is promoted to full
9. Fresh edit Read → edit works; external change → same edit returns stale and requires reread Stale content is overwritten

Add one deliberate network negative control: show that the file sandbox alone does not block a harmless outbound probe. The correct result is evidence that a separate egress policy is still required—not a reason to run a real exfiltration test.

Common mistakes

FAQ

What is the safest starting preset?

For interactive trials, the documented default workspace-write + ask is the reasonable baseline, combined with a disposable workspace and separate network controls. A stricter unattended composition should explicitly define its sandbox and approval policy and prove every denied path; do not infer it from a UI label.

Can post-execute undo an unsafe tool action?

No. It can replace or block the result seen by the model, but ordinary side effects are not transactional. Authorization and confinement must happen before or during dispatch.

Does an approval make danger-full-access safe?

No. Approval answers whether one action may proceed; it does not shrink the action's filesystem, network, credential, or process reach.

Is rc.8 installable from npm?

At the August 20 check, GitHub published dsh-v0.1.0-rc.8 and the repository was at that tag, while npm latest/next remained rc.7. Pin the registry artifact you actually verify.

Sources