You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This consolidates #3 and #4 — the same underlying problem seen from two angles. After a full source-level analysis of DSH core and a fully captured failing session, the conclusion is:
The sandbox escalation … is not strictly wider … failures are produced entirely inside DSH core (deepseek-harness), not by this plugin. This plugin's adapters forward the tool schemas DSH assembles, verbatim; there is nothing to fix here. This issue documents the verified root cause, an end-to-end reproduced trace, and the proposed upstream fix, and serves as the canonical tracker.
Two compounding design gaps in DSH core:
Escalation fields are advertised statically, ignoring the session's current mode. Whenever a confining executor/filesystem is mounted, every sandbox-enforcing tool (bash, pwsh, write, edit) advertises sandbox_permissions with the full enum ["workspace-write", "danger-full-access"] — baked at composition time. A session already standing at workspace-write still sees "workspace-write" as an option; a session at danger-full-access (issue Error: sandbox escalation to "danger-full-access" is not strictly wider than this call's current "danger-full-access" mode #3's case) still sees both. Escalation must be strictly wider than the current mode, so these values are guaranteed-invalid in those states — the schema itself hands the model a trap.
The rejection is not self-correcting. An equal-or-narrower request fails with:
sandbox escalation to "X" is not strictly wider than this call's current "X" mode
True, but it never says nothing was denied — retry without the fields. Models read it as "my operation was rejected → I need more permission" and escalate harder.
Reproduced end-to-end trace
Captured from a local DSH web session log (session-c69ec652…), provider codex, model gpt-5.6-luna, reasoning effort medium — notably not a weak model.
Session state: cwd /Users/v1ki/Desktop/factory, preset workspace-write (sandbox workspace-write, approval ask). The user asked for a prompt text to be saved to a file inside the workspace — a bare write would have succeeded on step 1 with no escalation whatsoever.
escalated to sandbox_permissions: "danger-full-access", justification: “前一次工作区写入调用被运行环境拒绝,需要使用更宽权限…”
approval/asked fired — a real danger-full-access approval prompt shown to the user
Key observations:
The sandbox never denied anything in the entire session. All four failures were self-inflicted by the schema; the [sandbox: file access denied …] marker never appeared.
The step-1 justification shows the model (mis)read sandbox_permissions as “declare the mode this operation runs under”, and picked exactly the value the runtime context had just advertised (“Current DSH file policy: workspace-write …”).
The step-4 justification is factually false — no write was ever rejected by the runtime; only the escalation request was rejected. The cascade ends with a needless full-access approval prompt for an operation that was always permitted at the standing mode. A reflexive “Approve” runs a plain in-workspace write at danger-full-access: a real (if mild) security widening caused purely by schema UX.
The write schema as actually advertised in this session (from the logged request/header)
{
"name": "write",
"description": "Create or fully replace a UTF-8 text file.",
"parameters": {
"type": "object",
"properties": {
"file_path": { "type": "string", "description": "Path to write, resolved by the filesystem backend." },
"content": { "type": "string", "description": "Full UTF-8 text content to write." },
"sandbox_permissions": {
"type": "string",
"enum": ["workspace-write", "danger-full-access"],
"description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval."
},
"justification": { "type": "string", "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." }
},
"required": ["file_path", "content"]
}
}
Note the enum contains "workspace-write" — the session's own standing mode, which can never be a valid escalation target in this state.
and packages/fs/tool-fs/src/sandbox.ts (FsSandboxController.schemaFields(), shared by write/edit) spreads the same two fields with enum: [...ESCALATION_TARGETS]. The decision is made once at plugin load from the capability fact (“is a confining backend mounted?”), never from the session's standing mode.
The strict-wider check lives only at execution.packages/sandbox/sandbox/src/escalation.ts:
exportconstWIDER_MODES: Record<string,readonlySandboxMode[]>={'read-only': ['workspace-write','danger-full-access'],'workspace-write': ['danger-full-access'],}// in approveEscalation():if(!(WIDER_MODES[effectiveMode]??[]).includes(modeasSandboxMode)){thrownewError(`sandbox escalation to "${mode}" is not strictly wider than this call's current "${effectiveMode}" mode`)}
The code comments explain the enum is deliberately not cut down by the composition default (a narrower-switched session would be stranded) — a sound argument that nevertheless does not cover the session's standing mode, which is per-call truth the advertised schema currently contradicts on every request.
The trap is armed by DSH's own runtime context.SandboxPolicyService injects per-request context naming the current mode (“Current DSH file policy: workspace-write. Any available operation … may modify files under the session workspace …”), which is exactly the string a confused model copies into sandbox_permissions.
The adapter receives GenerateOptions.tools already assembled by DSH. It has no session handle and cannot know the standing mode, so it cannot narrow the enum correctly.
Unconditionally stripping the fields would break the one sanctioned mechanism DSH gives models to request wider access after a real denial in read-only/workspace-write sessions.
Proposed upstream fix
1. Narrow the advertised enum per assembly (structural fix). Prompt assembly runs before every model step, AssembleContext carries the agent (see packages/core/agent/src/runtime-types.ts), and the system-prompt/assemble waterfall's return value is authoritative — packages/core/agent/src/model-selection.ts is an in-tree precedent. SandboxPolicyService (which already owns per-session mode resolution and the sandbox:policy runtime context) can register a listener that rewrites every advertised sandbox_permissions (matched by parameter name — covers bash/pwsh/write/edit at once) to WIDER_MODES[standingMode]:
Per-assembly recomputation means a mid-session /permission switch is reflected on the next step; the registry stays untouched, so the “schemas are registry-global” doctrine holds.
2. Make the rejection self-correcting (behavioral safety net). Schema validation only checks advertised keys, so a model can still send the fields from conversational memory. In approveEscalation, split the “requested ⊆ current” case:
sandbox escalation to "workspace-write" is unnecessary: this call already runs under
"workspace-write" and nothing was denied — retry the same command without
sandbox_permissions and justification
3. (Optional) One sentence in renderPolicyContext for workspace-write: “Operations inside the workspace need no sandbox_permissions.” — removes the ambiguity that armed step 1.
Counterfactual against the trace above: with (1), step 1 cannot pick "workspace-write" and the natural bare write succeeds immediately; even if the fields are hallucinated from memory, (2) ends the cascade at step 1 instead of step 4's needless full-access prompt.
Workaround until upstream ships
If you hit … is not strictly wider …: nothing was denied — retry the same call withoutsandbox_permissions/justification. A standing user rule like “Never set sandbox_permissions unless the exact same call was just denied with a [sandbox: …] marker” suppresses the pattern.
Summary
This consolidates #3 and #4 — the same underlying problem seen from two angles. After a full source-level analysis of DSH core and a fully captured failing session, the conclusion is:
The
sandbox escalation … is not strictly wider …failures are produced entirely inside DSH core (deepseek-harness), not by this plugin. This plugin's adapters forward the tool schemas DSH assembles, verbatim; there is nothing to fix here. This issue documents the verified root cause, an end-to-end reproduced trace, and the proposed upstream fix, and serves as the canonical tracker.Two compounding design gaps in DSH core:
Escalation fields are advertised statically, ignoring the session's current mode. Whenever a confining executor/filesystem is mounted, every sandbox-enforcing tool (
bash,pwsh,write,edit) advertisessandbox_permissionswith the full enum["workspace-write", "danger-full-access"]— baked at composition time. A session already standing atworkspace-writestill sees"workspace-write"as an option; a session atdanger-full-access(issue Error: sandbox escalation to "danger-full-access" is not strictly wider than this call's current "danger-full-access" mode #3's case) still sees both. Escalation must be strictly wider than the current mode, so these values are guaranteed-invalid in those states — the schema itself hands the model a trap.The rejection is not self-correcting. An equal-or-narrower request fails with:
True, but it never says nothing was denied — retry without the fields. Models read it as "my operation was rejected → I need more permission" and escalate harder.
Reproduced end-to-end trace
Captured from a local DSH web session log (
session-c69ec652…), providercodex, modelgpt-5.6-luna, reasoning effortmedium— notably not a weak model.Session state: cwd
/Users/v1ki/Desktop/factory, presetworkspace-write(sandboxworkspace-write, approvalask). The user asked for a prompt text to be saved to a file inside the workspace — a barewritewould have succeeded on step 1 with no escalation whatsoever.writesandbox_permissions: "workspace-write", justification: “在当前工作区创建文本文件,保存刚才使用的图片提示词。”not strictly widererrorwritebash(printf redirect, same file)sandbox_permissions: "workspace-write"bashsandbox_permissions: "danger-full-access", justification: “前一次工作区写入调用被运行环境拒绝,需要使用更宽权限…”approval/askedfired — a real danger-full-access approval prompt shown to the userKey observations:
[sandbox: file access denied …]marker never appeared.sandbox_permissionsas “declare the mode this operation runs under”, and picked exactly the value the runtime context had just advertised (“Current DSH file policy: workspace-write …”).danger-full-access: a real (if mild) security widening caused purely by schema UX.The
writeschema as actually advertised in this session (from the loggedrequest/header){ "name": "write", "description": "Create or fully replace a UTF-8 text file.", "parameters": { "type": "object", "properties": { "file_path": { "type": "string", "description": "Path to write, resolved by the filesystem backend." }, "content": { "type": "string", "description": "Full UTF-8 text content to write." }, "sandbox_permissions": { "type": "string", "enum": ["workspace-write", "danger-full-access"], "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval." }, "justification": { "type": "string", "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." } }, "required": ["file_path", "content"] } }Note the enum contains
"workspace-write"— the session's own standing mode, which can never be a valid escalation target in this state.Root cause, verified in DSH source
All references are to current
mainof deepseek-ai/deepseek-harness.Advertising is composition-time, not session-time.
packages/shell/tool-bash/src/index.ts(same intool-pwsh):and
packages/fs/tool-fs/src/sandbox.ts(FsSandboxController.schemaFields(), shared bywrite/edit) spreads the same two fields withenum: [...ESCALATION_TARGETS]. The decision is made once at plugin load from the capability fact (“is a confining backend mounted?”), never from the session's standing mode.The strict-wider check lives only at execution.
packages/sandbox/sandbox/src/escalation.ts:The code comments explain the enum is deliberately not cut down by the composition default (a narrower-switched session would be stranded) — a sound argument that nevertheless does not cover the session's standing mode, which is per-call truth the advertised schema currently contradicts on every request.
The trap is armed by DSH's own runtime context.
SandboxPolicyServiceinjects per-request context naming the current mode (“Current DSH file policy: workspace-write. Any available operation … may modify files under the session workspace …”), which is exactly the string a confused model copies intosandbox_permissions.Why this plugin is the wrong layer (re: #4)
lib/index.jsis stale:omitSandboxEscalationfix never reaches the runtime #4 (stale bundle missing anomitSandboxEscalationfix) was disproven by inspecting the published npm artifact: no such code ever existed in any released version, the repo, or its history.GenerateOptions.toolsalready assembled by DSH. It has no session handle and cannot know the standing mode, so it cannot narrow the enum correctly.read-only/workspace-writesessions.Proposed upstream fix
1. Narrow the advertised enum per assembly (structural fix). Prompt assembly runs before every model step,
AssembleContextcarries theagent(seepackages/core/agent/src/runtime-types.ts), and thesystem-prompt/assemblewaterfall's return value is authoritative —packages/core/agent/src/model-selection.tsis an in-tree precedent.SandboxPolicyService(which already owns per-session mode resolution and thesandbox:policyruntime context) can register a listener that rewrites every advertisedsandbox_permissions(matched by parameter name — coversbash/pwsh/write/editat once) toWIDER_MODES[standingMode]:read-only["workspace-write", "danger-full-access"](unchanged)workspace-write["danger-full-access"]danger-full-accesssandbox_permissions+justificationentirelyPer-assembly recomputation means a mid-session
/permissionswitch is reflected on the next step; the registry stays untouched, so the “schemas are registry-global” doctrine holds.2. Make the rejection self-correcting (behavioral safety net). Schema validation only checks advertised keys, so a model can still send the fields from conversational memory. In
approveEscalation, split the “requested ⊆ current” case:3. (Optional) One sentence in
renderPolicyContextforworkspace-write: “Operations inside the workspace need nosandbox_permissions.” — removes the ambiguity that armed step 1.Counterfactual against the trace above: with (1), step 1 cannot pick
"workspace-write"and the natural barewritesucceeds immediately; even if the fields are hallucinated from memory, (2) ends the cascade at step 1 instead of step 4's needless full-access prompt.Workaround until upstream ships
If you hit
… is not strictly wider …: nothing was denied — retry the same call withoutsandbox_permissions/justification. A standing user rule like “Never setsandbox_permissionsunless the exact same call was just denied with a[sandbox: …]marker” suppresses the pattern.Action items
lib/index.jsis stale:omitSandboxEscalationfix never reaches the runtime #4 as duplicates pointing to this tracker