Replies: 8 comments
|
This is the cleanest separation of the variable in the whole family, and it holds up: one runner, one ConPTY, and the only thing that changes is the host binary carrying the runner. A console-subsystem Two consequences of your report that are easy to miss and that I have made explicit in a diagnostic plugin I maintain,
The advisory still ships no command to run: there is no shell to run one in, and it tells the model to stop rather than retry. What it does hand the user is a fourth option, taken from Your report is named as the measurement the section rests on, and npm: |
|
Independent confirmation on a second machine, plus some process-level evidence we captured while it was failing. Environment: Windows 10 Pro x64 (build 22631, zh-CN), desktop host 0.2.0-rc.2, no PowerShell 7 (falls back to Windows PowerShell 5.1), Git Bash present. Same symptom: every Evidence captured during the failure window (19:14–20:10 local):
One data point that doesn't fit the deterministic model: our failures ran 19:14–20:10 (including immediately after a full app restart at 19:53), then recovered at 20:39 with no restart and no config change — same session, same recorded Happy to capture more (child argv via process polling, ETW) next time it reproduces. |
|
Hi, Non-PTY data point: same call site, same variable, raw Environment for the measurements below: Windows 11 Pro (10.0.26200), desktop build The session these measurements come from used the ordinary Same call site, confirmed against the running build rather than a checkout. The desktop bundle is an asar archive, so its packages can be read out of Control experiment, driven with the running build's own extracted 0.2.0 code, through the same outer launch layer the job runner uses (
Electron-as-node is therefore fine on its own; only its role as the ACL runner host fails. The console state also differs between exactly those two binaries, measured with Excluded by direct measurement, so that these do not need to be re-checked: the keep-alive group is present in the restricting list ( One arm differs from the control table in the opening post. That table records "Electron (RunAsNode) + no ConPTY" as working, whereas on this machine the non-PTY tool path fails deterministically with Fix candidate, verified. Re-running the identical confined chain with the app's own bundled All arms reported in this discussion are consistent with one rule: under the restricted token the child can inherit an already-attached console or run with no console at all, but it cannot be attached to a console that this spawn created — a ConPTY pseudoconsole, or a freshly allocated console. Repro scripts can be provided if they would help. |
|
Triage summary for maintainers - everything below is consolidated from this thread (nothing new; all sourced from the posts above): the root cause, the one unexplained arm, the verified fix, and the diagnostics asks. Credit: @asgdf-d (original report and root-cause isolation), @argszero, @longxing-alt, @szb15137 (independent measurements). (This repo has Issues disabled, so this discussion is the tracking vehicle - please prioritize/label here.) SummaryOn the Windows desktop build, every shell tool invocation fails while sandbox mode is A second surface with the same root cause (@szb15137): the plain non-PTY pwsh tool ( Environment
Reproduction
Session-override semantics (this is what makes the bug look intermittent to users, while it is deterministic per (session mode × sandbox runner path)): a session whose event stream records Root causeChain: the persistent shell session goes through In the desktop host, Unified rule, consistent with every measurement so far: under the restricted token the child can inherit an already-attached console or run with no console at all, but it cannot be attached to a console that this spawn created. MeasurementsThree-way control table (@asgdf-d's machine — one runner, one ConPTY, only the host binary changes):
Control table on a second machine (@szb15137, driven with the running build's own extracted 0.2.0 code through the same outer launch layer the job runner uses:
Electron-as-node is fine on its own; only its role as the ACL runner host fails. Excluded by direct measurement (no need to re-check): the keep-alive group is present in the restricting SID list ( Process-level evidence during the failure window (@longxing-alt): the Windows PowerShell event log shows zero engine-start events (400/403) at the failure timestamps while unrelated One arm to pin: spawn-path selection
Hypothesis: the deciding variable is whether anything in the chain was created with Related observation: after the shell recovered, the persistent REPL showed up as Suggested fixes
Workarounds until fixed
Related: #8313 (same host variable seen from the other side, per the thread). Diagnostic tooling: Repro material: @asgdf-d offered node-pty repro scripts / runner logs, @szb15137 offered repro scripts — happy to attach them here if maintainers want them. Addendum (Oct 4) — folding in @OrangeStone446's fourth machine and a mechanism-level analysis from #8775:
|
|
I met a similar problem yet. Windows:
|
| Item | Value |
|---|---|
| OS | Windows 11 Home (Chinese), 10.0.26100 |
| DSH Desktop | 0.2.0-rc.2 (installed at D:\DSH) |
| Node | 24.21.0 |
| PowerShell | 7.6.6 x64, MSI install, C:\Program Files\PowerShell\7\pwsh.exe |
| Account | local Administrator (high integrity) |
| Security software | 360 Total Security (AntiVirusProduct productState = 331776, enabled) + Windows Defender |
| Workspace | D:\DSH--Test, on drive D: |
| Host | desktop DeepSeek Harness.exe, profile desktop |
Conclusion: the filesystem ACL fence works; only starting a child process under the restricted token fails.
Causes ruled out
Each of the following was tested, not assumed:
Not a missing PowerShell installation.
C:\Program Files\PowerShell\7\pwsh.exeexists and runs standalone; the registry keyHKLM:\SOFTWARE\Microsoft\PowerShellCore\InstalledVersionscarriesSemanticVersion=7.6.6; the systemPATHcontainsC:\Program Files\PowerShell\7\. Running it from an ordinary terminal prints7.6.6.Not executable resolution. The first candidate of
candidatePwshPaths()in@deepseek-ai/dsh-pwsh-localis%ProgramFiles%\PowerShell\7\pwsh.exe, so it always matches; the profile'scordis.patch.ymlcontains nopwshPathoverride.Not a workspace ACL problem. A full diagnose-and-repair run of this repository's bundled
diagnose-windows-sandbox-aclscript againstD:\DSH--Test(run unconfined) reported:VERDICT=NOT_THIS_CLASS "No package allow ACE and no missing WRITE_DAC or WRITE_OWNER was observed on the inspected objects; no ACL change was needed." SUMMARY FIXED=0 GRANTED=0 REFUSED=0 RESTORED=0That is,
writeDac=TrueandwriteOwner=True, and noS-1-15-2-*foreign package ACEs to remove. The full record is in the appendix below.Not an unreadable binary.
C:\Program Files\PowerShell\7grantsBUILTIN\UsersReadAndExecute, Synchronize; the directory carries no explicit integrity label (it inherits Medium).
Suspected direction
0xC0000142 surfaces at the token-construction layer, not the file-permission layer. DSH's own sandbox documentation names this exact exit code:
The keep-alive group (logon SID + Everyone) is present in both modes: without it, early DLL initialization dies with
0xC0000142and CNG crashes pwsh with0xE0434352.
So the suspicion is that the Windows ACL restricted-token runner (@deepseek-ai/dsh-sandbox-windows-acl) fails to create a usable restricted token on this machine. I tried to reproduce the mechanism standalone (CreateRestrictedToken + set the low-integrity label + CreateProcessWithTokenW) but could not complete it, because SetTokenInformation returned 998. So the root cause is not confirmed — only the exclusionary evidence above is offered.
Impact and workaround
- Impact: in Workspace Write the sandboxed shell is entirely unusable; the session must be widened to full access to run any command at all, which removes the file-sandbox protection.
- Workaround: switch the session to full access whenever commands must run.
- Note: the failure predates any installation change on this machine — see
WARN powershell: PowerShell 7 not foundin thedsh-win32 doctoroutput.
Aside: a separate installation trap worth confirming
On this machine, winget install --id Microsoft.PowerShell installs the MSIX/Store build (PowerShell-7.6.6.msixbundle), which:
- lands in
C:\Program Files\WindowsApps\Microsoft.PowerShell_7.6.6.0_x64__8wekyb3d8bbwe; - does not write the
PowerShellCore\InstalledVersionsregistry key; - does not create
C:\Program Files\PowerShell\7\; - exposes
pwsh.exeonly through theWindowsAppsapp-execution alias.
DSH's resolver only looks at %ProgramFiles%\PowerShell\7\pwsh.exe and PATH, so it reports "PowerShell 7 not found" even though winget list shows the package as installed. Installing the MSI instead (or --installer-type msi) clears that warning. This may deserve its own hint in dsh-win32 doctor.
Appendix: raw `diagnose-windows-sandbox-acl` record (acl-report-4a4debcf158f497aa3ce78b6b549893a.jsonl, all 12 lines)
{"kind":"invocation","operation":"repair","path":"","status":"started","reason":"Inspect the requested paths, then repair the ACL problems this run can prove, before anything else is attempted.","details":{"paths":["D:\\DSH--Test"],"outputDirectory":"D:\\DSH--Test\\.acl-recovery","allowRoot":"D:\\DSH--Test","recoveryRecord":""}}
{"kind":"action","operation":"prepare_report","path":"D:\\DSH--Test\\.acl-recovery","status":"started","reason":"Create the recovery directory and a new JSONL report that keeps every record, because tool output is truncated to its tail.","details":{"effect":"files","id":1}}
{"kind":"action","operation":"prepare_report","path":"D:\\DSH--Test\\.acl-recovery","status":"completed","reason":"Create the recovery directory and a new JSONL report that keeps every record, because tool output is truncated to its tail.","details":{"effect":"files","id":1}}
{"kind":"action","operation":"initialize","path":"","status":"started","reason":"Load read-only access checks and DACL-only writes before inspecting permissions.","details":{"effect":"none","id":2}}
{"kind":"action","operation":"initialize","path":"","status":"completed","reason":"Load read-only access checks and DACL-only writes before inspecting permissions.","details":{"effect":"none","id":2}}
{"kind":"observation","operation":"caller","path":"","status":"read","reason":"The caller token determines effective access; unconfined execution does not imply elevation.","details":{"integrity":"S-1-16-12288 (High)","sid":"S-1-5-21-2323057633-2177745152-3024662527-500"}}
{"kind":"observation","operation":"inspect_acl","path":"D:\\DSH--Test","status":"read","reason":"Read the ACL and check effective WRITE_DAC and WRITE_OWNER; observed ACEs alone do not identify which rule caused a denial.","details":{"owner":"S-1-5-32-544","acesOmitted":false,"writeOwner":true,"lowLabel":true,"aces":[{"sid":"S-1-1-0","type":"Deny","rights":"DeleteSubdirectoriesAndFiles","inherited":false,"inheritance":"ContainerInherit","propagation":"None"},{"sid":"S-1-4-889669682-638475085","type":"Allow","rights":"DeleteSubdirectoriesAndFiles, Write, Delete, Synchronize","inherited":false,"inheritance":"ContainerInherit, ObjectInherit","propagation":"None"},{"sid":"S-1-5-32-544","type":"Allow","rights":"FullControl","inherited":true,"inheritance":"None","propagation":"None"},{"sid":"S-1-5-32-544","type":"Allow","rights":"268435456","inherited":true,"inheritance":"ContainerInherit, ObjectInherit","propagation":"InheritOnly"},{"sid":"S-1-5-18","type":"Allow","rights":"FullControl","inherited":true,"inheritance":"None","propagation":"None"},{"sid":"S-1-5-18","type":"Allow","rights":"268435456","inherited":true,"inheritance":"ContainerInherit, ObjectInherit","propagation":"InheritOnly"},{"sid":"S-1-5-11","type":"Allow","rights":"Modify, Synchronize","inherited":true,"inheritance":"None","propagation":"None"},{"sid":"S-1-5-11","type":"Allow","rights":"-536805376","inherited":true,"inheritance":"ContainerInherit, ObjectInherit","propagation":"InheritOnly"},{"sid":"S-1-5-32-545","type":"Allow","rights":"ReadAndExecute, Synchronize","inherited":true,"inheritance":"None","propagation":"None"},{"sid":"S-1-5-32-545","type":"Allow","rights":"-1610612736","inherited":true,"inheritance":"ContainerInherit, ObjectInherit","propagation":"InheritOnly"}],"errors":[],"writeDac":true}}
{"kind":"observation","operation":"inspect_acl","path":"D:\\","status":"read","reason":"Read the ACL and check effective WRITE_DAC and WRITE_OWNER; observed ACEs alone do not identify which rule caused a denial.","details":{"owner":"S-1-5-18","acesOmitted":false,"writeOwner":true,"lowLabel":null,"aces":[{"sid":"S-1-5-11","type":"Allow","rights":"-536805376","inherited":false,"inheritance":"ContainerInherit, ObjectInherit","propagation":"InheritOnly"},{"sid":"S-1-5-11","type":"Allow","rights":"Modify, Synchronize","inherited":false,"inheritance":"None","propagation":"None"},{"sid":"S-1-5-18","type":"Allow","rights":"FullControl","inherited":false,"inheritance":"None","propagation":"None"},{"sid":"S-1-5-18","type":"Allow","rights":"268435456","inherited":false,"inheritance":"ContainerInherit, ObjectInherit","propagation":"InheritOnly"},{"sid":"S-1-5-32-544","type":"Allow","rights":"268435456","inherited":false,"inheritance":"ContainerInherit, ObjectInherit","propagation":"InheritOnly"},{"sid":"S-1-5-32-544","type":"Allow","rights":"FullControl","inherited":false,"inheritance":"None","propagation":"None"},{"sid":"S-1-5-32-545","type":"Allow","rights":"ReadAndExecute, Synchronize","inherited":false,"inheritance":"None","propagation":"None"},{"sid":"S-1-5-32-545","type":"Allow","rights":"-1610612736","inherited":false,"inheritance":"ContainerInherit, ObjectInherit","propagation":"InheritOnly"}],"errors":[],"writeDac":true}}
{"kind":"observation","operation":"subtree_scan","path":"D:\\DSH--Test","status":"read","reason":"The caller cannot provision this directory, or it is the authorized root, so explicit package allow ACEs under it were collected in the same run; no reparse point or managed application tree is entered.","details":{"visited":7,"unreadable":0,"packageSources":[],"truncated":false}}
{"kind":"decision","operation":"classify","path":"D:\\DSH--Test","status":"NOT_THIS_CLASS","reason":"No package allow ACE was observed and both required rights are available. Other ACL restrictions and causes of the original failure are not ruled out.","details":{"writeOwner":true,"writeDac":true,"packageObjects":[]}}
{"kind":"decision","operation":"repair","path":"D:\\DSH--Test","status":"skipped","reason":"No package allow ACE and no missing WRITE_DAC or WRITE_OWNER was observed on the inspected objects; no ACL change was needed.","details":{}}
{"kind":"summary","operation":"repair","path":"D:\\DSH--Test","status":"completed","reason":"Operation completion records API execution; verification records the observed result. Only rerunning the original confined operation can confirm its failure is resolved.","details":{"granted":0,"recoveries":[],"operations":[{"id":1,"operation":"prepare_report","path":"D:\\DSH--Test\\.acl-recovery","effect":"files","status":"completed"},{"id":2,"operation":"initialize","path":"","effect":"none","status":"completed"}],"automaticRollback":true,"restored":0,"rollback":"not-needed","report":"D:\\DSH--Test\\.acl-recovery\\acl-report-4a4debcf158f497aa3ce78b6b549893a.jsonl","rollbackCommands":[],"observationFailures":0,"scanTruncated":false,"fixed":0,"nextAction":"stop","refused":0,"exitCode":0}}
|
@OrangeStone446 Thanks — this is a clean exclusion dataset, and it slots into the consolidated triage summary above without changing the picture. Two notes on the suspected direction:
One variable worth keeping visible for maintainers: 360 Total Security on this machine (the other reports don't mention AV). Process creation under restricted tokens is a classic AV-minifilter interaction point, so it is a per-machine confound worth recording — though evidently not a necessary condition, given the other reproductions. The winget MSIX/Store PowerShell trap (app-execution alias only, no |
|
Thanks — the correction is well taken, and it removes a wrong lead I introduced. Two clarifications from my own reproduction attempt, plus the AV variable you asked to record.
1. Direct confirmation that the host is the GUI-subsystem binary
I read the PE header of the shipped desktop executable rather than inferring it:
D:\DSH\DeepSeek Harness.exe Machine = 0x8664 (x64) Subsystem = 2 → WINDOWS_GUI (no console by default)
Independently confirmed by parsing the same header from a separate Node process, so this build is squarely the "GUI-subsystem desktop binary as runner host" shape. That is the spawn-side leg only; I have not tested whether a conhost-attached host resolves it on this machine, and I would rather leave that to you than claim it.
2. Two tool paths, one cause, two different error surfaces
Worth folding into the triage summary, because the two look unrelated at first sight:
PathWhat the user sees on this machine
ephemeral pwsh tool (fresh pwsh -Command per call)no output at all, just [exit code: 3221225794]
persistent PTY pwsh toolPTY shell exited during startup
I confirmed the second string is the PTY service's own open-time rejection: the code throws exactly when the startup wait settles as session_exit, i.e. the top-level shell exited. So a report shaped like "the shell tool prints a raw 0xC0000142 and nothing else" is the same console arm — it just never reaches the PTY layer that produces the friendlier message. A triage reading of 0xC0000142 as a shell-binary or AV problem is therefore a trap on this build.
Also consistent with your reading: my standalone attempt to reproduce the token path died in SetTokenInformation (998, insufficient handle rights) before any process was created. That never touched child creation, so it neither supported nor refuted anything — I have dropped the keep-alive quote from my report and will point at @szb15137's table instead.
3. AV variable you asked to keep visible
360 Total Security, complete record for the thread:
product 360安全卫士, version 13.0.0.2003, C:\Program Files (x86)\360\360Safe
AntiVirusProduct productState = 331776
real-time module running: ZhuDongFangYu.exe (主动防御)
minifilter present and running: 360FsFlt.sys, alongside BAPIDRV64.sys, 360AntiHijack64.sys, 360AntiSteal64.sys, 360Box64.sys, 360qpesv64.sys, 360Sensor64.sys (13 drivers total)
Given your note that the other reproductions have no such AV, I agree this is a per-machine confound and not a necessary condition — recorded, not weighted.
4. MSIX resolver gap
Agreed it should be its own item rather than a footnote here. The shape: winget install --id Microsoft.PowerShell installs the .msixbundle, which exposes pwsh.exe only through the WindowsApps app-execution alias and writes neither PowerShellCore\InstalledVersions nor C:\Program Files\PowerShell\7\, so it is invisible to candidatePwshPaths() and to PATH; winget list still reports it as installed. A --installer-type msi note in dsh-win32 doctor would prevent the misdiagnosis. I will open it separately so it does not get lost in this thread.
橙石流萤
***@***.***
原始邮件
发件人:Xingyu Long ***@***.***>
发件时间:2026年10月4日 00:52
收件人:deepseek-ai/deepseek-harness ***@***.***>
抄送:OrangeStone446 ***@***.***>, Mention ***@***.***>
主题:Re: [deepseek-ai/deepseek-harness] [Windows Desktop 0.2.0-rc.2] All shell tools die with "PTY shell exited during startup" when sandbox mode is workspace-write (root cause: GUI-subsystem Electron as ACL sandbox runner host + ConPTY) (Discussion #8322)
@OrangeStone446 Thanks — this is a clean exclusion dataset, and it slots into the consolidated triage summary above without changing the picture. Two notes on the suspected direction:
Your own checks actually rule out the token-construction hypothesis: the file-sandbox results (write inside the workspace succeeds, write outside is denied) prove the restricted token is created and enforced correctly on this machine. What fails is only the spawn of a console child under that token when the runner host is the GUI-subsystem desktop binary — the console rule in the summary above (under the restricted token a console can be inherited or absent, never newly created by that spawn, whether a ConPTY pseudoconsole or a fresh allocation). Your doc quote about the keep-alive group describes a second, separate 0xC0000142 trigger; @szb15137's control table excludes keep-alive absence by direct measurement, so on these machines it is the console arm.
Your machine also fails with a proper pwsh 7.6.6 MSI install, which strengthens the shell-agnostic reading — the earlier three reports were all on the PowerShell 5.1 fallback, so the failing shell binary does not matter.
One variable worth keeping visible for maintainers: 360 Total Security on this machine (the other reports don't mention AV). Process creation under restricted tokens is a classic AV-minifilter interaction point, so it is a per-machine confound worth recording — though evidently not a necessary condition, given the other reproductions.
The winget MSIX/Store PowerShell trap (app-execution alias only, no PowerShellCore\InstalledVersions key, no %ProgramFiles% folder, invisible to candidatePwshPaths() and PATH) is a real, separate resolver gap — agreed it deserves its own hint in dsh-win32 doctor; probably worth its own discussion item so it does not get lost inside this thread.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!
You are receiving this because you were mentioned.
|
Independent reproduction: the GUI host is the trigger condition, the token default DACL is the defect
I filed #8775 (the parameterized default-DACL bisect). Folding the two together, I ran the 1. The console shape is not the deciding variableHeld constant across all four arms: GUI-subsystem host (Electron 44.0.0 run as node, no
Same host binary, same ConPTY, same argv, same target: flipping only the default-DACL SID A model that fits every table in this thread: the default-DACL ACE must satisfy both
This also covers the arm marked "one arm to pin": the question for the ✅ "Electron + 2. On the two suggested fixesRunner host → the shipped console
Recommended instead: the one-line change already attached to #8775 — -setTokenDefaultDaclGrant(api, restrictedToken, this.tempWriteSidPtr ?? this.writeSidPtr ?? worldSid)
+setTokenDefaultDaclGrant(api, restrictedToken, logonSid)
3. Diagnostics asks — confirmations from these runs
Upstream status at the time of writing
Patch (inline, self-contained)diff --git a/packages/sandbox/sandbox-windows-acl/src/index.ts b/packages/sandbox/sandbox-windows-acl/src/index.ts
index 5de5d47..4fc7746 100644
--- a/packages/sandbox/sandbox-windows-acl/src/index.ts
+++ b/packages/sandbox/sandbox-windows-acl/src/index.ts
@@ -290,18 +290,21 @@ export class AclSandbox {
restrictTokenIntegrity(api, restrictedToken, lowLabelSid)
this.token = restrictedToken
// The restricted token's default DACL still names only the user's
- // ambient SIDs — none of the restricting SIDs. Every NEW object the
+ // ambient SIDs — none of the capability SIDs. Every NEW object the
// confined process creates (anonymous stdio pipes, sync objects) takes
- // its DACL from that default, so the write pass-2 check would deny
- // pipe creation (ERROR_ACCESS_DENIED; Node EPERM) and break every
- // piped-stdio grandchild spawn. Merge a full-access ACE for a
- // restricting SID (the PRIVATE temp SID when present, otherwise the
- // workspace SID, or Everyone under read-only): new-object creation
- // stays gated by the parent object's DACL, while the new object's own
- // DACL passes pass-2. Choosing the temp SID prevents default-DACL
- // objects in one session's temp tree from acquiring the shared
- // workspace capability.
- setTokenDefaultDaclGrant(api, restrictedToken, this.tempWriteSidPtr ?? this.writeSidPtr ?? worldSid)
+ // its DACL from that default, and such an object must pass BOTH access
+ // checks: pass 1 against the token's own enabled SIDs, and pass 2
+ // (WRITE_RESTRICTED) against the restricting list. An ACE naming a
+ // capability SID satisfies pass 2 only, so the confined process cannot
+ // write an object it just created and dies during early CRT
+ // initialization (STATUS_DLL_INIT_FAILED / 0xC0000142; reproduced on
+ // Win11 22635 with cmd.exe, powershell.exe and pwsh). The logon SID is
+ // the SID that satisfies both: an enabled group of the token (pass 1)
+ // that the restricting list already carries in either mode (pass 2).
+ // Session-scoped, it is also narrower than Everyone — which is what
+ // read-only effectively uses — and it never propagates the shared
+ // workspace or temp capability into default-DACL objects.
+ setTokenDefaultDaclGrant(api, restrictedToken, logonSid)
if (api.closeHandle(currentToken) === 0) throwLastError(api, 'CloseHandle', 'current process token')
currentTokenOpen = false
this.api = api
diff --git a/packages/sandbox/sandbox-windows-acl/src/token.ts b/packages/sandbox/sandbox-windows-acl/src/token.ts
index d78bac0..79f9afe 100644
--- a/packages/sandbox/sandbox-windows-acl/src/token.ts
+++ b/packages/sandbox/sandbox-windows-acl/src/token.ts
@@ -100,11 +100,15 @@ export function makeWellKnownSid(api: Win32Bindings, type: number): NativePtr {
* default DACL verbatim, which names no restricting SID: a new anonymous pipe
* (child stdio) therefore fails the write pass-2 check at creation
* (ERROR_ACCESS_DENIED; Node surfaces it as spawn EPERM), breaking every
- * piped-stdio grandchild spawn. The merged ACE names a RESTRICTING SID (the
- * write SID under workspace-write, Everyone under read-only), so each new
- * object's own DACL passes pass-2 while object creation itself stays gated by
- * the parent container's DACL (files outside the granted trees remain
- * uncreatable). Fails closed: any Win32 failure throws before the spawn.
+ * piped-stdio grandchild spawn. The merged ACE must name a SID that passes
+ * BOTH checks: an enabled group of the token itself (pass 1) and a member of
+ * the restricting list (pass 2). A capability SID satisfies pass 2 alone, and
+ * the confined process then cannot write the objects it creates — it dies in
+ * early CRT initialization with STATUS_DLL_INIT_FAILED. The logon SID is the
+ * SID that satisfies both, so each new object's own DACL passes pass-2 while
+ * object creation itself stays gated by the parent container's DACL (files
+ * outside the granted trees remain uncreatable). Fails closed: any Win32
+ * failure throws before the spawn.
* @param api - the binding table.
* @param token - the restricted token to adjust (requires TOKEN_ADJUST_DEFAULT).
* @param sidPtr - the restricting SID whose full-access ACE joins the default DACL.4. Limits
|
Uh oh!
There was an error while loading. Please reload this page.
Summary
On the Windows desktop build, every shell tool invocation (pwsh, and even
cmd /c ...)fails instantly with the constant error
Error: PTY shell exited during startupwhilethe sandbox mode is
workspace-write. Nothing in the command, path, encoding, or cwdchanges the outcome — the process never lives long enough to execute anything. Setting
DSH_PERMISSION_MODE=danger-full-access(per-session or via the user environmentvariable on a fresh session) completely fixes it, which also pinpoints the failing layer.
Environment
@deepseek-ai/dsh 0.2.0-rc.2resolvePwshPath()falls back to Windows PowerShell 5.1Reproduction
Fresh session on the desktop build (default sandbox mode =
workspace-write).Invoke the shell tool with any command — e.g.
echo hello,Get-Content <file>,cmd /c dir C:\.Every call returns immediately:
No exit code, no stderr, no resolved shell path is surfaced (see "Suggestions").
Fix A — set user env var
DSH_PERMISSION_MODE=danger-full-access, fully restart theapp, open a new session → shell tools work.
Fix B — in an existing broken session, switch the permission preset back to
danger-full-access→ shell tools immediately work again (session events confirmsandbox/modeswitching back).Note the session-level override semantics: a session whose event stream records
sandbox/mode: workspace-writestays broken even after the app is restarted withDSH_PERMISSION_MODE=danger-full-accessin the host process environment (verified byreading the host process environment block directly). This makes the bug look
intermittent to users, but it is deterministic per (session mode × sandbox runner path).
Root-cause analysis (source-level, verified locally)
Chain: the persistent shell session goes through
dsh-terminal-bash's PTY(node-pty / ConPTY). When
sandboxPolicy.mode != danger-full-access, the tool argv iswrapped by
sandbox.confine()into the windows-acl runner:In the desktop host,
process.execPathisDeepSeek Harness.exe— a GUI-subsystemElectron binary run with ELECTRON_RUN_AS_NODE. Three-way control experiment on this
machine:
node.exe(console subsystem)session_exit)So the sandbox runner itself is functional; the failure is the GUI-subsystem host +
ConPTY combination (an inner console child spawned by a GUI-subsystem parent gets no
console). It likely affects every Windows desktop user while the default
workspace-writesandbox is on.Workaround currently in use:
DSH_PERMISSION_MODE=danger-full-access(accepting thedisabled file sandbox as a trade-off).
Suggestions
process to a fresh console, or avoid ConPTY wrapping of the runner child, or ship a
console-subsystem helper binary as the sandbox host).
lines of stderr, and the resolved shell absolute path. With the current constant
one-liner there is nothing to distinguish "shell binary missing" from "sandbox
runner killed the child", and users cannot self-diagnose.
Happy to provide more traces (node-pty repro scripts, runner logs) if useful.
中文摘要(可选读)
Windows 桌面版 0.2.0-rc.2 在默认
workspace-write沙箱档位下,所有 shell 工具(pwsh / cmd)启动即死,报错恒定为
PTY shell exited during startup,无退出码、无stderr、无 shell 路径。根因:windows-acl 沙箱 runner 以 GUI 子系统的 Electron
(RunAsNode)为宿主、外层再套 ConPTY 时,内层 console 子进程拿不到控制台即死;纯
node 宿主同一 runner 同一 ConPTY 完全正常(三组对照实测)。
DSH_PERMISSION_MODE= danger-full-access可完全规避。另建议工具报错带上退出码 / stderr / 解析到的 shell绝对路径,便于用户自查。
All reactions