Files
deepseek-harness/packages/sandbox/sandbox-policy
kingwl 669771097d subagent: inherit parent sandbox/approval overrides in in-process children
Per-session policy overrides (sandbox/mode, approval/policy) never crossed
the delegation boundary: a spawn child of a read-only-switched parent ran
under the wider deployment default, and a fork child missed any switch made
after its seed boundary — delegation was a bypass channel for a user's
tightening.

The in-process driver now snapshots the delegating parent's override chain
and stamps it onto the child through the canonical write paths
(SandboxPolicyService.inheritOverride / ApprovalService.inheritOverride),
anchored inside the child's first turn via a one-shot agent/prompt-submit
listener: turn-enclosed (durable), ahead of the first request (an inherited
'never' reaches the child's first system prompt), and positioned after any
stale fork-seed switch so the ordinary last-event-wins fold resolves it.
Only overrides are copied — an unswitched parent stamps nothing and the
child follows the live deployment default; both services are consumed
opportunistically, so compositions without them delegate unchanged. Nesting
composes by construction (each stamp folds the already-stamped parent log).

Evidence: inheritance.spec.ts drives scripted-model children into the real
dsh-fs-sandbox fence through the real write tool (disk-state + denial-marker
assertions; spawn, stale-seed fork, grandchild, escalation fail-closed, and
no-stamp guards), inheritOverride contract tests in both service suites, and
the recorded subagent-sandbox-inheritance ACP snapshot (read-only preset →
delegate → child denied, replayed keylessly).

See .agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md.
2026-07-25 04:06:19 +08:00
..

dsh-sandbox-policy — the sandbox policy home (ctx.sandboxPolicy)

The single owner of sandbox-policy resolution: the deployment's default SandboxMode and fallback root, plus each session's durable mode override and immutable workspace root. Every enforcing capability family receives one resolved mode-and-root policy per call.

Why a shared home

Two families enforce the same mode vocabulary: the sandboxed bash executor (@deepseek-ai/dsh-bash-sandbox) and the sandboxed filesystem provider (@deepseek-ai/dsh-fs-sandbox). If each resolved its own mode + workspaceRoot, the two could drift into a split world — bash confined to one root while fs fences another, exactly what the sandbox RFC warns against. Both tool layers resolve policy through ctx.sandboxPolicy, and both enforcing backends consume that complete per-call result. The cross-family fs sandbox RFC records the shared-policy decision.

Config

  • mode — the deployment default SandboxMode (read-only / workspace-write / danger-full-access), validated at load. Default read-only (fail-safe).
  • workspaceRoot — the fallback directory workspace-write may write under for agentless calls or sessions without a cwd. Default process.cwd(), resolved to its absolute filesystem identity either way. A normal agent call uses its session header's immutable cwd instead.

Surface

  • ctx.sandboxPolicy.resolve({ session?, mode? }) — resolves one complete per-call policy. An explicit approved mode outranks the session's last sandbox/mode event, which outranks defaultMode; the session's immutable cwd is canonicalized with filesystem semantics before becoming workspaceRoot, otherwise the configured fallback applies. Canonicalization precedes lexical normalization so symlink/.. agrees with process working-directory resolution.
  • ctx.sandboxPolicy.defaultMode / ctx.sandboxPolicy.workspaceRoot — the deployment default and fallback root used by resolve().
  • effectiveSandboxMode(events) — the pure fold of a session's sandbox/mode events (the last switch wins, or undefined), used inside resolve().
  • setSandboxMode(session, mode) — THE write path for a per-session override: appends exactly one sandbox/mode event. The switch IS its event; nothing mutates the mode out of band.
  • ctx.sandboxPolicy.inheritOverride(parent, child) — the delegation-inheritance step: stamps the parent session's effective override (never the deployment default) onto a child session through setSandboxMode, skipping a child that already folds to it. The in-process subagent driver calls it inside the child's first turn so a delegating parent's tightened mode binds its children (rationale).
  • SANDBOX_MODES — every mode, for option advertisement and runtime validation.

The optional ./invariant companion rejects a forged durable sandbox/mode event whose value falls outside that closed vocabulary; Session and its companion own the surrounding storage and turn-enclosure rules.

The per-session store

A runtime switch (an ACP session/set_config_option, a test scenario) is one log-only sandbox/mode event on the session it applies to. effective = explicit grant ?? fold(events) ?? deployment default, so an override survives restart by replay and two sessions never see each other's state. Workspace identity does not need another event: the immutable SessionHeader.cwd recorded at creation is the root for every call in that session. The event is log-only (the approval/* precedent): the model learns the mode from the enforcing tools' denial markers, never from the event.

Model Experience

Indirectly, through dsh-tool-bash and dsh-tool-fs, which render the effective mode this service holds in their [sandbox: …] denial markers and escalation prompts; the sandbox/mode event itself never reaches the model.

KV Cache effect

No direct invalidation; the named consumers own any request-prefix changes, and the mode is deliberately absent from the prompt.

Known Limitations and Deferred Work

  • One primary workspace root per session — policy resolves SessionHeader.cwd; extra writable roots are not part of SandboxExecutionPolicy.
  • File-effect modes only — SandboxMode governs file effects; network and process policy are outside its vocabulary, so no knob here restricts them.