DSHarness Systems Dissection Pinned baseline 47f943859b · 36 verified / 0 in progress / 36 chapters
中文
Reliability and Product Surfaces·Chapter 31

Security Model and Trust Boundaries

How a local Agent product constrains broad execution with explicit policy

VerifiedUpstream 47f943859bScope: Build a threat model spanning configuration trust, model input, tool JSON, approval, sandboxes, paths, credentials, remotes, and self-modification.

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 inputDefault postureControls that applyWhat is still trusted
Model text and tool-call JSONUntrusted dataParsing, schema checks, visibility restrictions, policy hooks, guards, result normalizationThe selected tool body and every capability it can reach
Local operator, inherited environment, profile and patch filesAdministrative authorityLayer precedence, bootstrap-variable rejection in discovered .env files, load-time validation, transactional startupEverything intentionally inserted into the in-process plugin tree
Workspace files, skills, MCP metadata and provider responsesContent with provenance-dependent trustFormat validation, invocation flags, JSON projection, timeoutsInstruction meaning, external server behavior, and any explicitly granted credential
Browser, Remote, SDK and ACP clientsCarrier-specific controllerHost/Origin fences or private stdio, typed methods, ownership checksReachability or process ownership; no common user-authentication layer
OS sandbox runner, filesystem and E2B serviceEnforcement substrateKernel/ACL profiles, canonical paths, atomic operations, remote lifecycleThe OS, configured runner, remote service, and deployment composition
Security property

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

Control

Load failures are loud, profile reconciliation follows installed state, and failed package-manager commands do not activate a new bundle.

Assumption

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

Control

Approval questions fail closed and are auditable, while guards cannot force-allow a call denied elsewhere.

Boundary

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

Known gap

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

Control

Canonicalization, separator-aware containment, atomic publication, version checks and observation policy materially reduce accidental path escape and lost updates.

Gap

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

Control

Endpoint and key cannot accidentally come from different settings generations, and credentials are not copied wholesale into ordinary child environments.

Known gap

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

Known gap

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

Control

Generated codecs, strict descriptors, explicit mounts and a closed event list reduce accidental API exposure and malformed payloads.

Assumption

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

Boundary

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

Assumption

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 sourceControlResidual assumption or gap
Profile plugin / bundlePackage-manager success, Loader validation, transactional activationExecutes in process; package and patch author are administrators
SkillDiscovery parsing and invocation policyBody is trusted model instruction, not sanitized meaning
MCP serverNamespace, discovery/result-shape checks, timeout, cancellationExternal executable or endpoint owns the real side effect
Model providerBounded request/model settings, per-request key resolution, transport errorsConfigured endpoint string receives prompts, identifiers and bearer credential
Dynamic PluginOpt-in toolset, ownership IDs, façades, lifecycle disposalHost VM is escapable; Host-only code needs no approval
Rule of thumb

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

AreaExisting controlDeployment assumptionKnown gap / priority
Remote accessHost, Fetch Metadata, Origin and loopback pinningAccepted authorities are trusted controllersAdd real authentication and per-capability authorization before untrusted-network exposure
ExecutionTool schemas, ordered policy, monotonic guards, one-shot escalationRegistered tool bodies are trustedRequire an explicit validation wrapper for raw definitions; make high-impact dynamic Host execution separately consentable
SandboxFail-closed file-effect runners and filesystem containmentOS or custom runner and same-world composition are correctNetwork, credential, process and device policy are absent; Windows is partial
SecretsReference-based configuration, owner-only POSIX store, value-free descriptions, child scrubHost process and configured endpoint are trustedPrefer explicit environment allowlists or secret handles over name heuristics; add a Windows ACL assurance story
Supply chainLoud load failures, explicit mounts and package-manager policyLocal packages, skills and server definitions are administrator-authoredAdd provenance/pinning policy if unreviewed sources enter profiles
Paths and concurrencyCanonical identities, containment, atomic publication, version guardsNo hostile same-user topology manipulatorDescriptor-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.