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.
52 lines
2.4 KiB
TypeScript
52 lines
2.4 KiB
TypeScript
/**
|
|
* The writable-root derivation shared by every enforcement dialect that
|
|
* expresses a mode as a canonical allow-list: `workspace-write` means "the
|
|
* workspace root plus the platform temp areas", and this module is that
|
|
* meaning's one home. The Seatbelt profile
|
|
* (`@deepseek-ai/dsh-sandbox-local`) and the in-process filesystem fence
|
|
* (`@deepseek-ai/dsh-fs-sandbox`) both derive their allow-list here, so "the
|
|
* write tool cannot write /tmp but bash can" asymmetries cannot arise between
|
|
* them. The bwrap and Landlock dialects keep their own grant spellings (an
|
|
* ephemeral `/tmp` mount, launcher-owned flags) — the honest per-runner
|
|
* differences recorded in the sandbox RFC — with parity pinned by test.
|
|
*
|
|
* @module dsh-sandbox/roots
|
|
*/
|
|
|
|
import { realpathSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import type { SandboxPolicy } from './index.ts'
|
|
|
|
/**
|
|
* Resolve a granted root to the path the enforcement layer actually compares:
|
|
* canonical (symlinks resolved), because both Seatbelt filters and the fs
|
|
* fence's containment check match resolved paths — `/tmp` IS `/private/tmp`
|
|
* on darwin, and an as-spelled grant would match nothing.
|
|
* @param path - the root as configured or platform-reported.
|
|
* @returns the canonical path, or the spelling as-is when resolution fails
|
|
* (a missing root matches nothing until it exists — the conservative
|
|
* outcome; inventing a fallback would grant a path the caller never named).
|
|
*/
|
|
export function canonicalPath(path: string): string {
|
|
try {
|
|
return realpathSync(path)
|
|
} catch {
|
|
// realpathSync failed: the path (or a prefix) is missing or unreadable.
|
|
return path
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The roots one confined execution may WRITE under — the mode's meaning as a
|
|
* canonical, deduplicated allow-list. `read-only` allows nothing;
|
|
* `workspace-write` allows the policy's workspace root, the host `/tmp`, and
|
|
* the per-user platform temp dir (`os.tmpdir()` — the real temp area for
|
|
* mkstemp-family tools; omitting it would deny what the mode promises).
|
|
* @param policy - the file-effect policy to derive the allow-list from.
|
|
* @returns the canonical writable roots; empty exactly under `read-only`.
|
|
*/
|
|
export function writableRoots(policy: SandboxPolicy): string[] {
|
|
if (policy.mode !== 'workspace-write') return []
|
|
return [...new Set([policy.workspaceRoot, '/tmp', tmpdir()].map(canonicalPath))]
|
|
}
|