Extend SandboxMode enforcement from bash to the filesystem tools, the sandbox RFC's deferred cross-family phase. - dsh-sandbox-policy (new, ctx.sandboxPolicy): the single home for the deployment default mode + workspaceRoot and the per-session override event, renamed bash/sandbox-mode -> sandbox/mode and moved here with its fold/setter. Decouples the bash seam from dsh-session. - dsh-fs-sandbox (new): SandboxedFileSystem extends LocalFileSystem and fences write/edit by the per-call mode (read-only denies, workspace-write contains to the workspace + temp roots via the shared writableRoots, danger passes through); reads pass through. Structured FS_SANDBOX_DENIED; in-lock parent re-canonicalization. A policy fence in trusted code, not a kernel boundary. - dsh-sandbox: the shared escalation kit (writableRoots, the strictly-wider ladder, denial/hint markers, approveEscalation) both tool families use; approveEscalation takes a structural approver so dsh-sandbox gains no approval/agent dependency, and both tools stay duplication-free. - tool-fs: write/edit advertise sandbox_permissions/justification under a confining ctx.fs, map FS_SANDBOX_DENIED to the shared [sandbox: ...] marker, and resolve the same one-approved-wider retry. - examples/acp-agent: composes sandbox-policy + fs-sandbox, drops the gating that disabled the fs stack under confined modes. RFC docs/rfc/implemented/feature/2026-07-14-cross-family-fs-sandbox.md; the old sandbox RFC's In-process/deferred/FAQ sections updated to shipped fact.
2.5 KiB
dsh-sandbox-policy — the sandbox policy home (ctx.sandboxPolicy)
The single owner of the deployment's sandbox policy: the file-effect SandboxMode a session starts from, the workspace-write boundary root, and the per-session sandbox/mode override every enforcing capability family reads.
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 held its own mode + workspaceRoot config, 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 inject ctx.sandboxPolicy and read the SAME default instead. The cross-family fs sandbox RFC records the decision.
Config
mode— the deployment defaultSandboxMode(read-only/workspace-write/danger-full-access), validated at load. Defaultread-only(fail-safe).workspaceRoot— the absolute directoryworkspace-writemay write under. Defaultprocess.cwd(), resolved absolute either way.
Surface
ctx.sandboxPolicy.defaultMode/ctx.sandboxPolicy.workspaceRoot— the deployment default the enforcing implementations read for their resolve fallback and boundary.effectiveSandboxMode(events)— the pure fold of a session'ssandbox/modeevents (the last switch wins, orundefined). The tool layers apply it to stamp each call, so neither the executor nor the provider depends on session events.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.SANDBOX_MODES— every mode, for option advertisement and runtime validation.
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 = fold(events) ?? the deployment default, so an override survives restart by replay, two sessions never see each other's state, and there is no external config store. The event is log-only (the approval/* precedent): the model learns the mode from the enforcing tools' denial markers, never from the event. Execution honors the fold in each tool layer, weakest-precedence beneath an escalation grant.