Review fix (ds-review-bot critical #2 on #623): the first-turn event stamp had a durability hole no turn anchoring can close — an idle SessionStart- style injection persists a complete one-shot turn before any prompt turn opens, so a crash in that window left a resumable-looking child with no inherited policy, falling back to a possibly wider deployment default. The captured overrides now ride the child's creation meta into its immutable SessionHeader (sandboxMode/approvalPolicy, neutral strings at the session boundary — the delegationDepth precedent), durable from the moment the session exists: no listener ordering can starve the baseline and no crash window can lose it. overrideOf(session) on both policy services resolves fold(events past header.seedLength) ?? header baseline, validating against the closed vocabulary on read; stampOverride and the prompt-submit listener machinery are deleted. The header field rides both persistence backends (JSONL header line; SQLite sessions columns, SCHEMA_VERSION 11 — pre-release, no migration). pty-local reads through overrideOf so PTY spawns see the baseline too. Red-first: header-durability-before-any-turn test (the injection crash window shape), baseline/seed-boundary/closed-vocabulary contract tests in both service suites; the real-wall suite (race, veto, fork stale-seed, grandchild) re-anchored on header assertions and green. The Agent Note's Alternatives now records the superseded event-stamping iteration with the review evidence; bilingual docs updated.
4.7 KiB
dsh-sandbox-policy — the sandbox policy home (ctx.sandboxPolicy)
English | 中文
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 defaultSandboxMode(read-only/workspace-write/danger-full-access), validated at load. Defaultread-only(fail-safe).workspaceRoot— the fallback directoryworkspace-writemay write under for agentless calls or sessions without a cwd. Defaultprocess.cwd(), resolved to its absolute filesystem identity either way. A normal agent call uses its session header's immutablecwdinstead.
Surface
ctx.sandboxPolicy.resolve({ session?, mode? })— resolves one complete per-call policy. An explicit approved mode outranks the session's lastsandbox/modeevent, which outranksdefaultMode; the session's immutablecwdis canonicalized with filesystem semantics before becomingworkspaceRoot, otherwise the configured fallback applies. Canonicalization precedes lexical normalization sosymlink/..agrees with process working-directory resolution.ctx.sandboxPolicy.defaultMode/ctx.sandboxPolicy.workspaceRoot— the deployment default and fallback root used byresolve().effectiveSandboxMode(events)— the pure fold of a session'ssandbox/modeevents (the last switch wins, orundefined), used insideresolve().setSandboxMode(session, mode)— THE write path for a per-session override: appends exactly onesandbox/modeevent. The switch IS its event; nothing mutates the mode out of band.ctx.sandboxPolicy.overrideOf(session)— the session's override chain, never the deployment default: the fold of the session's OWN switches (events pastSessionHeader.seedLength), else the header's inheritedsandboxModedelegation baseline, validated against the closed vocabulary on read (throws on foreign values — a durable boundary). The in-process subagent driver captures this at delegation and writes it into each child's creation-time header, so a delegating parent's tightened mode binds its children with no first-turn timing window (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 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 ofSandboxExecutionPolicy. - File-effect modes only —
SandboxModegoverns file effects; network and process policy are outside its vocabulary, so no knob here restricts them.