feat(bash): the sandboxed executor — per-call policy carrier, denial facts, runner-failure classification

dsh-bash grows the per-call policy carrier: BashExecRequest.sandboxMode
(request-optional, spec required-but-nullable — the owner pattern; resolve()
is the one explicit defaulting step) and the BashExecutor.sandboxMode
capability fact (undefined in the base class — composition truth the tool
layer can read). dsh-bash-local carries the field verbatim and confines
nothing.

dsh-bash-sandbox extends LocalBashExecutor and hands ctx.sandbox the exact
argv it is about to spawn. A denial is a RESULT FACT (the command RAN;
result.sandbox.denied is orthogonal to exitCode/signal), classified
conservatively against the wrap own dialect; a RUNNER failure outranks
denial — foreground re-throws the structured SANDBOX_UNAVAILABLE, a settled
background task stamps sandbox.runnerFailed — so a broken sandbox never
reads as a failing command and the command never runs unconfined.
dsh-tool-bash renders the markers and teaches the model not to retry around
a policy denial; escalation and per-session switching are staged follow-ups.
This commit is contained in:
kingwl
2026-07-09 16:05:44 +08:00
parent 7b8c3a9b40
commit 2eed448acf
38 changed files with 1529 additions and 40 deletions

View File

@@ -15,6 +15,7 @@
*/
import { Context, Service } from 'cordis'
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
import type { BashExecRequest, BashExecSpec, BashRunResult, BashTask, BashTaskId, BashTaskListener, BashTaskRead, OwnerToken } from './types.ts'
export { BashTaskId, OwnerToken } from './types.ts'
@@ -22,6 +23,7 @@ export type {
BashExecRequest,
BashExecSpec,
BashRunResult,
BashSandboxInfo,
BashTask,
BashTaskListener,
BashTaskRead,
@@ -70,6 +72,21 @@ export abstract class BashExecutor extends Service {
}, 'bash listener teardown')
}
/**
* The sandbox mode this executor confines commands under BY DEFAULT, or
* `undefined` when it does not sandbox at all — the capability fact the
* tool layer reads to advertise escalation honestly (a mode-widening lever
* is only offered when a sandboxing executor is mounted to honor it, and
* only for modes strictly wider than this one). Composition truth, not
* configuration: the base class reports `undefined`; a sandboxing
* implementation overrides the getter with its configured mode.
* @returns the configured default mode of a sandboxing executor;
* `undefined` for an executor that never confines.
*/
get sandboxMode(): SandboxMode | undefined {
return undefined
}
/**
* Resolve a caller's {@link BashExecRequest} into a fully-specified
* {@link BashExecSpec}, applying this implementation's config defaults and