feat(tool-bash): sandbox escalation — one approved wider retry after a denial

The tool gate advertises sandbox_permissions (an enum of exactly the modes
STRICTLY WIDER than the mounted executor default — the schema makes a
non-widening request inexpressible) plus a required justification, exactly
when ctx.bash.sandboxMode reports a confining mode at registration:
composition truth, never a dead lever. An escalating call resolves
ctx.approval BEFORE anything executes with the audit-self-contained reason
"escalate sandbox to <mode>: <justification>"; allowed-once stamps the
granted mode onto that one bash request (the seam-level per-call override),
while rejected / cancelled / unavailable and the no-service / no-agent
paths each fail closed with their own error text and execute nothing. The
description teaches the flow only when the fields exist: retry the SAME
command once after a real denial, never preemptively; a rejected
escalation is final. No new session events: the attempt is an ordinary
tool/call, the decision is the approval audit pair, the outcome an
ordinary tool/result whose facts name the mode it ran under.
This commit is contained in:
kingwl
2026-07-09 16:37:10 +08:00
parent 2eed448acf
commit 0e49615a3d
7 changed files with 493 additions and 34 deletions

View File

@@ -52,5 +52,5 @@ The `BashExecRequest` seam carries optional `stdin` and `env`, used by the hooks
Commands run with the executor's full authority unless a sandboxing executor ([`dsh-bash-sandbox`](../bash-sandbox/)) confines them — the deny-only sandbox reports denials as result facts, rendered here as the denial marker; per-call allow/deny/ask policy is the `tools/pre-execute` waterfall (see docs/architecture.md).
The escalation gate — one approved wider retry of a denied command through `ctx.approval` — is the sandbox RFC's staged follow-up ([§ Escalation](../../../docs/rfc/proposed/feature/2026-07-06-sandbox.md)); this layer today renders the denial facts and forbids retrying around them.
On top of a denial sits the escalation gate ([the sandbox RFC § Escalation](../../../docs/rfc/proposed/feature/2026-07-06-sandbox.md)): an escalating call (`sandbox_permissions` + `justification`) resolves [`ctx.approval`](../../approval/approval/README.md) BEFORE anything executes — `allowed-once` stamps the granted mode onto the bash request as the seam-level `sandboxMode` override (that one call runs, classifies, and reports under the wider mode; its neighbors keep the executor's default), while `rejected`/`cancelled`/`unavailable` and the no-service / no-agent paths each fail closed with their own error text and execute nothing. The seam is consumed opportunistically (`ctx.get('approval')`, the dsh-tools ask-routing pattern); the grant is consumed by the very call that asked, and nothing is stored. The static description teaches — and a denied result itself prompts, via the escalation-available marker appended exactly when the fields are advertised — the SAME-TURN flow: on a denial a wider mode would cure, retry the exact command once with `sandbox_permissions` (the narrowest mode that suffices) + `justification` immediately, without detouring through chat (the approval prompt IS the user's consent); never speculatively — an escalation is grounded in a real denial (up-front only when the session already denied the same access), a prompt-stated approvals-disabled policy turns the exception off entirely, and a rejected escalation is final for that command.