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
{{ message }}
Repository navigation
[Windows] ACL sandbox: workspace-write cannot start any child (0xC0000142); read-only works
#8534
Hi - reporting a reproducible defect in the Windows ACL sandbox backend. workspace-write
mode cannot start any child process, while read-only works normally. I traced it to the
token's default DACL and verified a fix; apologies if this is already known, I could not find
an existing report (Issues appear disabled, so posting here per CONTRIBUTING).
Summary
In workspace-write, the restricted token's default DACL is granted an ACE for a capability SID (S-1-4-...). That SID exists only in the token's restricting SID list,
never in its normal SID list. Windows performs two access checks for a WRITE_RESTRICTED
token (normal SIDs first, restricting SIDs second) and requires both to pass. Objects the
child creates without an explicit security descriptor - which the loader does during DLL
initialization - therefore fail at the first check. The child dies before reaching its entry
point with STATUS_DLL_INIT_FAILED (0xC0000142) and produces zero output.
read-only is unaffected because it grants the default DACL to Everyone (S-1-1-0), which
is present in both lists.
Environment
OS
Windows 11 Enterprise, 10.0.26200
Account
built-in Administrator, High IL (no UAC filtering)
Harness
0.2.0-rc.2 (desktop build) - the only version I tested
Affected package
@deepseek-ai/dsh-sandbox-windows-acl 0.2.0-rc.2
Also surfaces via
dsh-sandbox-local, dsh-pwsh-sandbox
Node (bundled)
v24.21.0
koffi
3.1.1
pwsh
7.6.6 (MSIX) - not interpreter-specific, see below
Impact
Once the sandbox is enabled, every ordinary shell command fails, including cmd /c exit 0.
The failure is silent (0xC0000142, zero bytes), which invites misattribution to the shell,
the interpreter, or the machine.
The readiness probe does not catch this.defaultProbeWindowsAcl() in dsh-sandbox-local
exercises --mode read-onlyonly. On an affected host the probe succeeds, the backend is
reported usable/partial, and then every real command fails.
Reproduction
Attached below is a standalone script using only the public AclSandbox API.
Critical detail - the defect is parent-process dependent.AclSandbox builds its restricted
token from the calling process's own token, so who calls init() decides the outcome:
Parent process
read-only
workspace-write
plain node.exe
OK
OK
Electron (ELECTRON_RUN_AS_NODE=1)
OK
CRASH 0xC0000142
Interleaved runs with fresh directories, 3 repetitions each: node always passes, Electron always
fails. Production runs the sandbox runner as the Electron binary (process.execPath in windowsAclRunnerArgv()), so production is always in the failing column. A test driven from node will not reproduce it - which is likely why this was missed.
# from a harness checkout, after install/build$env:ELECTRON_RUN_AS_NODE='1'&'C:\path\to\DeepSeek Harness.exe' repro-acl-workspace-write.mjs `
packages/sandbox/sandbox-windows-acl/lib/index.js `"$env:TEMP\_ws""$env:TEMP\_tmp"
Observed:
parent : ...\DeepSeek Harness.exe
mode : Electron (ELECTRON_RUN_AS_NODE=1) - the failing configuration
read-only cmd /c exit 0 -> OK
workspace-write cmd /c exit 0 -> CRASH 0xC0000142 (STATUS_DLL_INIT_FAILED)
BUG REPRODUCED: read-only starts a child, workspace-write cannot.
Expected: both modes start a child; under workspace-write the child additionally has write
access inside the granted workspace and remains denied outside it.
Root cause
The two-pass check, from createRestrictedToken() - capability SIDs are passed only as SidsToRestrict, never added to the token's normal SID list:
api.createRestrictedToken(currentToken,13,// DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED0,null,// SidsToDisable: none0,null,// PrivilegesToDelete: nonerestrictingSids.length/16,restrictingSids,// <-- capability SIDs live ONLY heretokenSlot,);
And the single default-DACL ACE, from setTokenDefaultDaclGrant():
Why workspace writes still work despite this: the granted directory's DACL carries the user's
inherited ACEs in addition to the capability ACE, so the directory itself passes both checks.
The default DACL has no such second entry - which is why only newly created objects break.
Fix
The default-DACL grant must name a SID present in both lists.
Option A - narrowest (my recommendation). Use the logon SID, already computed in init()
and already in the restricting list for both modes:
(A third option is adding the capability SID to the token's normal SIDs as well, so a
capability-only default DACL can satisfy pass 1. Larger semantic change; flagged rather than
recommended.)
Verification of the fix
Both options applied to an extracted copy of the module, re-run against the same repro:
cap (product) read-only OK workspace-write CRASH 0xC0000142
logon (Option A) read-only OK workspace-write OK
write inside workspace -> rc=0 file=true (expected)
write outside workspace -> rc=9 file=false (expected)
world (Option B) read-only OK workspace-write OK
write inside workspace -> rc=0 file=true (expected)
write outside workspace -> rc=9 file=false (expected)
The security boundary is preserved: the workspace stays writable, the escape probe stays denied.
The default DACL only governs objects created without an explicit descriptor; filesystem write
authority remains gated by the capability ACE on the granted trees.
Single-variable isolation (what is not the cause)
Hypothesis
Test
Result
capability SIDs in the restricting list
injected the same S-1-4-... SIDs into read-only's list
still PASS → not it
directory grants (grantWrite)
skipped them
still FAIL → not it
TMP/TEMP rewrite
skipped / redirected
still FAIL → not it
Low integrity label
read-only is also Low
PASS → not it
interpreter (Windows PowerShell 5.1)
cmd.exe fails identically; 5.1 passes under read-only
The runner itself is healthy in workspace-write: given a deliberately bad input it reports
cleanly (windows-acl-run: CreateProcessAsUserW failed (Win32 2), exit 127). The failure is
downstream, in the spawned child.
Open question (not reduced)
I could not reduce the parent-dependence to a single token field. The two parent tokens are
identical on everything I could inspect: integrity level, group list, AppContainer status,
capability count, and default-DACL ACEs. The default-DACL effect and the fix are established by
direct experiment; why Electron specifically triggers it is not. That likely needs a kernel
debugger, and I am flagging it rather than guessing.
Two adjacent observations (lower priority)
read-only + PowerShell emits a benign stderr line on every run: 尝试对 'FileSystem' 提供程序执行 InitializeDefaultDrives 操作失败。
Harmless, but it pollutes stderr and could confuse denial classification, since DENIAL_SIGNATURES matching is substring-based.
The package README documents that read-only starts PowerShell in ConstrainedLanguage
because the AppLocker probe cannot write to the temp directory. Worth cross-checking against
this defect - if that probe is itself affected in some configuration, the reported language
mode may be conservative rather than accurate.
Happy to provide the full isolation matrix, the exact build steps, or a patch if useful. Thanks!
repro-acl-workspace-write.mjs
#!/usr/bin/env node
/** * Minimal reproduction: Windows ACL sandbox "workspace-write" cannot start a child. * * Usage: * node repro-acl-workspace-write.mjs <path/to/sandbox-windows-acl/lib/index.js> <workspaceDir> <tempDir> * * In the harness repo the module path is: * packages/sandbox/sandbox-windows-acl/lib/index.js * * IMPORTANT - this defect is PARENT-PROCESS DEPENDENT. * AclSandbox derives its restricted token from the calling process's own token. * Under a plain `node` parent the same code PASSES; under the Electron binary that * hosts the sandbox runner in production it FAILS. So to reproduce, run this with: * * ELECTRON_RUN_AS_NODE=1 <path/to/DeepSeek Harness.exe> repro-acl-workspace-write.mjs ... * * Exit code: 0 when the bug reproduces, 2 when it does not (e.g. wrong parent). * * Uses throwaway directories: a workspace-write grant leaves a standing Low integrity * label and a capability ACE on the granted root, so do not point it at a real workspace. */import{mkdirSync,rmSync,existsSync}from'node:fs'import{join}from'node:path'const[,,libArg,wsArg,tmpArg]=process.argvif(!libArg||!wsArg||!tmpArg){console.error('usage: node repro-acl-workspace-write.mjs <acl lib/index.js> <workspaceDir> <tempDir>')process.exit(64)}constCMD=process.env.ComSpec||'C:\\Windows\\System32\\cmd.exe'constPWSH=process.env.PWSH||'pwsh'constSTATUS_DLL_INIT_FAILED=3221225794// 0xC0000142constelectron=process.env.ELECTRON_RUN_AS_NODE==='1'console.log(`parent : ${process.execPath}`)console.log(`mode : ${electron ? 'Electron (ELECTRON_RUN_AS_NODE=1) - the failing configuration' : 'plain node - expected to PASS even when affected'}`)const{ AclSandbox, workspaceWriteSid, tempWriteSid }=awaitimport('file:///'+libArg.replace(/\\/g,'/'))constws=wsArgconsttmp=tmpArgconstescape=join(tmp,'..','acl-escape-probe.txt')rmSync(ws,{recursive: true,force: true})rmSync(tmp,{recursive: true,force: true})rmSync(escape,{force: true})mkdirSync(ws,{recursive: true})mkdirSync(tmp,{recursive: true})constfmt=(code)=>code===0 ? 'OK'
: code===STATUS_DLL_INIT_FAILED ? 'CRASH 0xC0000142 (STATUS_DLL_INIT_FAILED)'
: `exit=${code}`asyncfunctionspawnChild(sb,command,args){constchild=sb.spawn({ command, args,stdio: 'inherit'})const{ exitCode }=awaitchild.wait()returnexitCode}constresults={}// --- 1. read-only: a child under a Low-integrity write-restricted token ---{constsb=newAclSandbox({writableDirs: [],tempDir: null,mode: 'read-only'})awaitsb.init()try{results.readOnly=awaitspawnChild(sb,CMD,['/c','exit','0'])console.log(`read-only cmd /c exit 0 -> ${fmt(results.readOnly)}`)}finally{sb.dispose()}}// --- 2. workspace-write: the same token, plus the capability write SIDs ---{constsb=newAclSandbox({writableDirs: [ws],tempDir: tmp,mode: 'workspace-write',writeSid: workspaceWriteSid(ws),tempWriteSid: tempWriteSid(tmp),})awaitsb.init()try{results.workspaceWrite=awaitspawnChild(sb,CMD,['/c','exit','0'])console.log(`workspace-write cmd /c exit 0 -> ${fmt(results.workspaceWrite)}`)// Only meaningful once the child starts: confirm the write boundary still holds.if(results.workspaceWrite===0){constinside=join(ws,'probe-inside.txt')constsIn=`try { Set-Content -LiteralPath '${inside}' -Value hi -ErrorAction Stop; exit 0 } catch { exit 9 }`constrcIn=awaitspawnChild(sb,PWSH,['-NoLogo','-NoProfile','-NonInteractive','-Command',sIn])console.log(` write inside workspace -> rc=${rcIn} file=${existsSync(inside)} (expect rc=0 file=True)`)constsOut=`try { Set-Content -LiteralPath '${escape}' -Value hi -ErrorAction Stop; exit 0 } catch { exit 9 }`constrcOut=awaitspawnChild(sb,PWSH,['-NoLogo','-NoProfile','-NonInteractive','-Command',sOut])console.log(` write outside workspace -> rc=${rcOut} file=${existsSync(escape)} (expect rc=9 file=False)`)}}finally{sb.dispose()}}console.log('')constreproduced=results.readOnly===0&&results.workspaceWrite===STATUS_DLL_INIT_FAILEDif(reproduced){console.log('BUG REPRODUCED: read-only starts a child, workspace-write cannot.')}elseif(!electron&&results.workspaceWrite===0){console.log('NOT reproduced - expected under a plain node parent. Re-run under the Electron')console.log('binary with ELECTRON_RUN_AS_NODE=1 (see the header of this file).')}else{console.log('NOT reproduced under this host/parent combination.')}process.exit(reproduced ? 0 : 2)
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Hi - reporting a reproducible defect in the Windows ACL sandbox backend.
workspace-writemode cannot start any child process, while
read-onlyworks normally. I traced it to thetoken's default DACL and verified a fix; apologies if this is already known, I could not find
an existing report (Issues appear disabled, so posting here per CONTRIBUTING).
Summary
In
workspace-write, the restricted token's default DACL is granted an ACE for acapability SID (
S-1-4-...). That SID exists only in the token's restricting SID list,never in its normal SID list. Windows performs two access checks for a
WRITE_RESTRICTEDtoken (normal SIDs first, restricting SIDs second) and requires both to pass. Objects the
child creates without an explicit security descriptor - which the loader does during DLL
initialization - therefore fail at the first check. The child dies before reaching its entry
point with
STATUS_DLL_INIT_FAILED(0xC0000142) and produces zero output.read-onlyis unaffected because it grants the default DACL to Everyone (S-1-1-0), whichis present in both lists.
Environment
Administrator, High IL (no UAC filtering)@deepseek-ai/dsh-sandbox-windows-acl0.2.0-rc.2dsh-sandbox-local,dsh-pwsh-sandboxImpact
Once the sandbox is enabled, every ordinary shell command fails, including
cmd /c exit 0.The failure is silent (
0xC0000142, zero bytes), which invites misattribution to the shell,the interpreter, or the machine.
The readiness probe does not catch this.
defaultProbeWindowsAcl()indsh-sandbox-localexercises
--mode read-onlyonly. On an affected host the probe succeeds, the backend isreported usable/
partial, and then every real command fails.Reproduction
Attached below is a standalone script using only the public
AclSandboxAPI.Critical detail - the defect is parent-process dependent.
AclSandboxbuilds its restrictedtoken from the calling process's own token, so who calls
init()decides the outcome:read-onlyworkspace-writenode.exeELECTRON_RUN_AS_NODE=1)Interleaved runs with fresh directories, 3 repetitions each: node always passes, Electron always
fails. Production runs the sandbox runner as the Electron binary (
process.execPathinwindowsAclRunnerArgv()), so production is always in the failing column. A test driven fromnodewill not reproduce it - which is likely why this was missed.Observed:
Expected: both modes start a child; under
workspace-writethe child additionally has writeaccess inside the granted workspace and remains denied outside it.
Root cause
The two-pass check, from
createRestrictedToken()- capability SIDs are passed only asSidsToRestrict, never added to the token's normal SID list:And the single default-DACL ACE, from
setTokenDefaultDaclGrant():read-onlyS-1-1-0workspace-writeS-1-4-...Why workspace writes still work despite this: the granted directory's DACL carries the user's
inherited ACEs in addition to the capability ACE, so the directory itself passes both checks.
The default DACL has no such second entry - which is why only newly created objects break.
Fix
The default-DACL grant must name a SID present in both lists.
Option A - narrowest (my recommendation). Use the logon SID, already computed in
init()and already in the restricting list for both modes:
Option B - mirror
read-only. Keep the capability SID and add Everyone:(A third option is adding the capability SID to the token's normal SIDs as well, so a
capability-only default DACL can satisfy pass 1. Larger semantic change; flagged rather than
recommended.)
Verification of the fix
Both options applied to an extracted copy of the module, re-run against the same repro:
The security boundary is preserved: the workspace stays writable, the escape probe stays denied.
The default DACL only governs objects created without an explicit descriptor; filesystem write
authority remains gated by the capability ACE on the granted trees.
Single-variable isolation (what is not the cause)
S-1-4-...SIDs intoread-only's listgrantWrite)read-onlyis also Lowcmd.exefails identically; 5.1 passes underread-only%TEMP%The runner itself is healthy in
workspace-write: given a deliberately bad input it reportscleanly (
windows-acl-run: CreateProcessAsUserW failed (Win32 2), exit 127). The failure isdownstream, in the spawned child.
Open question (not reduced)
I could not reduce the parent-dependence to a single token field. The two parent tokens are
identical on everything I could inspect: integrity level, group list, AppContainer status,
capability count, and default-DACL ACEs. The default-DACL effect and the fix are established by
direct experiment; why Electron specifically triggers it is not. That likely needs a kernel
debugger, and I am flagging it rather than guessing.
Two adjacent observations (lower priority)
read-only+ PowerShell emits a benign stderr line on every run:尝试对 'FileSystem' 提供程序执行 InitializeDefaultDrives 操作失败。Harmless, but it pollutes stderr and could confuse denial classification, since
DENIAL_SIGNATURESmatching is substring-based.read-onlystarts PowerShell inConstrainedLanguagebecause the AppLocker probe cannot write to the temp directory. Worth cross-checking against
this defect - if that probe is itself affected in some configuration, the reported language
mode may be conservative rather than accurate.
Happy to provide the full isolation matrix, the exact build steps, or a patch if useful. Thanks!
repro-acl-workspace-write.mjs
All reactions