fix(sandbox): resolve workspace roots per session
This commit is contained in:
@@ -196,11 +196,10 @@ Requires: `sandbox` · `sandboxPolicy`
|
||||
```ts config-catalog
|
||||
/**
|
||||
* Plugin config: the local executor's knobs, verbatim. The sandbox policy —
|
||||
* the default mode and the `workspace-write` boundary root — is NOT here: it
|
||||
* lives on `ctx.sandboxPolicy` (`@deepseek-ai/dsh-sandbox-policy`), the one
|
||||
* home both enforcing families read, so bash and fs can never confine to
|
||||
* different roots. The runner choice is likewise the `ctx.sandbox` provider's
|
||||
* config, not this executor's.
|
||||
* the default mode and fallback `workspace-write` root — is NOT here: it lives
|
||||
* on `ctx.sandboxPolicy` (`@deepseek-ai/dsh-sandbox-policy`), which resolves
|
||||
* each calling session's mode and cwd for both enforcing families. The runner
|
||||
* choice is likewise the `ctx.sandbox` provider's config, not this executor's.
|
||||
*/
|
||||
export type Config = LocalConfig
|
||||
```
|
||||
@@ -349,8 +348,8 @@ Requires: `sandboxPolicy`
|
||||
/**
|
||||
* Plugin config: the local backend's knobs, verbatim (only `cwd`, the resolve
|
||||
* base for relative paths). The sandbox default (mode + `workspace-write`
|
||||
* boundary root) is NOT here — it lives on `ctx.sandboxPolicy`, the one home
|
||||
* both enforcing families share.
|
||||
* fallback root) is NOT here — `ctx.sandboxPolicy` resolves each calling
|
||||
* session for both enforcing families.
|
||||
*/
|
||||
export type Config = LocalConfig
|
||||
```
|
||||
@@ -746,8 +745,8 @@ export interface Config {
|
||||
/** File-sandbox mode a session starts from (default: `read-only`). */
|
||||
mode?: SandboxMode
|
||||
/**
|
||||
* Absolute root directory `workspace-write` may write under (default:
|
||||
* `process.cwd()`). Both enforcing families fence against this SAME root.
|
||||
* Fallback root for agentless calls and sessions without a cwd (default:
|
||||
* `process.cwd()`). Normal agent calls use their session cwd instead.
|
||||
*/
|
||||
workspaceRoot?: string
|
||||
}
|
||||
@@ -755,7 +754,7 @@ export interface Config {
|
||||
|
||||
Depends on: [`SandboxMode`](core-data-structures/sandbox.md)
|
||||
|
||||
Source: [`packages/sandbox/sandbox-policy/src/index.ts:44`](../packages/sandbox/sandbox-policy/src/index.ts)
|
||||
Source: [`packages/sandbox/sandbox-policy/src/index.ts:39`](../packages/sandbox/sandbox-policy/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-session-persistence-jsonl`
|
||||
|
||||
|
||||
@@ -458,12 +458,12 @@ abstract listDir(target: FsTarget, signal?: AbortSignal): Promise<FsDirEntry[]>
|
||||
* @param content - the full new file content.
|
||||
* @param expected - the write intent guarding the write; omit for unconditional.
|
||||
* @param signal - aborts before the atomic rename takes effect.
|
||||
* @param sandboxMode - the per-call sandbox mode this write runs under; a
|
||||
* sandboxing backend fences the write by it, the bare backend ignores it.
|
||||
* Omit to leave the backend its own default.
|
||||
* @param sandboxPolicy - the per-call mode and workspace root this write
|
||||
* runs under; a sandboxing backend fences the write by it, the bare backend
|
||||
* ignores it. Omit to leave the backend its own default.
|
||||
* @returns the outcome, including the version the write produced.
|
||||
*/
|
||||
abstract writeText( target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal, sandboxMode?: SandboxMode, ): Promise<FsWriteOutcome>
|
||||
abstract writeText( target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy, ): Promise<FsWriteOutcome>
|
||||
|
||||
/**
|
||||
* Atomically edit literal text. When supplied, the version guard is checked
|
||||
@@ -473,15 +473,15 @@ abstract writeText( target: FsTarget, content: string, expected?: FsWriteIntent,
|
||||
* @param edit - the literal search/replace request.
|
||||
* @param expected - the version guard; omit for an unconditional edit.
|
||||
* @param signal - aborts before the atomic rename takes effect.
|
||||
* @param sandboxMode - the per-call sandbox mode this edit runs under; a
|
||||
* sandboxing backend fences the edit by it, the bare backend ignores it.
|
||||
* Omit to leave the backend its own default.
|
||||
* @param sandboxPolicy - the per-call mode and workspace root this edit runs
|
||||
* under; a sandboxing backend fences the edit by it, the bare backend
|
||||
* ignores it. Omit to leave the backend its own default.
|
||||
* @returns the outcome, including the version the edit produced.
|
||||
*/
|
||||
abstract editText( target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal, sandboxMode?: SandboxMode, ): Promise<FsEditOutcome>
|
||||
abstract editText( target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy, ): Promise<FsEditOutcome>
|
||||
```
|
||||
|
||||
Types: [FsDirEntry](../core-data-structures/filesystem.md) · [FsEditOutcome](../core-data-structures/filesystem.md) · [FsEditRequest](../core-data-structures/filesystem.md) · [FsInfo](../core-data-structures/filesystem.md) · [FsPathInfo](../core-data-structures/filesystem.md) · [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core-data-structures/filesystem.md) · [FsWriteIntent](../core-data-structures/filesystem.md) · [FsWriteOutcome](../core-data-structures/filesystem.md) · [SandboxMode](../core-data-structures/sandbox.md)
|
||||
Types: [FsDirEntry](../core-data-structures/filesystem.md) · [FsEditOutcome](../core-data-structures/filesystem.md) · [FsEditRequest](../core-data-structures/filesystem.md) · [FsInfo](../core-data-structures/filesystem.md) · [FsPathInfo](../core-data-structures/filesystem.md) · [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core-data-structures/filesystem.md) · [FsWriteIntent](../core-data-structures/filesystem.md) · [FsWriteOutcome](../core-data-structures/filesystem.md) · [SandboxExecutionPolicy](../core-data-structures/sandbox.md)
|
||||
|
||||
Source: [`packages/fs/fs/src/index.ts:81`](../../packages/fs/fs/src/index.ts)
|
||||
|
||||
@@ -598,13 +598,28 @@ abstract confine(argv: readonly string[], policy: SandboxPolicy): ConfinedArgv
|
||||
|
||||
Types: [ConfinedArgv](../core-data-structures/sandbox.md) · [SandboxPolicy](../core-data-structures/sandbox.md)
|
||||
|
||||
Source: [`packages/sandbox/sandbox/src/index.ts:122`](../../packages/sandbox/sandbox/src/index.ts)
|
||||
Source: [`packages/sandbox/sandbox/src/index.ts:131`](../../packages/sandbox/sandbox/src/index.ts)
|
||||
|
||||
## `ctx.sandboxPolicy` — `SandboxPolicyService`
|
||||
|
||||
The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode and workspace root; enforcing implementations read defaultMode and workspaceRoot, and the tool layers fold each session's `sandbox/mode` override with effectiveSandboxMode on top.
|
||||
The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode and fallback workspace root. Tool layers call resolve for each execution so a session's mode log and immutable cwd travel together to every enforcing capability.
|
||||
|
||||
Source: [`packages/sandbox/sandbox-policy/src/index.ts:60`](../../packages/sandbox/sandbox-policy/src/index.ts)
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Resolve the complete policy for one capability call. An approved explicit
|
||||
* mode outranks the session's last `sandbox/mode` event, which outranks the
|
||||
* deployment default. A session cwd is its workspace-write boundary; the
|
||||
* configured root is the fallback for agentless calls and sessions without a
|
||||
* cwd.
|
||||
* @param request - optional session and approved mode override.
|
||||
* @returns the fully resolved per-call mode and absolute workspace root.
|
||||
*/
|
||||
resolve(request: SandboxPolicyRequest = {}): SandboxExecutionPolicy
|
||||
```
|
||||
|
||||
Types: [SandboxExecutionPolicy](../core-data-structures/sandbox.md) · [SandboxPolicyRequest](../core-data-structures/sandbox.md)
|
||||
|
||||
Source: [`packages/sandbox/sandbox-policy/src/index.ts:63`](../../packages/sandbox/sandbox-policy/src/index.ts)
|
||||
|
||||
## `ctx.sessionPersistence` — `SessionPersistence` (abstract seam)
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ interface BashExecRequest {
|
||||
* reject non-`DSH_*` names supplied through this managed channel.
|
||||
*/
|
||||
dshEnv?: DshEnvironment | undefined
|
||||
/** Explicit per-call sandbox mode override. */
|
||||
sandboxMode?: SandboxMode | undefined
|
||||
/** Fully resolved per-call sandbox policy; sandboxing executors default it. */
|
||||
sandboxPolicy?: SandboxExecutionPolicy | undefined
|
||||
}
|
||||
```
|
||||
|
||||
@@ -100,8 +100,8 @@ interface BashExecSpec {
|
||||
env?: Record<string, string> | undefined
|
||||
/** Managed `DSH_*` snapshot; implementations reject ordinary names. */
|
||||
dshEnv?: DshEnvironment | undefined
|
||||
/** Resolved sandbox mode; ignored by executors that do not confine. */
|
||||
sandboxMode: SandboxMode | undefined
|
||||
/** Resolved sandbox policy; ignored by executors that do not confine. */
|
||||
sandboxPolicy: SandboxExecutionPolicy | undefined
|
||||
}
|
||||
```
|
||||
|
||||
@@ -159,7 +159,7 @@ interface CollectedOutput {
|
||||
|
||||
## File sandbox: `BashSandboxInfo`
|
||||
|
||||
A sandbox-consuming executor exposes its configured fallback through `BashExecutor.sandboxMode`. The tool layer folds each session's durable `sandbox/mode` override (owned by [`@deepseek-ai/dsh-sandbox-policy`](../../packages/sandbox/sandbox-policy/README.md)) and may replace it for one user-approved strictly wider call. The mode/enforcement vocabulary is owned by the [`@deepseek-ai/dsh-sandbox` seam](sandbox.md); modes govern file effects only.
|
||||
A sandbox-consuming executor exposes its configured mode fallback through `BashExecutor.sandboxMode`. The tool layer asks [`@deepseek-ai/dsh-sandbox-policy`](../../packages/sandbox/sandbox-policy/README.md) to resolve each calling session's durable `sandbox/mode` override and immutable cwd into `BashExecRequest.sandboxPolicy`; a user-approved strictly wider call replaces only the mode. The mode/root/enforcement vocabulary is owned by the [`@deepseek-ai/dsh-sandbox` seam](sandbox.md); modes govern file effects only.
|
||||
|
||||
A sandboxed run reports its mode, conservative denial classification, and enforcement completeness. `runnerFailed` marks a sandbox runner failure before the command ran; foreground execution throws `SANDBOX_UNAVAILABLE`, while a settled background process has only its facts channel.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
|
||||
| [user-interaction.md](user-interaction.md) | the UI-backed human question/answer seam: `AskUserQuestionRequest`, answer/options vocabulary, provider API, error taxonomy |
|
||||
| [approval.md](approval.md) | the one-shot user-approval seam: `ApprovalRequest`, `ApprovalOutcome`, per-session policy, audit and answerer contracts |
|
||||
| [bash.md](bash.md) | the bash executor seam: `BashExecRequest`/`Spec`, `BashRunResult`, background `BashProcess` handles |
|
||||
| [sandbox.md](sandbox.md) | the process-confinement seam: file-effect modes, `SandboxPolicy`, `ConfinedArgv`, enforcement and fail-closed errors |
|
||||
| [sandbox.md](sandbox.md) | per-session policy resolution and the process-confinement seam: file-effect modes, execution/provider policies, `ConfinedArgv`, enforcement and fail-closed errors |
|
||||
| [code-runtime.md](code-runtime.md) | the code-execution seam: `CodeRunRequest`/`Result`, binding namespaces, captured logs, the `CodeRunFailure` taxonomy |
|
||||
| [filesystem.md](filesystem.md) | the filesystem seam: `FsTarget`, read/write/edit outcomes, observed-file state, `FsErrorCode` |
|
||||
| [skills.md](skills.md) | the skill service: discovery priority, `SkillSummary`/`SkillDefinition`, session-prefix catalog, model-facing `skill` loading |
|
||||
|
||||
@@ -38,7 +38,35 @@ type SandboxEnforcement = 'full' | 'partial'
|
||||
|
||||
## Per-call policy
|
||||
|
||||
The policy is fully resolved and carried per call. This permits concurrent consumers and one-shot escalated retries to ask the same provider for different boundaries without mutating provider state.
|
||||
The complete execution policy is resolved and carried per capability call. It includes `danger-full-access` so a consumer can resolve policy once before deciding whether to bypass confinement. Normal tool calls derive `workspaceRoot` from the calling session's immutable cwd; deployment configuration is the agentless fallback.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* The complete file-effect policy resolved for one capability call. The root
|
||||
* is carried even under modes that do not consume it so callers can resolve
|
||||
* policy once before choosing the enforcement path.
|
||||
*/
|
||||
interface SandboxExecutionPolicy {
|
||||
/** The file-effect mode this execution runs under. */
|
||||
mode: SandboxMode
|
||||
/** Absolute root directory `workspace-write` may write under. */
|
||||
workspaceRoot: string
|
||||
}
|
||||
```
|
||||
|
||||
`ctx.sandboxPolicy.resolve()` accepts the active session and, for an approved retry, an explicit mode. The service owns precedence and root fallback so bash and fs do not repeat it.
|
||||
|
||||
```ts type-equiv
|
||||
/** Inputs that select the sandbox policy for one capability call. */
|
||||
interface SandboxPolicyRequest {
|
||||
/** Calling session; its immutable cwd becomes the workspace boundary. */
|
||||
session?: Session
|
||||
/** Explicit approved mode override, which outranks session policy. */
|
||||
mode?: SandboxMode
|
||||
}
|
||||
```
|
||||
|
||||
Only a confined execution reaches `ctx.sandbox`; its provider policy narrows the mode while retaining the same root. This permits concurrent sessions, consumers, and one-shot escalated retries to ask the same provider for different boundaries without mutating provider state.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -46,15 +74,12 @@ The policy is fully resolved and carried per call. This permits concurrent consu
|
||||
* fixed on the provider: two consumers may confine under different policies
|
||||
* at the same instant (bash under `read-only` while a confined child agent
|
||||
* needs its state directory writable), and an approved escalated retry is a
|
||||
* new call with a wider policy. Defaulting/resolution is the consumer's
|
||||
* explicit step (its config owns the fallback chain); the provider treats
|
||||
* the policy as fully specified.
|
||||
* new call with a wider policy. Defaulting/resolution is an explicit step at
|
||||
* the consumer boundary; the provider treats the policy as fully specified.
|
||||
*/
|
||||
interface SandboxPolicy {
|
||||
interface SandboxPolicy extends SandboxExecutionPolicy {
|
||||
/** The file-effect mode this execution runs under. */
|
||||
mode: ConfinedSandboxMode
|
||||
/** Absolute root directory `workspace-write` may write under. */
|
||||
workspaceRoot: string
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user