Skip to content

fix(desktop): provide a console for Windows ACL children - #629

Open
zp-home wants to merge 1 commit into
anywhere-labs:masterfrom
zp-home:fix/528-windows-workspace-write-console-host
Open

fix(desktop): provide a console for Windows ACL children#629
zp-home wants to merge 1 commit into
anywhere-labs:masterfrom
zp-home:fix/528-windows-workspace-write-console-host

Conversation

@zp-home

@zp-home zp-home commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

中文

摘要

在不修改上游 DSH ACL 沙箱的前提下,修复 #528#305#516#439 所报告的 Windows Desktop workspace-write 故障。

Electron 图形宿主及其 Node 模式 ACL 跳板进程启动时没有控制台。在受影响的 Windows 环境中,受限控制台子进程因此被迫在受限令牌生效后自行创建控制台,随后在 cmd.exe、PowerShell 或其他控制台程序开始执行前,就在 DLL 初始化阶段以 0xC0000142STATUS_DLL_INIT_FAILED)退出。

本 PR 让经过精确校验的 Desktop 跳板进程在导入上游 ACL 运行器之前分配并隐藏控制台。受限子进程随后继承真实控制台,与拥有控制台的 CLI 宿主保持一致。

关联 Issue

Closes #528

Closes #305

Closes #516

Closes #439

相关替代方案 #266 会捆绑独立 Node 运行时,并说明 Windows 包体积约增加 87 MB。本 PR 保留现有 Electron RunAsNode 协议,只修复 #528 受控实验隔离出的控制台宿主差异,不会修改或关闭 #266

类型与平台

  • 问题修复
  • 文档
  • Windows 安装包
  • Windows 便携版 ZIP
  • macOS
  • Linux

复现

#528 记录的受影响环境:

  • DSH Desktop 2.0.2;2.0.1 同样受影响。
  • Windows 11 24H2,内部版本 26100.9168,x64。
  • 高完整性级别的 Electron 图形宿主。
  • 默认 workspace-write 模式。

复现步骤:

  1. 在 Windows 启动 DSH Desktop,保持会话为 workspace-write
  2. 执行任意命令,例如 Write-Output hicmd.exe /c echo hi
  3. 工具以无符号退出码 3221225794,即 0xC0000142 返回,标准输出与标准错误均为空。

#528 已记录的受控二分:

宿主 子进程控制台状态 结果
拥有控制台的 Node 子进程继承宿主控制台 成功且输出正常
没有控制台的 Electron 图形宿主 受限子进程自行创建控制台 在受影响机器上以 0xC0000142 退出
Electron 加 AttachConsole(-1) 子进程继承附加控制台 成功且输出正常
Electron 加 DETACHED_PROCESS 子进程没有控制台 不再崩溃,但 Windows PowerShell 5.1 标准输出不可用
Electron 宿主预先隐藏执行 AllocConsole() 子进程继承所分配的控制台 成功,输出与 ACL 边界均保持正常

本机复现限制

开发机为 Windows 11 10.0.26200.0、x64、中完整性级别(S-1-16-8192)、Electron 43.4.0。它确认 Electron 图形运行器启动时没有控制台,但未修复的 Desktop v2.0.1 与旧 master 都能由 Windows 为受限子进程创建控制台并正常退出。因此,本 PR 不会声称已经在本机自然复现原始崩溃码。

根因结论来自 #528 的受影响高完整性环境矩阵与受控宿主控制台二分。发布前仍需在受影响机器与真实打包产物上完成最终冒烟测试。

根因

上游 ACL 运行器有意省略 CREATE_NO_WINDOWCREATE_NEW_CONSOLE,因为任一标志都可能使受限子进程在 DLL 初始化阶段失败。CLI 路径之所以正常,是因为子进程会继承 CLI 宿主控制台。

失败顺序:

没有控制台的 Electron 运行器
  -> 受限子进程创建首个控制台
  -> 受影响 Windows 宿主在 DLL 初始化阶段失败(0xC0000142)

修复顺序:

没有控制台的 Electron 运行器
  -> 已校验且尚未受限的 Desktop 跳板进程调用 AllocConsole()
  -> 跳板进程隐藏控制台窗口
  -> 上游 ACL 运行器创建受限子进程
  -> 子进程继承控制台

修复

  • 新增 windows-console-host.ts,通过延迟加载的 Koffi 绑定调用 GetConsoleWindowAllocConsoleGetLastErrorShowWindow
  • 已有控制台时保持不变。
  • 仅在 Windows 且精确的 Desktop ACL 跳板进程没有控制台窗口时分配。
  • 可取得窗口句柄时使用 SW_HIDE 隐藏。
  • 分配失败时 fail closed,继续使用现有 windows-acl-run 签名与退出码 127。
  • 调用位置位于精确上游 runner 校验之后、上游模块导入之前。
  • 增加非 Windows、已有控制台、分配与隐藏、无窗口句柄、失败和调用顺序回归测试。
  • 同步更新中文与英文 README 和架构记录。

与最新 master 的合并

本分支已 rebase 到 master@681ba66091fc5b1e827650137f69b3ee4c435922。冲突来自最新 master 新增的 Windows ACL relay/ConPTY 路径。合并结果保留了 relay payload 解码、runner 身份校验、relay 环境清理和参数恢复,并在身份校验成功后、参数恢复与上游 import 前执行 ensureWindowsConsoleHost()

最终 head 为 c3ef012dfe2acf7e2cba02560715ee5663477c32,相对 master 为 ahead 1 / behind 0,GitHub 状态为 CLEAN / MERGEABLE

安全边界与副作用

以下行为没有改变:

  • 固定的 deepseek-harness 子模块与上游 @deepseek-ai/dsh-sandbox-windows-acl
  • 受限令牌构造、限制 SID、工作区与临时目录 ACE、ACL 清理、作业对象、标准输入输出和进程创建标志。
  • relay payload 的 runner 校验、环境清理与参数恢复。
  • danger-full-access、普通 Node、macOS 和 Linux 路径。
  • 不允许自动回退到不受限执行的策略。

失败与生命周期行为:

  • 非预期 runner 参数会在分配控制台前被拒绝。
  • AllocConsole() 失败时保持安全关闭,不会在缺少 confinement 的情况下执行。
  • 隐藏控制台只存在于短生命周期 ACL runner 和子进程期间,进程结束后由系统释放。
  • 每次受限命令会有少量控制台宿主开销。
  • 从分配到 SW_HIDE 之间理论上可能出现极短闪现;原生探针观察到继承的控制台处于隐藏状态。

验证

基于 master@681ba66 的 rebase 后验证:

  • corepack yarn install --immutable:通过,仅有仓库既有 peer dependency 警告。
  • 聚焦回归:1 个文件,20/20 通过。
  • corepack yarn workspace dsh-plugin-desktop check:win-package:13 个文件,230/230 通过。
  • runtime closure:201 个可达第一方节点。
  • corepack yarn check:layout:通过,上游子模块 b150a551b8 一致。
  • corepack yarn check:bilingual-docs:44 条记录、88 份文档一致。
  • corepack yarn typecheck:通过。
  • git diff --check origin/master...HEAD:通过。

首次运行 Windows package gate 时,工作树的安装态仍来自 rebase 前基线,最新 master 新增的 settings icon Yarn patch 尚未链接到 node_modules,因此 package marker 断言失败,其他 229/230 通过。执行 immutable install 后重跑,230/230 全部通过。

本次 rebase 后未重新运行完整 corepack yarn check 或真实安装包冒烟。旧 head 的远程 CI 曾全部通过;新 head 的 CI 由本次 push 重新触发。

既有原生验证证据

旧 head 在真实 Electron GUI runner 与真实 ACL runner 上得到以下结果;本次 rebase 未重跑该人工矩阵:

检查 结果
Electron 图形宿主在启动子进程前的控制台 不存在
cmd.exe /c echo 退出码 0,输出 ELECTRON_GUI_OK
Windows PowerShell 5.1 退出码 0,输出 ELECTRON_GUI_OK
受限子进程控制台 CHILD_CONSOLE_PRESENT
受限子进程控制台可见性 CHILD_CONSOLE_HIDDEN
工作区内写入 INSIDE_OK,文件内容成功写入
工作区外写入 OUTSIDE_DENIED,外部文件不存在

剩余发布门禁

合并或发布前,需要在 #528 的受影响高完整性 Windows 机器以及真实安装包或便携版产物上重跑矩阵。验收要求是:cmd 与 PowerShell 正常退出、标准输出和标准错误可捕获、没有可见控制台、工作区内写入成功、外部写入拒绝,并且没有 ACL 残留。

发布说明

Windows Desktop 的 workspace-write 现在会为受限控制台工具提供隐藏且可继承的控制台,从而避免受影响机器上的 0xC0000142 启动失败,同时不削弱 ACL confinement。用户不需要迁移或修改配置。


English

Summary

Fix the Windows Desktop workspace-write failure reported in #528, #305, #516, and #439 without changing the upstream DSH ACL sandbox.

The Electron GUI host and its Node-mode ACL trampoline start without a console. On affected Windows environments, the restricted console child is consequently forced to create its own console after the restricted token is active. DLL initialization then exits with 0xC0000142 (STATUS_DLL_INIT_FAILED) before cmd.exe, PowerShell, or another console program can run.

This PR makes the validated Desktop trampoline allocate and hide a console before importing the upstream ACL runner. The restricted child then inherits a real console, matching the console-hosted CLI assumption.

Related Issues

Closes #528

Closes #305

Closes #516

Closes #439

Related alternative #266 bundles an independent Node runtime and reports an approximately 87 MB Windows package increase. This PR keeps the existing Electron RunAsNode protocol and addresses the narrower console-host mismatch isolated by #528. It does not modify or close #266.

Type And Platforms

  • Bug fix
  • Documentation
  • Windows installer
  • Windows portable ZIP
  • macOS
  • Linux

Reproduction

Affected environment recorded by #528:

  • DSH Desktop 2.0.2; 2.0.1 also affected.
  • Windows 11 24H2 build 26100.9168, x64.
  • High-integrity Electron GUI host.
  • Default workspace-write mode.

Reproduction steps:

  1. Start DSH Desktop on Windows and keep the session in workspace-write.
  2. Run any shell command, for example Write-Output hi or cmd.exe /c echo hi.
  3. The tool returns unsigned exit code 3221225794 / 0xC0000142 with empty stdout and stderr.

Controlled bisection recorded in #528:

Host Child console state Result
Node with a console Child inherits the host console Succeeds and output works
Electron GUI without a console Restricted child creates a console 0xC0000142 on the affected host
Electron plus AttachConsole(-1) Child inherits the attached console Succeeds and output works
Electron plus DETACHED_PROCESS Child has no console Avoids the crash, but PowerShell 5.1 stdout is unusable
Electron plus hidden host-side AllocConsole() Child inherits the allocated console Succeeds; output and the ACL boundary remain intact

Local Reproduction Limit

The development machine is Windows 11 10.0.26200.0, x64, Medium integrity (S-1-16-8192), Electron 43.4.0. It confirms that the Electron GUI runner starts without a console, but both unpacked Desktop v2.0.1 and the old pre-fix master allowed Windows to create the restricted child's console and exited 0. This PR therefore does not claim that the original crash code was reproduced naturally on the development machine.

The root-cause claim relies on #528's affected high-integrity matrix and controlled host-console bisection. A final affected-host and packaged-artifact smoke remains required before release.

Root Cause

The upstream ACL runner intentionally omits CREATE_NO_WINDOW and CREATE_NEW_CONSOLE; either flag can make restricted children fail DLL initialization. This works for CLI because the child inherits the CLI host console.

Failure sequence:

consoleless Electron runner
  -> restricted child creates its first console
  -> affected Windows host fails DLL initialization (0xC0000142)

Repaired sequence:

consoleless Electron runner
  -> validated, unrestricted Desktop trampoline calls AllocConsole()
  -> trampoline hides the console window
  -> upstream ACL runner creates the restricted child
  -> child inherits the console

Fix

  • Add windows-console-host.ts with lazily loaded Koffi bindings for GetConsoleWindow, AllocConsole, GetLastError, and ShowWindow.
  • Leave an existing console unchanged.
  • Allocate only on Windows and only when the exact Desktop ACL trampoline has no console window.
  • Hide the allocated HWND with SW_HIDE when one is available.
  • Treat allocation failure as fatal and retain the existing windows-acl-run signature plus exit 127.
  • Call the adapter after exact upstream runner validation and before the upstream import.
  • Add regression coverage for non-Windows, existing-console, allocate/hide, no-HWND, failure, and call-order behavior.
  • Update English and Chinese README and architecture records together.

Integration With Current Master

The branch is rebased onto master@681ba66091fc5b1e827650137f69b3ee4c435922. The conflict came from the Windows ACL relay/ConPTY path added on current master. The resolution preserves relay payload decoding, runner identity validation, relay-environment cleanup, and argument restoration. It invokes ensureWindowsConsoleHost() after successful identity validation and before argument restoration and the upstream import.

The final head is c3ef012dfe2acf7e2cba02560715ee5663477c32. It is ahead 1 / behind 0, and GitHub reports CLEAN / MERGEABLE.

Security Boundary And Side Effects

The following behavior is unchanged:

  • The pinned deepseek-harness submodule and upstream @deepseek-ai/dsh-sandbox-windows-acl.
  • Restricted token construction, restricting SIDs, workspace/temp ACEs, ACL cleanup, job object, stdio, and process creation flags.
  • Relay runner validation, environment cleanup, and argument restoration.
  • danger-full-access, plain Node, macOS, and Linux paths.
  • The no-unrestricted-fallback policy.

Failure and lifecycle behavior:

  • Unexpected runner arguments are rejected before console allocation.
  • AllocConsole() failure fails closed instead of executing without confinement.
  • The hidden console exists only for the short-lived ACL runner and child lifetime and is released by process teardown.
  • There is small per-command console-host overhead.
  • A theoretical flash can exist between allocation and SW_HIDE; the native probe observed the inherited console as hidden.

Verification

Post-rebase verification on master@681ba66:

  • corepack yarn install --immutable: passed with existing peer dependency warnings only.
  • Focused regression: 1 file, 20/20 passed.
  • corepack yarn workspace dsh-plugin-desktop check:win-package: 13 files, 230/230 passed.
  • Runtime closure: 201 reachable first-party nodes.
  • corepack yarn check:layout: passed; upstream submodule b150a551b8 is consistent.
  • corepack yarn check:bilingual-docs: 44 records and 88 documents are consistent.
  • corepack yarn typecheck: passed.
  • git diff --check origin/master...HEAD: passed.

The first Windows package gate run used the pre-rebase installation state, so the settings icon Yarn patch added by current master was not linked into node_modules. Its package marker assertion failed while the other 229/230 tests passed. After the immutable install, the rerun passed 230/230.

The complete corepack yarn check and a real packaged-artifact smoke were not rerun after this rebase. Remote CI passed on the old head; the force-push triggers CI again for the new head.

Existing Native Evidence

The old head produced the following results with the real Electron GUI runner and real ACL runner. This manual matrix was not rerun after the rebase:

Check Result
Electron GUI host console before launch absent
cmd.exe /c echo Exit 0, ELECTRON_GUI_OK
Windows PowerShell 5.1 Exit 0, ELECTRON_GUI_OK
Restricted child console CHILD_CONSOLE_PRESENT
Restricted child console visibility CHILD_CONSOLE_HIDDEN
Workspace write INSIDE_OK, file content written
Outside-workspace write OUTSIDE_DENIED, no outside file

Remaining Release Gate

Before merge or release, rerun the #528 matrix on the affected high-integrity Windows host and a packaged installer or portable artifact. Acceptance requires normal cmd and PowerShell exit, captured stdout and stderr, no visible console, successful workspace writes, denied outside writes, and no ACL residue.

Release Notes

Windows Desktop sessions using workspace-write now provide restricted console tools with a hidden inherited console, avoiding the affected-host 0xC0000142 startup failure without weakening ACL confinement. No migration or configuration change is required.

@zp-home
zp-home force-pushed the fix/528-windows-workspace-write-console-host branch 4 times, most recently from 2d0374c to fb7427f Compare August 26, 2026 05:22
@zp-home
zp-home force-pushed the fix/528-windows-workspace-write-console-host branch from fb7427f to c3ef012 Compare August 26, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment