Replies: 1 comment
|
Thanks — this is the sharpest report in this family so far, and not because of the proposed fix. Two things make it worth more than its predecessors: it supplies a reproduction that needs no desktop ( Your four code citations verify, verbatim
What your report disproves in our own published text
Your appendix B measures that variable present in both hosts — including the one that works — and your production trace shows the runner running. Your token comparison ( Both statements were wrong, so 0.8.0 retracts them by name rather than silently rewriting them — a cause we shipped twice earns its sentence when it is withdrawn. Test arms now fail if either sentence comes back. The mechanism we now shipThe three-line trace from your appendix A is what we adopted: The single strongest piece of evidence in the report is the control you drew yourself in that same capture — under the same GUI-subsystem host, the harness's own unrestricted Plugin: 0.8.0
The advisory now:
Evidence for the release: 82/82 suite arms; 28 injected defects, 27 caught / 1 equivalent / 0 silent, none skipped; all five peer-range lines install, build and pass. Your report also caught a factual error of our own that C.3 implies: we had written the flag list as two sets and omitted Two notes on the fixes as written
Fourth report of this code, after |
Uh oh!
There was an error while loading. Please reload this page.
Windows ACL 沙箱:桌面版下所有受限控制台命令都以
0xC0000142(STATUS_DLL_INIT_FAILED)退出Summary
桌面版 Windows 上,
windows-acl沙箱(Windows 上唯一的沙箱后端)完全不可用:任何跑在它下面的命令都在真正执行之前就以3221225794(0xC0000142,STATUS_DLL_INIT_FAILED)退出,且没有任何输出。cmd、PowerShell 7(pwsh)、Windows PowerShell 5.1、Python 一律如此,read-only与workspace-write两种模式都一样;不受限的danger-full-access则一切正常,所以最初的表现像是"沙箱随机把 pwsh 干掉"——它和 pwsh 版本无关,也和解释器无关。文件类工具(read/write/edit/glob/grep)不受影响。我搜过讨论列表中有2起类似bug报告, 仓库也有看起来针对它的修复, 但更新到0.20rc1依然如此. 所以报告上来, 可能之前的fix覆盖不全.受影响的代码(都在本仓库内)
@deepseek-ai/dsh-sandbox-localpackages/sandbox/sandbox-localwindowsAclRunnerInvocation()用process.execPath托管 runner —— 一行修复就在此处@deepseek-ai/dsh-sandbox-windows-aclpackages/sandbox/sandbox-windows-aclSTATUS_DLL_INIT_FAILED@deepseek-ai/dsh-win32-processpackages/subprocess/win32-processcreateRestrictedProcess()—— 所有受限子进程创建的唯一收口,可移植修复落在这里并非 PowerShell 层的问题:
@deepseek-ai/dsh-pwsh-sandbox(packages/shell/pwsh-sandbox)只是把这个失败报出来而已。受限的cmd和python死法完全一样,所以缺陷在 pwsh 接缝之下,pwsh 相关包不需要改动。Reproduction
在桌面版应用内(用户可见的症状):保持默认的
read-only或workspace-write沙箱,让 agent 执行任意控制台命令——例如pwsh -Command '"hi"'。每次都以3221225794失败且无输出;把同一条命令切到danger-full-access就立刻成功。0.17rc2和0.20rc1均有此现象.最小复现(原版 Node + 随包发布的 runner,完全不需要 Electron):
spawnSync(process.execPath, [runner, ...]),其中runner是@deepseek-ai/dsh-sandbox-windows-acl/lib/runner.js:实测输出:
detached: true(DETACHED_PROCESS)在这里并不是什么偏门配置——它恰恰是 GUI 子系统父进程免费提供的东西,而桌面版正是用 GUI 子系统二进制来托管 runner 的:dsh-sandbox-local/lib/index.js——windowsAclRunnerInvocation()返回[process.execPath, builtEntry];紧邻的注释还写着 "The prefix stays[node, runner, ...]",也就是说这段代码默认process.execPath就是一个 Node 可执行文件。process.execPath是 Electron 二进制(…\DeepSeek Harness.exe)而不是node.exe。而且同一个构建是故意把它当 Node 宿主用的:dsh-desktop-host启动包管理器子进程时用的就是command: process.execPath、env: { ELECTRON_RUN_AS_NODE: "1", DSH_DESKTOP_NODE_EXECUTABLE: process.execPath, … }。于是 ACL runner 实际以
DeepSeek Harness.exe <runner.js> …的身份运行 —— 一个永远不可能拥有控制台的 GUI 子系统进程。Current behavior
任何受限控制台命令都返回
3221225794/0xC0000142,stdout 与 stderr 均为空。runner 自身可以证明是活着的、也无责:给它一个不存在的
--temp,它照样会打印windows-acl-run: --temp is not an existing directory: …并以127退出 —— attached 和 detached 两种情况下都是如此。挂在那个受限孙进程上的是子进程,不是 runner 自己的启动。同一个 runner、同一套 argv、同一个 workspace/temp、同一个令牌、同一台机器,只有宿主二进制 / spawn 标志在变:
cmd /c exitpwsh -c exitnode.exe000node.exewindowsHide: true(CREATE_NO_WINDOW)0node.exedetached: true(DETACHED_PROCESS)0xC00001420xC00001420DeepSeek Harness.exe(GUI 子系统)0xC00001420xC00001420用到的 GUI 子系统目标是
DeepSeek Harness.exe -e "process.exit(0)"(各种配置下都退出0)和wscript.exe //B(退出1—— 一个真实的应用程序退出码,说明它初始化并跑完了)。由此可以读出三条:
CREATE_NO_WINDOW—— 后者仍会创建一个无窗口控制台)能把控制台传下去;没有控制台的 runner(DETACHED_PROCESS,或任何 GUI 子系统宿主)不能。这与该后端自己 README 里记录的已知限制一致(原文:"console isolation is unavailable — children share the host console (CREATE_NO_WINDOW / CREATE_NEW_CONSOLE children die with STATUS_DLL_INIT_FAILED)"):在受限令牌下,任何控制台的创建都会失败,而桌面版恰好为每一条受限控制台命令都强制走了"创建控制台"这条路。README 那句"子进程共享宿主控制台"的假设,只在宿主确实有控制台时成立 —— GUI 子系统的 Electron 宿主永远没有。这一点已在内核层面确认(见附录 A):失败时
conhost.exe是被受限子进程自己创建出来的,带的是受限令牌,并以STATUS_ACCESS_DENIED结束。Expected behavior
受限命令应当和不受限时一样正常执行并返回输出,只是多一层写限制。具体地说:
cmd /c exit→ 退出码0pwsh -c "Write-Output …"→ 退出码0,文本出现在 stdout附录 C 里的两个 runner 侧修复,都是在故障配置(无控制台 runner)下实测达到上述结果的,且不丢输出。
Environment
@deepseek-ai/dsh-desktop 0.2.0-rc.1(与 npmnext一致)19044.4529,运行于移动云电脑(China Mobile cloud desktop / Ecloud VDI)客户机内cmd.exe、pwsh 7.6.5、powershell 5.1、CPythonD:\Apps\Node22\node.exev22.23.2 对比随包发行的DeepSeek Harness.exe(Electron;其内嵌 Node 自报 v24.18.1,且process.execPath就是该二进制)node.exe%USERPROFILE%\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe—— v24.21.0,本桌面版已经随包发行的运行时除 Windows Defender 外没有第三方 AV/EDR。该客户机会向所有进程注入一个 VDI 钩子 DLL,但已排除(见附录 B)。
这是否只是某一台机器的问题?
应当不是。触发条件是桌面版给每一个安装都固定下来的属性 —— ACL runner 由 GUI 子系统二进制托管,而这种宿主永远不可能拥有控制台 —— 并且该机制与 VDI、注入的钩子 DLL、杀软、解释器统统无关(附录 B)。上面
detached: true那条复现用的是原版 Node,不需要上述任何环境,所以机制本身在任何机器上都可复现;桌面版的症状也应当在该构建的任何 Windows 安装上复现。但要说明:本文所有测量都是在移动云电脑(China Mobile cloud desktop / Ecloud VDI)客户机内完成的。如果桌面版症状在普通工作站上复现不出来,请到这类云电脑环境里再试 —— 那是本报告确认问题的环境 —— 同时始终保留
detached: true那条 runner 用例,作为与机器无关的机制判据。附录 A — 根本原因与内核级证据
Process Monitor抓取了桌面版发出的两组用例;两组里的受限目标都是同一个唯一命名的
cmd.exe副本(dsh_conhost_probe.exe),因此这些事件不可能与系统噪音混淆。过滤器:Process Create/Process Exit。用例 1 —— runner 由
node.exe托管(runner 拥有控制台):受限子进程退出码0整条链里没有出现任何
conhost.exe:受限子进程继承了 runner 已经拥有的那个控制台。用例 2 —— 同一个 runner、同一套 argv,但以
DETACHED_PROCESS启动(runner 没有控制台):受限子进程死亡按顺序读下来,这就是整个缺陷:
conhost.exe的Process Create事件归属于受限子进程自身的原因。STATUS_ACCESS_DENIED死亡。STATUS_DLL_INIT_FAILED死亡 —— 也就是桌面版里每条受限命令看到的0xC0000142。所以这既不是令牌派生的问题,也不是加载器或目标自身 DLL 的问题:是控制台宿主在受限令牌下被拒绝,而桌面版把每一条受限控制台命令都送上了这条路。
同一次抓取里还有对照,可以排除"这台机器上控制台创建本身就是坏的":harness 自己那条不受限的
pwsh.exe(由dsh-subprocess-local的 runner 拉起)用同样的方式申请控制台 ———— 而这个
conhost.exe退出码是0。控制台创建对不受限客户端成功、只对受限客户端被拒,这正是一个无法施加到控制台宿主上的写受限低完整性令牌会产生的结果。抓取记录里还能看到生产环境的 spawn 模式本身,即桌面版确实是用
process.execPath(Electron 二进制)而不是node来托管 runner 的:(这条是
dsh-subprocess-local的 runner —— 不受限路径,所以上面那个conhost.exe才会成功;dsh-sandbox-local构造它的 windows-acl 前缀时用的是同一套process.execPath写法。)附录 B — 已排除的嫌疑项
下面这些在可用的(
node.exe宿主)和故障的(DeepSeek Harness.exe宿主)两种配置下完全一致:Get-ProcessMitigation -Id <pid>:无任何差异,全是默认值whoami /groups+/priv:完全一致(16 个组、相同 SID 含S-1-2-0/S-1-2-1/S-1-5-11、Mandatory Label\High Mandatory Level、29 行特权),会话1IsProcessInJob = true;两者OpenInputDesktop都成功(无JOB_OBJECT_UILIMIT_DESKTOP)WinSta0/DefaultELECTRON_RUN_AS_NODE在两个宿主里都存在(而 node 宿主照样能用)windowsHide/CREATE_NO_WINDOW、无控制台 runnerpwsh.exe失败方式相同;换cmd、python也相同cmd与pwsh7、5.1 死法完全一样cmd.exe副本运行正常;注入模块在成功的受限运行里同样存在--temp时 runner 会打印自己的错误并以127退出,在 Electron 宿主下同样如此cmd.exe可以运行(LOWPROBE_OK,退出0)时间线备注:本机是 Windows 10
19044,而沙箱代码注释里提到的是在 Windows 1126200上验证过。该缺陷并不限于任一版本 —— 用原版 Node 加detached: true就能复现 —— 但 Windows 10 用户看到的是桌面版的症状。附录 C — 在故障环境中验证通过的修复方案
C.1 用真正的
node.exe托管 ACL runner(推荐)把 runner 宿主解析为一个真正的 Node 可执行文件,而不是
process.execPath。本桌面运行时已经随包带了一个(%USERPROFILE%\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe)。配合生产环境本来就用的windowsHide: true,控制台子系统的node.exe会得到一个属于自己的无窗口控制台,受限子进程再继承它。Node 版本无所谓:上面的矩阵用的是 v22.23.2,下表用的是随包的 v24.21.0 —— 两者都是控制台子系统二进制,都能用:windowsHideDeepSeek Harness.exe(生产)false0xC0000142DeepSeek Harness.exe(生产)true0xC0000142node.exefalse0,stdoutFIX_OKnode.exetrue(生产选项)0,stdoutFIX_OK建议改动点:在
dsh-sandbox-local的windowsAclRunnerInvocation()里解析宿主(例如process.env.DSH_NODE/ 随包运行时,回退到process.execPath)。C.2 让 runner 自己申请一个控制台(可移植兜底)
如果不想去定位 Node 可执行文件,同样的效果可以落在所有受限子进程创建的唯一收口上(
dsh-win32-process的createRestrictedProcess):runner 没有控制台时,在 spawn 之前先申请一个。四个绑定加一个辅助函数:在无控制台的 runner(即故障条件)下实测,
createRestrictedProcess其余部分保持不变:ensureRunnerConsolecmd /c exit0xC00001420pwsh -c "Write-Output DETACH_FIX_OK"0xC00001420,stdout"DETACH_FIX_OK\r\n"cmd /c echo DETACH_CMD_OK0xC00001420,stdout"DETACH_CMD_OK\r\n"0000,行为不变 ——AllocConsole返回 0,分支根本不会进入这个变体输出保真,而且与宿主无关:不需要定位
node.exe,所以即便将来 runner 宿主因为别的原因改掉,它依然成立。需要权衡的地方:给dsh-win32-process增加了user32依赖和四个绑定;每个 runner 进程从此都持有一个控制台(一个 runner 一个控制台宿主);窗口只能在AllocConsole返回之后才隐藏,因此理论上有一瞬闪现 —— 而控制台子系统的node.exe宿主靠现有的windowsHide: true就能免费拿到真正无窗口的控制台。首选 C.1,C.2 作为可移植兜底。C.3 已实测并否决的方案:给受限子进程加
DETACHED_PROCESS既然根因是"受限子进程去申请控制台",那么子进程侧的修法就是让它以
DETACHED_PROCESS创建、根本不申请。我们把它打进随包的dsh-win32-process(子进程创建标志在管道路径上是0,见lib/index.js:458;在继承 Job 的路径上是4/CREATE_SUSPENDED,见lib/index.js:693),并在同一个无控制台 runner 下测量:DETACHED_PROCESScmd /c exit0xC00001420pwsh -c "Write-Output …"0xC00001420,但 stdout 与 stderr 都是 0 字节cmd /c echo DETACH_CMD_OK0,stdout"DETACH_CMD_OK\r\n"(完好)这个补丁反过来确认了机制 —— 去掉控制台申请,崩溃就消失 —— 但不能当作修复方案:在
DETACHED_PROCESS下,pwsh7 会静默地完全不产出输出(不是改道到 stderr,是真的被丢弃),那等于把响亮的失败换成最要命的解释器上的哑失败。cmd.exe的输出完好,说明这是解释器相关的行为。runner 侧的两个方案都保持输出完整 —— 受限pwsh的 stdout 被正常捕获为FIX_OK—— 这才是推荐它们的原因。附录 D — 建议的回归测试
detached: true用原版 Node 加随包 runner 就能复现,所以现有的 Windows ACL 测试套件无需 Electron 就能覆盖它:cmd /c exit→0detached: true托管,受限cmd /c exit→ 目前是0xC0000142;必须变成0CREATE_NEW_CONSOLE,受限cmd /c exit→ 目前是STATUS_DLL_INIT_FAILED(已在 README 记录);等到受限令牌下的控制台创建被修好或绕开之后,应当变成0附录 E — 各项测量的取得方式
<host> <pkg>/lib/runner.js --workspace <ws> --temp <已存在的目录> --mode workspace-write -- <目标 argv>(agentless 形式:不带
--write-sid/--temp-write-sid;runner 会自建并清理自己的私有临时子目录。)koffi调用kernel32!GetConsoleWindow、GetStdHandle、AllocConsole读出的。All reactions