Security Model and Trust Boundaries
How a local Agent product constrains broad execution with explicit policy
Conclusion: capability policy, not a hostile-tenant sandbox
DeepSeek Harness treats ordinary model output as untrusted data, then turns selected requests into real authority through a capability graph chosen by the local operator. Its strongest controls are explicit composition, runtime schema checks, per-session permission facts, one-shot escalation, file-effect confinement, path containment, and carrier-specific request fences. It is not designed to isolate the Host from a malicious local plugin, a hostile same-user process, a trusted remote client, or model-written code after the operator has deliberately exposed bash-equivalent execution.
The decisive security question is therefore not “is the Agent sandboxed?” but “which principal selected this capability, which boundary validates its input, which mechanism actually enforces it, and what trust assumption remains after that enforcement?” The implementation answers those questions unevenly: several boundaries fail closed, while others intentionally rely on local ownership, process isolation, third-party infrastructure, or a package supply chain.
1. Threat model: five authorities and three different meanings of “trusted”
| Principal or input | Default posture | Controls that apply | What is still trusted |
|---|---|---|---|
| Model text and tool-call JSON | Untrusted data | Parsing, schema checks, visibility restrictions, policy hooks, guards, result normalization | The selected tool body and every capability it can reach |
| Local operator, inherited environment, profile and patch files | Administrative authority | Layer precedence, bootstrap-variable rejection in discovered .env files, load-time validation, transactional startup | Everything intentionally inserted into the in-process plugin tree |
| Workspace files, skills, MCP metadata and provider responses | Content with provenance-dependent trust | Format validation, invocation flags, JSON projection, timeouts | Instruction meaning, external server behavior, and any explicitly granted credential |
| Browser, Remote, SDK and ACP clients | Carrier-specific controller | Host/Origin fences or private stdio, typed methods, ownership checks | Reachability or process ownership; no common user-authentication layer |
| OS sandbox runner, filesystem and E2B service | Enforcement substrate | Kernel/ACL profiles, canonical paths, atomic operations, remote lifecycle | The OS, configured runner, remote service, and deployment composition |
The intended property is capability confinement: an untrusted model call should not exceed the capabilities and policies selected by the operator. It is not a multi-tenant noninterference claim against principals that already control configuration, plugins, the process environment, or a trusted transport endpoint.
2. Boot configuration is an administrative boundary, not passive data
The CLI snapshots three environment layers in fixed order: inherited process environment, invoking-directory .env, then Harness-home .env. Inherited values win. Both discovered files are parsed before either is materialized, and a denylist blocks variables that can redirect process startup, module loading, VCS behavior, proxies, certificates, provider endpoints, or any DSH_/XDG_/DYLD_ prefix. Accepted values are then copied into process.env without replacing inherited values.SEC-ENV-LAYERS
User patch and overlay files are more powerful than .env: they may replace configuration, insert Loader rows, and contain !!js expressions. A present but unreadable, unparsable, or wrong-shaped layer fails boot instead of being skipped.SEC-PATCH-LAYERS The root include resolves relative plugins beside the configuration and bare plugins through the configured module base, then asks the Loader to create the resulting tree.SEC-LOADER-IMPORT
Startup is transactional in the lifecycle sense: prepare, mount, settle, and activation audit run in order; any failure disposes the partial root and preserves the deepest error stack.SEC-BOOT-TRANSACTION That protects availability and cleanup. It does not make a configured plugin safe: a successfully imported plugin executes inside the Host process with whatever Services its composition exposes.
3. Profiles and plugin installation make the local package chain executable policy
The effective profile is composed as bundle patches, profile patches, home patches, command-line overlays, then launcher-owned overlays. Later administrative layers can therefore alter or insert the rows that define the runtime capability graph.SEC-PROFILE-ORDER
dsh plugin is intentionally a thin pnpm forwarder. It runs the requested package-manager command in the profile directory and, after success, automatically adds every installed dependency declaring a bundle patch to the profile’s active layer list. Relative package specs are anchored to the directory from which the operator invoked the command; git-package build policy is delegated to pnpm.SEC-PLUGIN-PNPM
Load failures are loud, profile reconciliation follows installed state, and failed package-manager commands do not activate a new bundle.
Package provenance, install scripts, transitive code, and an authored bundle patch are trusted as local administrator code. There is no additional product sandbox between successful installation and in-process activation.
4. Model tool JSON crosses several validators, but validation ownership is not uniform
The Agent loop converts each model tool block into an execution request. Valid JSON is parsed, an empty argument string becomes {}, and invalid JSON is deliberately preserved as a raw string so the eventual tool can return a useful validation error rather than losing the authored input.SEC-MODEL-PARSE
First-party tools built with defineTool() compile their parameter and output schemas. Immediately before the user body, the wrapper validates arguments and raises ToolArgsError; replay presenters validate softly, because historical arguments may follow an older schema.SEC-TOOL-DEFINE The lower-level registry contract is different: register() validates the output contract, timeout and reserved name, but it does not inject generic input validation around an arbitrary ToolDefinition.execute.SEC-RAW-TOOL-REGISTER
Before policy runs, the registry snapshots arguments as lossless JSON and deep-freezes the detached value. Invalid or getter-hostile values materialize as an error result; a caller cannot mutate the object after policy inspected it.SEC-TOOL-ARG-SNAPSHOT
5. Authorization is an ordered pipeline, not a universal confirmation dialog
For a ready call, ToolRuntime runs the scoped tools/pre-execute waterfall with a default allow. Only an ask decision invokes the approval service. A resulting allow is then subjected to monotonic guards; any denial materializes before dispatch, and cancellation is checked again before the body starts.SEC-TOOL-GATE
The approval service requires an open turn, appends an approval/asked event, resolves policy and answerers, then appends approval/decided. never rejects before answerer dispatch; missing, throwing, or vocabulary-violating answerers become unavailable; cancellation discards a late answer. allowed-once is the only grant.SEC-APPROVAL-CLOSED
Approval questions fail closed and are auditable, while guards cannot force-allow a call denied elsewhere.
Approval is consumer-triggered policy, not a mandatory gate around every tool. An ordinary action already allowed by the standing sandbox and tool policy executes without prompting. Adding an answerer does not convert the runtime into default-deny authorization.
6. Permission presets pin standing authority; escalation is a separate one-call grant
The preset service requires a shell that advertises the sandboxMode policy capability and an approval service. Its built-in table maps workspace-write to ask and danger-full-access to never; it validates the configured default before publishing settings.SEC-PRESETS-VALIDATE New sessions receive durable preset, sandbox-mode and approval-policy facts, while seeded or partially initialized sessions preserve existing facts and gain only missing ones.SEC-PRESETS-PIN
The shipped base composition adds a third read-only + ask choice and starts fresh sessions at workspace-write + ask unless the inherited DSH_PERMISSION_MODE selects another mode. danger-full-access is paired with never because it is already a standing file-access grant, not because never approves anything.SEC-SHIPPED-PERMISSION
Shell and filesystem escalation share one stricter choreography: the requested mode must be wider than the call’s current mode, an approval service and Agent must exist, the reason and exact call identity are audited, and only allowed-once stamps the wider mode onto that call. Every other outcome runs nothing.SEC-ESCALATION
7. “Sandbox” means process file-effect policy, and nothing broader
The sandbox seam defines only three file-effect modes. A provider must return a wrapped argv plus an enforcement level and diagnostic dialect, or fail closed when no backend is usable. The contract explicitly leaves network access and process visibility outside its vocabulary.SEC-SANDBOX-SEAM
The official limits are broader still: there is no syscall, device, or credential restriction; confinement is same-world; denial classification relies on stderr; and runner diagnostics are in-band, so a child can imitate a failure signature and confuse diagnosis without escaping confinement.SEC-SANDBOX-SCOPE
The local provider chooses Linux bubblewrap then Landlock, macOS Seatbelt, or the Windows ACL runner. Bubblewrap and Seatbelt advertise full file-effect enforcement; a probed Landlock launcher reports full or partial according to ABI support, while Windows advertises partial because ACL and hard-link semantics cannot satisfy the absolute promise.SEC-LOCAL-RUNNERSSEC-LANDLOCK-PROBE A configured custom runner bypasses platform selection and is treated as the operator’s assertion of full enforcement after only shape validation of its command and failure signatures.SEC-CUSTOM-RUNNER
A command confined to workspace writes can still read host files, use the network, enumerate visible processes, access surviving ambient variables, and invoke reachable services. “Workspace-write” must never be described as general-purpose isolation.
8. Filesystem policy contains mutations, while reads and several races remain outside it
The sandboxed filesystem inherits local reads unchanged and fences only writeText and editText. read-only denies mutation; workspace-write re-resolves the target immediately before the operation and checks the fresh canonical path against workspace and temporary roots; danger-full-access delegates unfenced. The source explicitly accepts the residual ancestor-symlink race between the final check and syscall.SEC-FS-FENCE
The local filesystem’s cwd is merely a resolution default: absolute paths and .. escape it. Mutation locks are process-local, guarded replacement can race another process, version tokens depend on filesystem metadata, and failed post-commit cleanup may leave private staging residue.SEC-FS-LOCAL-LIMITS
The observation policy adds a valuable stale/unseen-write gate, but it is freshness rather than authorization. Its state does not survive resume, direct ctx.fs reads are not observations, and any windowed read authorizes a full overwrite while the version remains unchanged.SEC-OBSERVATION-LIMITS
Canonicalization, separator-aware containment, atomic publication, version checks and observation policy materially reduce accidental path escape and lost updates.
They do not defend file confidentiality, a hostile same-user process changing path topology, or every cross-process replacement race. Stronger adversarial containment would require descriptor-based traversal or an OS boundary, not another lexical check.
9. Credentials are references at configuration surfaces, but secrets remain process authority
The credential seam makes configuration carry environment-variable-shaped references rather than values. Consumers resolve on each operation; describe exposes only configured/source/writable facts, while set and unset own durable changes.SEC-CREDENTIAL-SEAM
The local provider defines explicit precedence: inherited environment, managed $DSH_HOME/.credentials.yaml, project .env, then home .env. The managed document is not materialized into process.env; nevertheless, the project is intentionally trusted to supply a fallback credential.SEC-CREDENTIAL-LAYERS On POSIX, an existing credentials file with any group/other permission bit is rejected before reading, and parse errors avoid quoting the secret-bearing source line. Windows skips the POSIX-mode check because ACLs are not represented here.SEC-CREDENTIAL-FILE
Resolution makes inherited environment read-only and dominant, then consults the managed snapshot and dotenv fallbacks. The UI cannot appear to overwrite an inherited secret: describe reports it unwritable and set/unset reject a shadowed write.SEC-CREDENTIAL-RESOLVE
10. Child environments reduce ambient leakage; provider egress is still an explicit trust decision
The shared subprocess baseline removes every environment name matching KEY|PASSWORD|SECRET|TOKEN and every DSH_* name, case-insensitively. It retains values such as PATH, HOME, locale and proxy settings, and a later explicit environment overlay may restore any removed value.SEC-CHILD-ENV
The DeepSeek adapter validates bounded request and model settings, snapshots the configured endpoint, pairs that endpoint generation with a credential resolved for the same request, and retains the last good live configuration after an invalid settings update. The endpoint field itself is a configured string, not an HTTPS, host-allowlist, or SSRF policy.SEC-PROVIDER-CONFIG It then serializes the model request and sends it with the bearer key, attribution, anonymous user identity, optional Session identity and compaction marker to the configured /chat/completions endpoint.SEC-PROVIDER-TRANSPORT
Endpoint and key cannot accidentally come from different settings generations, and credentials are not copied wholesale into ordinary child environments.
Name-based scrubbing is a heuristic, not data-flow tracking: secrets named COOKIE, AUTH, or another unmatched label survive, while explicit overlays deliberately bypass the scrub. A configured model endpoint is trusted with the credential and all serialized prompt content.
11. MCP is an external capability provider, not an extension of the local shell sandbox
An MCP stdio transport spawns the configured command through the upstream SDK with the scrubbed parent environment plus explicit overrides. The HTTP transport connects to the configured URL and sends configured headers. Neither path goes through the model-facing shell executor or its sandbox escalation flow.SEC-MCP-TRANSPORT
Discovery drains the external server’s tool list and builds the complete server-qualified candidate map before touching the registry. It then disposes the previous generation and registers the next one sequentially: duplicate tool names in that server list fail before the swap, while a registry conflict disposes the partial new generation and leaves zero tools from that server rather than restoring the previous set.SEC-MCP-SYNC Execution does not locally validate arguments against the advertised input schema: null and primitive values become {}, while arrays and objects pass to the server. The executor checks or normalizes the top-level result shape; Native rendering defensively extracts text and non-text summaries, while canonical JSON content remains intact for Code Mode. An MCP isError becomes a tool failure.SEC-MCP-EXEC
12. Browser reachability is fenced, but reachability is not user authentication
Every Browser API request must carry a loopback or configured Host authority. Explicit cross-site Fetch Metadata is rejected, and an attached Origin must match the Host authority. The code states directly that binding and network reachability are separate and that this fence is not an authentication layer.SEC-BROWSER-FENCE
Configuration, credentials, native Host actions, preset document/copy/remove actions and model discovery are pinned to loopback even when trustedHosts admits a LAN authority. The same source also explains why ordinary Agent methods are not treated as a lesser privilege: a client able to create a Session can already select a preset and, under the shipped capability graph, run commands as the Host process. The fence therefore prevents DNS rebinding and cross-site browser abuse but does not identify a human user.SEC-BROWSER-PRIVILEGED
A declared non-loopback authority is an administrative reachability grant to the non-pinned API surface. It should not be exposed to an untrusted network until a real authentication and authorization layer exists.
13. Generated Remote narrows capabilities and shapes, then inherits the Browser carrier’s trust
The Client assembly mounts a fixed list of generated Remote contributions and unwinds them in reverse order if activation fails or the assembly disposes. Capability availability is therefore explicit at build/composition time rather than discovered from every live Host Service.SEC-REMOTE-MOUNT The Client gateway requires a strict codec for every parameter, result and contextual identity; its boundary parser rejects either a missing strict codec or a schema parse failure.SEC-REMOTE-CODECS Invocation obtains the active Connection and sends the generated endpoint through its /api RPC path, so Remote adds no separate carrier identity.SEC-REMOTE-CARRIER
Forwarded Host events are controlled by one explicit allowlist. The original event name and JSON argument list cross verbatim, with no projection, redaction or renaming; adding one list entry expands the observable surface.SEC-REMOTE-EVENTS
Generated codecs, strict descriptors, explicit mounts and a closed event list reduce accidental API exposure and malformed payloads.
Remote supplies no independent identity boundary. Calls and events ride the same Connection authority as the Browser API, so type safety proves payload shape—not who is entitled to invoke the capability or see an allowlisted verbatim event.
14. SDK trust rests on ownership of a local runtime process and its complete event stream
The TypeScript SDK client directly spawns the configured runtime outside any Harness Context. Unless an explicit complete environment is supplied, the child inherits process.env; requests and notifications then cross private JSONL stdio. A request timeout abandons only the client-side pending entry: it does not cancel server work, which may continue or complete while a late response is ignored; closing the owned runtime is the controller's forcible termination path.SEC-SDK-CLIENT
The SDK server subscribes to every Session event and Agent status in its Context, reports local child lifecycle, and lets the caller choose cwd, provider and model at initialization.SEC-SDK-SERVER Filtering a subscription to one Session tree is a client-side convenience, not server-side authorization.SEC-SDK-FILTER The Python client likewise copies the entire parent environment, merges optional overrides, and spawns the selected runtime over stdio.SEC-SDK-PYTHON
15. ACP is a trusted automation bridge with narrow one-shot approval semantics
The ACP module describes itself as an automation-only JSON-RPC stdio server for trusted programmatic clients. It creates and owns fresh Agents and Sessions, carrying prompt text, committed assistant text, cancellation and permission decisions while leaving presentation features outside the bridge.SEC-ACP-TRUST
For approval, the bridge answers only requests belonging to an Agent it owns and carrying a call ID. It offers only allow-once and reject-once, maps an unknown non-allow response to rejection, advertises no authentication methods, implements authenticate as a no-op, and accepts the client-supplied absolute cwd when creating a Session.SEC-ACP-BRIDGESEC-ACP-CWD
Exact ownership and one-shot choices prevent a stale or foreign ACP answer from becoming a durable grant. They do not authenticate the stdio peer; security rests on which process launched or connected the bridge and controls its pipes.
16. Remote execution changes the execution world; it does not remove composition trust
The E2B owner creates one shared sandbox handle per Context, validates its API key, absolute Linux cwd and lifetime, asks the service for a secure sandbox, prepares an owner-only runtime directory, and randomizes the SDK login-shell HOME. It attempts a rollback kill after setup failure and also asks the service to kill the sandbox on timeout or disposal; those best-effort cleanup calls do not prove that the remote resource was destroyed. Its API key is explicitly not forwarded into the remote sandbox.SEC-E2B-OWNER
The remote filesystem has no automatic Host synchronization. Mutation coordination remains local to the Host process, other connections or remote commands can race replacements, and opening a canonical path still has a remote reopen race.SEC-E2B-LIMITS
Filesystem, subprocess, shell, terminal and search capabilities must all refer to the same remote world and root. Sessions sharing that Context also share the E2B owner; isolation between those Sessions is not established by creating one remote sandbox for the Context.
17. Code Mode and dynamic plugins are containment for cooperative code, not malicious-code isolation
The worker-thread CodeRuntime says this explicitly: a fresh worker, empty environment, heap limit, busy/wall timers and forced termination are containment, while model code retains bash-equivalent trust.SEC-WORKER-TRUST
Dynamic Host code runs in a fresh node:vm realm with traps steering common operations to Services, but Host-realm helpers remain an escape route; the module states that the VM is not containment for malicious code.SEC-DYNAMIC-VM More importantly, a Host-only dynamic Package activates immediately. Only a Package with Client code enters the approval request state, and a plugin-wide grant may cover future Client versions.SEC-DYNAMIC-RUN
The Host façade is still useful defense in depth: property-style Service access requires a declared injection and therefore participates in Cordis lifecycle parking, but optional ctx.get(name) is not declaration-gated and can retrieve any live Service. Retrieved Services are wrapped to reject a direct Context return—not to deep-sanitize arbitrary object graphs—while the special tools façade exposes metadata and guarded registration without a live execute function.SEC-DYNAMIC-GUARD The browser half is evaluated with new Function in the page realm. Selected bare names are shadowed and process/Buffer are passed as undefined, but ambient window, document, globalThis and other page capabilities remain reachable; injected React, console, styles and Host calls are also real capabilities.SEC-DYNAMIC-CLIENT
The self-modification toolchain is not in the base capability set: the advanced headless example explicitly inserts CodeRuntime, the Host runner and tool-cordis.SEC-SELF-MOD-OPTIN
18. Supply-chain closure: plugins, skills, MCP servers and endpoints all become authority
Skills illustrate the difference between syntax safety and semantic trust. The model tool checks model-invocable after both lookup and load, explicit user invocation injects only user-invocable skills, and the model catalog retains only model-invocable entries.SEC-SKILL-INVOCATION-GATES The wrapper's name attribute is escaped, but the instruction body is embedded verbatim because the implementation classifies skills as trusted local content.SEC-SKILL-TRUST
| Extension source | Control | Residual assumption or gap |
|---|---|---|
| Profile plugin / bundle | Package-manager success, Loader validation, transactional activation | Executes in process; package and patch author are administrators |
| Skill | Discovery parsing and invocation policy | Body is trusted model instruction, not sanitized meaning |
| MCP server | Namespace, discovery/result-shape checks, timeout, cancellation | External executable or endpoint owns the real side effect |
| Model provider | Bounded request/model settings, per-request key resolution, transport errors | Configured endpoint string receives prompts, identifiers and bearer credential |
| Dynamic Plugin | Opt-in toolset, ownership IDs, façades, lifecycle disposal | Host VM is escapable; Host-only code needs no approval |
Anything that can add a plugin, skill, MCP endpoint, model endpoint, approval answerer, sandbox runner or dynamic Host Package belongs in the administrative trust domain, even if its configuration is expressed as YAML or JSON.
19. Control / assumption / gap ledger and hardening priorities
| Area | Existing control | Deployment assumption | Known gap / priority |
|---|---|---|---|
| Remote access | Host, Fetch Metadata, Origin and loopback pinning | Accepted authorities are trusted controllers | Add real authentication and per-capability authorization before untrusted-network exposure |
| Execution | Tool schemas, ordered policy, monotonic guards, one-shot escalation | Registered tool bodies are trusted | Require an explicit validation wrapper for raw definitions; make high-impact dynamic Host execution separately consentable |
| Sandbox | Fail-closed file-effect runners and filesystem containment | OS or custom runner and same-world composition are correct | Network, credential, process and device policy are absent; Windows is partial |
| Secrets | Reference-based configuration, owner-only POSIX store, value-free descriptions, child scrub | Host process and configured endpoint are trusted | Prefer explicit environment allowlists or secret handles over name heuristics; add a Windows ACL assurance story |
| Supply chain | Loud load failures, explicit mounts and package-manager policy | Local packages, skills and server definitions are administrator-authored | Add provenance/pinning policy if unreviewed sources enter profiles |
| Paths and concurrency | Canonical identities, containment, atomic publication, version guards | No hostile same-user topology manipulator | Descriptor-relative operations and stronger cross-process coordination remain future work |
This chapter is a static control-flow audit of commit 47f943859bef60e4160492346772ded9b24f765a. It distinguishes implementation controls from deployment assumptions and source-declared limitations; it does not claim exploit testing, kernel conformance testing, package provenance verification, or a formal noninterference proof. The safest operational reading is narrow: keep the Host local unless authenticated, review every administrative extension as code, expose only the capabilities required for the task, and treat each configured external endpoint as a data-and-authority recipient.
My Learning Notes
Autosaved only in this browser. Nothing is uploaded or committed. Export Markdown whenever you want to keep a copy.