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
windows-acl: ACL re-apply on private temp dir under C:\Windows\Temp strips the creator's access → runner fails with "--temp is not an existing directory" (all sandboxed commands unusable)
Summary
On Windows, when a user's TMP/TEMP environment variables resolve to the system temp dir (C:\Windows\Temp — e.g. a machine-level env var pointing there with no user-level override), every sandboxed command fails:
Error: sandbox mode "workspace-write" is requested but no sandbox backend is usable on this host;
refusing to run the command unconfined.
...
Runner failure: windows-acl-run: --temp is not an existing directory: C:\WINDOWS\TEMP\dsh-XXXXXX
The directory actually exists — it was created milliseconds earlier by the provider — but after the ACL grant step it becomes invisible to the creating user, so the runner's existsSync gate fails. The reported message is misleading: it is not a missing directory, it is a directory the creator can no longer access.
Non-elevated user account; HKLM\...\Session Manager\Environment has TMP=TEMP=C:\WINDOWS\TEMP; user-level (HKCU) TMP/TEMP empty → os.tmpdir() = C:\WINDOWS\TEMP.
Repro
Windows host where the user's TMP/TEMP resolves to C:\Windows\Temp (machine-level env var, no user-level override).
Run npx @deepseek-ai/dsh web as the non-elevated user.
Invoke any sandboxed tool (pwsh or bash).
Every sandboxed call fails with the error above; runner stderr is windows-acl-run: --temp is not an existing directory: C:\WINDOWS\TEMP\dsh-XXXXXX, exit code 127; the tool layer re-classifies this as SandboxUnavailableError ("no sandbox backend is usable on this host").
The temp dir C:\WINDOWS\TEMP\dsh-XXXXXXexists on disk (created by materializeAclGrant's mkdtempSync) yet existsSync from the same user returns false after the grant.
Root cause (verified by exercising the published package directly)
Provider materializeAclGrant does mkdtempSync(join(tmpdir(), "dsh-")), then AclWriteGrant.create(tempWriteSid(tempDir)) + grant.add(tempDir).
grant.add → grantWrite → mergeAndApply (SetEntriesInAclW + SetNamedSecurityInfoW) re-applies the freshly created directory's DACL, which at that point consists only of inherited ACEs.
Under the system temp dir whose DACL includes a CREATOR OWNER inherited ACE (standard C:\Windows\Temp shape), the re-apply leaves the directory with no usable ACE for the creator — not even the owner's implicit rights suffice (existsSync needs FILE_READ_ATTRIBUTES). The creator is fully locked out:
== child of C:\WINDOWS\TEMP, BEFORE grant ==
icacls: NT AUTHORITY\SYSTEM:(I)(OI)(CI)(S,RD)
BUILTIN\IIS_IUSRS:(I)(OI)(CI)(S,RD)
BUILTIN\Users:(I)(CI)(S,WD,AD,X)
BUILTIN\Administrators:(I)(F)
BUILTIN\Administrators:(I)(OI)(CI)(IO)(F)
NT AUTHORITY\SYSTEM:(I)(F)
NT AUTHORITY\SYSTEM:(I)(OI)(CI)(IO)(F)
<HOST>\<user>:(I)(F)
CREATOR OWNER:(I)(OI)(CI)(IO)(F)
existsSync=true R=true W=true X=true
== same dir, AFTER AclWriteGrant.create(tempWriteSid(dir)).add(dir) ==
existsSync=false R=false W=false X=false
icacls: Access is denied
Control — the same code path under C:\Users\<u>\AppData\Local\Temp is harmless: inherited ACEs are preserved, the capability ACE is added, access is unchanged:
AFTER grant (child of %LOCALAPPDATA%\Temp):
S-1-4-…:(OI)(CI)(W,D,DC)
NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
BUILTIN\Administrators:(I)(OI)(CI)(F)
<HOST>\<user>:(I)(OI)(CI)(F)
existsSync=true R=true W=true X=true
End-to-end confirmation: invoking the published runner directly with --temp under the user temp dir succeeds (workspace-write mode, child executes under the restricted token, stdio passes through, exit 0).
So the failure is specific to the private temp dir being created under a system temp dir whose DACL shape does not survive the merge-and-apply. The runner's requireDirectory gate then reports --temp is not an existing directory, and the tool layer surfaces "no sandbox backend is usable on this host".
Impact
Every sandboxed command on an affected host is permanently unusable (each fresh dsh-XXXX temp dir is broken identically), so the sandbox appears "always unavailable".
Workaround (verified on the affected machine)
Set a user-level TMP/TEMP to %LOCALAPPDATA%\Temp (HKCU overrides the machine-level value) and restart the dsh server. The sandbox then works end-to-end (verified with the real runner).
Suggested fixes
After the grant in materializeAclGrant / grant.add, verify the creator can still stat/access the directory, and fail with a clear error (e.g. "temp dir no longer accessible after ACL apply; choose a different temp root") instead of letting the runner fail with the misleading "not an existing directory".
Preserve inherited ACEs across the re-apply: when building the merged ACL, strip ACE_INHERITED from retained ACEs (materializing them as explicit entries) so SetEntriesInAclW + SetNamedSecurityInfoW does not lose them.
Choose the temp root more defensively: if tmpdir() points at a system dir and applying the ACL there locks the creator out, fall back to the user temp dir.
Notes
The package README documents the grant as a "standing directory modification" and notes "Granted directories must be owned by the caller. The sandbox relies on the owner's implicit WRITE_DAC." The observed behavior under a system temp dir goes beyond that documented boundary: the caller loses all access (including the ability to stat the directory it just created).
Repro requires no source checkout — it exercises the published package: AclWriteGrant.create(tempWriteSid(dir)) + grant.add(dir) on a fresh mkdtempSync child of the temp root, comparing existsSync/accessSync before and after.
New selfContainDacl in packages/sandbox/sandbox-windows-acl: materializes a private temp directory's inherited ACEs as explicit ones (inheritance bits preserved, ACE_INHERITED dropped) BEFORE the capability grant, so the grant's SetNamedSecurityInfoW re-apply cannot lose them to re-propagation — covering both the seam path (AclWriteGrant.add on a revocable path) and the agentless runner path (AclSandbox.init under manageDacls). Workspace roots are deliberately untouched (their inherited ACEs are the standing reuse cache).
Safety net: grantWrite re-verifies the creator's access after a real apply and throws the true cause instead of the runner's misleading "not an existing directory" (ENOENT is left to the caller).
Tests in packages/sandbox/sandbox-windows-acl/tests/self-contain.spec.ts pin the behavior; verified against the real C:\WINDOWS\TEMP on the affected machine (lockout reproduced on the published package, fixed path passes on source). All repo checks pass (typecheck, lint, translation pairing, agent-note format, export JSDoc, md links).
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.
windows-acl: ACL re-apply on private temp dir under C:\Windows\Temp strips the creator's access → runner fails with "--temp is not an existing directory" (all sandboxed commands unusable)
Summary
On Windows, when a user's
TMP/TEMPenvironment variables resolve to the system temp dir (C:\Windows\Temp— e.g. a machine-level env var pointing there with no user-level override), every sandboxed command fails:The directory actually exists — it was created milliseconds earlier by the provider — but after the ACL grant step it becomes invisible to the creating user, so the runner's
existsSyncgate fails. The reported message is misleading: it is not a missing directory, it is a directory the creator can no longer access.Environment
@deepseek-ai/dsh0.1.0-rc.6 (installed vianpx)@deepseek-ai/dsh-sandbox-windows-acl0.1.0-rc.6 (koffi-based restricted-token runner)HKLM\...\Session Manager\EnvironmenthasTMP=TEMP=C:\WINDOWS\TEMP; user-level (HKCU)TMP/TEMPempty →os.tmpdir()=C:\WINDOWS\TEMP.Repro
TMP/TEMPresolves toC:\Windows\Temp(machine-level env var, no user-level override).npx @deepseek-ai/dsh webas the non-elevated user.windows-acl-run: --temp is not an existing directory: C:\WINDOWS\TEMP\dsh-XXXXXX, exit code 127; the tool layer re-classifies this asSandboxUnavailableError("no sandbox backend is usable on this host").C:\WINDOWS\TEMP\dsh-XXXXXXexists on disk (created bymaterializeAclGrant'smkdtempSync) yetexistsSyncfrom the same user returnsfalseafter the grant.Root cause (verified by exercising the published package directly)
materializeAclGrantdoesmkdtempSync(join(tmpdir(), "dsh-")), thenAclWriteGrant.create(tempWriteSid(tempDir))+grant.add(tempDir).grant.add→grantWrite→mergeAndApply(SetEntriesInAclW+SetNamedSecurityInfoW) re-applies the freshly created directory's DACL, which at that point consists only of inherited ACEs.CREATOR OWNERinherited ACE (standardC:\Windows\Tempshape), the re-apply leaves the directory with no usable ACE for the creator — not even the owner's implicit rights suffice (existsSyncneedsFILE_READ_ATTRIBUTES). The creator is fully locked out:C:\Users\<u>\AppData\Local\Tempis harmless: inherited ACEs are preserved, the capability ACE is added, access is unchanged:--tempunder the user temp dir succeeds (workspace-writemode, child executes under the restricted token, stdio passes through, exit 0).So the failure is specific to the private temp dir being created under a system temp dir whose DACL shape does not survive the merge-and-apply. The runner's
requireDirectorygate then reports--temp is not an existing directory, and the tool layer surfaces "no sandbox backend is usable on this host".Impact
Every sandboxed command on an affected host is permanently unusable (each fresh
dsh-XXXXtemp dir is broken identically), so the sandbox appears "always unavailable".Workaround (verified on the affected machine)
Set a user-level
TMP/TEMPto%LOCALAPPDATA%\Temp(HKCU overrides the machine-level value) and restart the dsh server. The sandbox then works end-to-end (verified with the real runner).Suggested fixes
materializeAclGrant/grant.add, verify the creator can still stat/access the directory, and fail with a clear error (e.g. "temp dir no longer accessible after ACL apply; choose a different temp root") instead of letting the runner fail with the misleading "not an existing directory".ACE_INHERITEDfrom retained ACEs (materializing them as explicit entries) soSetEntriesInAclW+SetNamedSecurityInfoWdoes not lose them.tmpdir()points at a system dir and applying the ACL there locks the creator out, fall back to the user temp dir.Notes
AclWriteGrant.create(tempWriteSid(dir))+grant.add(dir)on a freshmkdtempSyncchild of the temp root, comparingexistsSync/accessSyncbefore and after.Fix available
A fix is ready in https://github.com/qzshch/deepseek-harness/tree/fix/sandbox-windows-acl-temp-dacl-self-contain (commit
70b05f8, PR-ready):selfContainDaclinpackages/sandbox/sandbox-windows-acl: materializes a private temp directory's inherited ACEs as explicit ones (inheritance bits preserved,ACE_INHERITEDdropped) BEFORE the capability grant, so the grant'sSetNamedSecurityInfoWre-apply cannot lose them to re-propagation — covering both the seam path (AclWriteGrant.addon a revocable path) and the agentless runner path (AclSandbox.initundermanageDacls). Workspace roots are deliberately untouched (their inherited ACEs are the standing reuse cache).grantWritere-verifies the creator's access after a real apply and throws the true cause instead of the runner's misleading "not an existing directory" (ENOENT is left to the caller).packages/sandbox/sandbox-windows-acl/tests/self-contain.spec.tspin the behavior; verified against the realC:\WINDOWS\TEMPon the affected machine (lockout reproduced on the published package, fixed path passes on source). All repo checks pass (typecheck,lint, translation pairing, agent-note format, export JSDoc, md links).All reactions