DeepSeek Harness ACP sessions bypass the sandbox policy
If in DeepSeek Harness (DSH) you configure the sandbox and permissions on an agent preset and verify everything with a web or headless session — writes outside the workspace are clearly denied — there may well be a gap you did not notice: switch to an ACP session (dsh --profile acp) and the same write succeeds straight into the location outside the workspace. According to the reporter's tests and the community's source review, the cause is not a misconfiguration but that an ACP session joins no preset composition (the bundle leaves the model-facing lines on the host plane), so the sandbox / permission lines placed in the preset in a deployment do not apply to it at all; and the path that looks most like the fix — "add field forwarding to agentOptions()" — is not viable even at the type level, because the underlying AgentOptions has no sandbox or permission field at all (#9693). This article gives a one-shot reproduction test, why it cannot be fixed by changing agentOptions(), and how to backstop it with host-side policy before the core is fixed.
DeepSeek Harness symptom: same machine, same profile, a different entry stops the sandbox
In one line: whether the sandbox boundary takes effect depends on whether the session took part in the profile's composition; an ACP session does not, so it never gets the sandbox line from the preset. The test is very clean — the same patch, the same machine, the same target path, changing only the entry:
| Session entry | Writing outside the workspace | Note |
|---|---|---|
web (dsh web) | Denied | The session took part in the preset composition, the sandbox line is in effect |
| headless | Denied | Same as above |
ACP (dsh --profile acp) | Write succeeds | Does not join a preset, the sandbox line is not in effect |
According to the reporter's tests, the differences among these three are stably reproducible, and independent of the workspace path and the content written (#9693).
DSH plugin: mechanism, two things stacked and both required
The root cause is a "missing composition", not "fields not forwarded" — the latter is only the surface. Unpacked:
- Surface:
agentOptions()passes only the two fields{ provider, model }, which looks like "a few fields are missing"; - Real cause: the
AgentOptionstype has no sandbox / permission field at all, so even if you wanted to forward them there is nowhere to get them from; - Composition layer: the ACP bundle leaves the model-facing lines on the host plane, an ACP session joins no preset composition, while the deployment writes the sandbox and permissions on the preset — the two do not line up (#9693).
This conclusion was confirmed item by item during the community's source review, including the ACP package's own comment text and the AgentOptions type definition (#9693).
DeepSeek Harness: three-step reproduction to check whether your deployment hits it
- Prepare a target path outside the workspace. For example create an empty directory
D:\dsh-outsideoutside the workspace and confirm it is not inside the workspace (the workspace path is shown under Settings → Workspace). - Do a control with a web session. In a
workspace-writeweb session have the model perform a write:
Set-Content -Path "D:\dsh-outside\probe.txt" -Value "probe"
# expected: denied by the sandbox, the result carries a marker such as file access denied, the file is not created
- Do the same with an ACP session. Start a session with
dsh --profile acpand run the identical command; expected: the file is created, with no sandbox marker in the result. Inconsistent results between the two groups means it reproduces (#9693).
Remember to delete the target file after reproducing; the purpose of such a test is only to judge "whether the boundary is there", so do not leave it in your scripts as a routine verification.
DSH plugin: why "add a field to agentOptions" is not the fix
In one line: that path is not viable even at the type level — AgentOptions has no such two fields, so you must first make an ACP session take part in the profile's composition before the config has anywhere to take effect. This one is worth telling separately, because it is the direction most easily gone astray in this class of problem (#9693).
The correct fix directions are three, ordered from hardest to easiest to land:
- Make the ACP session take part in the composition (core side): bring the sandbox and permission lines into the ACP session's composition path so it goes through the same profile composition logic as web/headless. This is the root fix, but it is a core-side change.
- Push the policy down to the host side: on the host side use a plugin to do tool pre-checks, so an "out-of-bounds write" is denied at the dispatch stage with a readable hint. This is fastest to take effect, but covers only the tools and path shapes the plugin can see.
- Temporarily reduce the dependency: in ACP scenarios avoid treating the "sandbox boundary" as a security precondition — that is, do not expose the ACP entry to untrusted input until the core is fixed.
The reporter in the thread adopted exactly option 2, and clearly marked its limitation: it is only a policy boundary, not a capability boundary — tools/pre-execute stops the path declared in a tool call, but does not cover paths assembled on a shell command line, nor the MCP channel; the permissions the process actually holds are unchanged (#9693).
DeepSeek Harness host-side backstop: how to write the pre-execute path policy
The idea is to block, before a tool is dispatched, paths that "read as inside the workspace but resolve outside it". Three steps:
- Create a minimal host-side plugin hooked on
tools/pre-executethat does a realpath resolution and workspace containment check on the path arguments declared by file-type tools; - Mount it in the profile's patch layer:
- insert:
- id: acp-path-policy-guard
name: './plugins/acp-path-policy-guard'
- Verify: in an ACP session rerun the out-of-bounds write from step 2; expect it to be denied by the plugin this time, with a result explaining "this is a policy denial, not a command error". While verifying, also assemble a path on the shell to go around it (for example build
D:\dsh-outsidefrom variables on the command line) and confirm you accept it going through — if you do not, it means you still need to add the core-side path (#9693).
Remember this is not a fix: it turns a "silent out-of-bounds" into an "explicit denial + hint", but the sandbox itself still does not apply to ACP sessions. Do not promise a security boundary by treating pre-execute as a substitute for the sandbox.
DSH plugin troubleshooting notes
The key to this problem is to first ask "which entry did this session go through", rather than suspecting the config. Six points:
- Same machine, same profile, different entry comparison is the highest-value step: web denied, ACP passing basically orients it (#9693).
- Which layer the sandbox config is written on decides who it applies to: config written on the preset layer is only received by sessions that take part in the composition; another case of "the policy looks right but does not take effect as expected" is a rejected permission escalation ("not strictly wider"), covered in Fix sandbox "not strictly wider" in DeepSeek Harness (#9693).
- Do not use
agentOptions()as the landing point: what is missing is not forwarding, but that this layer has no place to hold those two fields at all (#9693). - pre-execute is the policy layer, not the capability layer: you can use it as a backstop, but not to promise security (#9693).
- Do an out-of-bounds test before exposing the ACP entry externally: this kind of "config looks right, actually not in effect" gap can only be found by behavior comparison.
- State the fix status honestly: as of
0.2.0-rc.2, the behavior that ACP sessions do not take part in preset composition still exists; the reporter's host-side plugin is a user-side backstop, not an official fix, and it does not add the sandbox to the ACP channel either.
Gaps like "different entry, different policy" are often entangled with plugin mounts and config layers, making troubleshooting a long way around. If you want to clean up the plugin-side variables first, use a visual plugin marketplace such as DSH Plugin Hub instead of hand-checking commands everywhere — plugin install/uninstall, update confirmation, diagnostics and system logs are all in one panel, so you can first confirm "which plugins and config lines are actually mounted in the current environment".

Source: Discussion #9693.
FAQ
With the same DeepSeek Harness config, a web session being denied while an ACP session writes straight through happens because the sandbox and permission config live in an agent preset and an ACP session joins no preset composition — it leaves the model-facing lines on the host plane, so the sandbox and permission lines in the preset have no effect on an ACP session at all. To test it, on the same machine write to the same outside-workspace path once from a web session and once from an ACP session; the former is denied and the latter succeeds, which reproduces it.
No — adding sandbox and permission config to a DeepSeek Harness ACP session by forwarding fields through agentOptions() is the wrong place to put the fix. According to the community's source review, agentOptions() only passes provider and model, and the underlying AgentOptions type has no sandbox / permission field to forward at all, so 'adding fields to this method' is not viable even at the type level. The real fix is to make ACP sessions participate in the profile composition (composition), or to push the policy down to host-side tool pre-checks.
In DeepSeek Harness, a host-side plugin using tools/pre-execute for path policy can only be a policy boundary, not a capability boundary. pre-execute is a 'reject before dispatch' gate that stops the tools and path shapes the plugin can see, but it does not cover paths assembled on a shell command line or the MCP channel, and it does not change the permissions the process actually holds. It suits an 'extra, readable layer of denial and hint', not a replacement for the sandbox.
To tell whether a DeepSeek Harness profile's sandbox setting is in effect, do a behavior comparison: in the same session, try writing the same relative structure both inside and outside the workspace; if the outside write also succeeds and there is no sandbox marker, the current session never got the sandbox policy. The second step is to confirm which entry point this session went through (web / headless / ACP), because the ACP entry does not join a preset, so the preset-layer config not applying to it is by design, not a misconfiguration.
Related Terms
- ACP (Agent Client Protocol)
- ACP is a class of session entry point DeepSeek Harness uses to connect to external clients, started with dsh --profile acp, exposing session capabilities to an external host. Its profile composition differs from the web/headless entries, so the sandbox and permission lines placed in an agent preset in a deployment do not automatically apply to an ACP session.— https://github.com/deepseek-ai/deepseek-harness/discussions/9693
- agentOptions()
- agentOptions() is the method in DeepSeek Harness that builds a sub-session's start parameters, currently passing only the two fields provider and model. Because the underlying AgentOptions type itself has no sandbox and permission fields, 'adding forwarding here' is not viable even at the type level, and the sandbox config must be provided by profile composition.— https://github.com/deepseek-ai/deepseek-harness/discussions/9693
- agent preset
- An agent preset is a pre-composed set of agent lines in DeepSeek Harness (including model, tools, sandbox and permission config); when a session is created it composes these lines into the runtime per the preset. It takes effect at a profile's preset layer only if the session actually takes part in that composition — and an ACP session happens not to.— https://github.com/deepseek-ai/deepseek-harness/discussions/9693
- tools/pre-execute
- tools/pre-execute is the hook DeepSeek Harness fires before dispatching a tool; a plugin can inspect arguments, reject the call or rewrite the context at this step. It is often used as a policy-layer backstop (for example path-boundary checks), but it is a policy boundary rather than a capability boundary and cannot replace the sandbox's restriction of a process's permissions.— https://github.com/deepseek-ai/deepseek-harness/discussions/9693