The durable sandbox/acl-session event carried a workspace binding that
always equals the session cwd and a random temp path that only needed
to be stable per session. Both are now derived: the temp subdirectory
is sha256(session id + workspace), created exclusively and removed on
provider dispose, so fork/resume semantics fall out of the derivation
and the record, its fold/provision/tamper validation, the immediate
flush kick, and the session-store dependency all disappear.
CreateNamedPipeW with NULL security attributes does not install a kernel
template: KernelBase builds the documented 5-ACE default SD in user mode
and passes it down; the kernel itself (a raw SD-null create) applies the
token default DACL. Correct the claim in both README sides and the
runner.spec pin comment, and link the MS template documentation.
New objects created without an explicit security descriptor take
their DACL from the token's default DACL, which CreateRestrictedToken
builds from the user's ambient SIDs — none of them a restricting SID.
Confined children therefore failed the write pass-2 check when
creating anonymous pipes (CreatePipe: ERROR_ACCESS_DENIED, surfaced
as Node EPERM), breaking PowerShell pipelines and other CreatePipe
consumers. Merge a full-access write-SID ACE (Everyone under
read-only) into the token default DACL at init via
SetTokenInformation(TokenDefaultDacl).
Named pipes are EXEMPT: their default security descriptor is the
kernel's PUBLIC template (owner/SYSTEM/Admins full, Everyone
read-only), which no token change influences, so libuv's piped stdio
capture stays denied for confined grandchildren — the POC-documented
boundary, now pinned by the runner suite (inherit/ignore OK, pipe
DENIED) and documented in the README pair. The NUL paragraph is
corrected to the measured matrix (Everyone has 0x1201BF on the
device: cmd/node writes land; Set-Content fails at the PS layer).
The per-session random write SID forced a full tree propagation per
session per server lifetime (minutes on large workspaces). The write
SID is now the per-workspace identity derived from the canonical
workspace path (workspaceWriteSid: sha256 -> S-1-4-x-y), stored
nowhere: the workspace-root ACE materializes once per workspace per
machine and every later provision hits the exact-ACE skip.
- workspace ACEs are STANDING (never revoked - the reuse cache); temp
ACEs stay revocable (disposed with the provider), so an inheritable
ACE never outlives its session's temp dir on the ambient temp root
- AclSandbox requires the write SID under workspace-write; read-only
parses/grants nothing; the runner derives the SID itself (the
--write-sid flag's presence still marks the seam-managed contract)
- the acl-session record drops writeSid (sessionId/workspace/tempDir
remain): the SID-tamper surface and its validation are gone
- sandbox-local holds two grant maps: standing workspace grants and
revocable per-session temp grants
Docs (README pair, design note pair, catalogs, type-equiv) and the
acl-session/grant/acl/probe/runner suites updated; workspace-sid.spec
pins the derivation contract.
Durable record: bound to the owning session id and validated at the fold (orphan-SID shape, temp path inside the host temp root) — a fork's copied parent record no longer provisions the child, and a tampered record fails loud. Private temp dir: random unguessable name persisted in the record, created exclusively (pre-existing entries and reparse points fail EEXIST). Persistence: a fresh provision kicks an immediate flush (no write-behind debounce), narrowing the crash window to the flush latency — documented as the one self-healing gap. Runner-failure rules: exit-gated on 127 so a confined command that prints the signature on a non-127 exit is never misclassified. Spawn: AssignProcessToJobObject failure terminates the suspended child (no hanging orphans). SandboxExecutionPolicy.sessionId is the branded SessionId. Boundary docs: qualifying clause on the absolutist sentences, NULL-DACL Known Limitation, 'full' scoped to the supported NTFS surface, CLM gate comment.
workspace-write now runs [logon SID, Everyone, orphan]: the two lists differ only by the orphan, and the keep-alive group (logon SID + Everyone) is the single shared invariant. The WMI namespace security check fails in BOTH modes (0x80041003), so CIM/Get-ComputerInfo are unavailable everywhere — the price of closing the C:\\-root tree-creation escape (AU:(AD) + AU:(OI)(CI)(IO)(M)) in workspace-write too. The unused WinLocalSid/WinInteractiveSid/WinAuthenticatedUserSid constants and their ABI-probe prints are removed; the enforcement 'full' claim now stands on a closed NTFS surface. New regression: a C:\\Users\\Public subdirectory write is denied under BOTH modes (the ambient-writable blind spot the review flagged — INTERACTIVE is absent from both lists). FAT-class (non-ACL) targets outside the granted roots remain writable (no security descriptors to intersect) — documented as a legacy residue, warn-only, not engineered around. Docs/design note/PR body updated in both languages (list I/J terminology gone everywhere).
A session that materialized its workspace-write grant and then switched to read-only (or crashed and resumed under read-only) kept a writable workspace for the server lifetime: the standing orphan-SID ACE survived the downgrade and list I still carried the orphan, so the write-restricted pass-2 check granted it. List I is now [logon SID, EVERYONE] only — the standing ACE stays inert under read-only while the unrevoked ACE keeps the re-upgrade free (map hit, no re-propagation). Pinned by the runner's real-token mode-downgrade regression (standing ACE denied under read-only, effective again on re-upgrade), the acl-session mode-switch cycle (nothing under read-only, one materialization on upgrade, no revoke on downgrade), and ConstrainedLanguage pins in both modes.
Known Limitations gains the per-command DACL mutation entry (inheritance is lazy, not a per-file walk; per-session grant reuse is deferred work); the design note gains a Testing section stating why the win32-only roster flip cannot ride macOS/Linux snapshot fixtures and naming the substitute evidence.
Add the implemented Agent Note owning the win32 rung decision: mxc rejected (OS floor 24H2, BaseContainer tier only on 25H2+ with the OS feature enabled, and arbitrary-path reads demand wholesale host DACL writes), AppContainer rejected (no ambient read access — arbitrary-path reads unsupported), landstrip already rejected and AppContainer-shaped. Link the note from the backend README pair and restate the choice in the PR description.
JSDoc every sandbox-windows-acl export (the 138 verify-export-jsdoc violations); fix the sandbox-windows-acl README code block (undeclared workspaceRoot), bring its stale claims current (the provider wiring and kill-on-close are live), add the canonical Model Experience sections to both new packages, audited the backend's indirect Model Experience sentence, and regenerate docs/config-catalog.md.
Confine Windows command execution through a WRITE_RESTRICTED token whose restricting SIDs carry an orphan-SID write allowlist, ported from https://github.com/huoyaoyuan/windows-acl-restrict-poc (@ 10e4dfb). Every Win32 call is checked and fails closed - the POC silently ran children with the FULL token when CreateRestrictedToken failed.
- @deepseek-ai/dsh-sandbox-windows-acl: koffi primitives verified against the MinGW Windows headers (verify/abi-probe.cpp) plus the confinement runner ([node, runner, --workspace, --temp, --mode, --, argv...]: kill-on-close job, stdio passthrough, exit-code mirroring, windows-acl-run: failure signature, grant revocation). read-only = strict zero grants (NUL device not writable; documented). Windows-only execution: exempted from the Linux coverage lane (windowsOnlyCoverageExclusions).
- @deepseek-ai/dsh-sandbox-local: PLATFORM_CHAINS.win32 filled with the windows-acl runner (full enforcement, ACL denial dialect, runner-failure rules).
- @deepseek-ai/dsh-pwsh-sandbox: sandbox-consuming pwsh executor (call-for-call mirror of dsh-bash-sandbox) over a new argv-level seam in dsh-pwsh-local; per-file coverage complete via the fake-provider spec.
- bundle/base: the Windows platform layer mounts the confined pwsh roster - sandbox/policy/fs-sandbox/permission/approval re-enabled, the POSIX bash stack stays disabled.
Co-authored-by: Huo Yaoyuan <huoyaoyuan@hotmail.com>