DeepSeek Harness workspace-write 报 0xC0000142:默认 DACL 怎么排查
DeepSeek Harness 在 Windows 上切到 workspace-write 后,所有 shell 子进程都返回 0xC0000142,而 read-only 下同样的命令能跑——这类「只在 workspace-write 挂」的案例,根因通常在受限令牌的默认 DACL:workspace-write 把派生能力 SID 写进了 TokenDefaultDacl,read-only 写的是 world SID。 难点不在结论,而在证据:这两种模式同时改了两个部位,直接对比根本分不清是 restricting 列表还是默认 DACL。社区最后是靠「只覆盖默认 DACL」的单变量实验才钉死的(#11223、#11448)。
DeepSeek Harness 先做减法:把「两处同时变」拆成单变量
read-only 与 workspace-write 的差异不只在默认 DACL,还在 restricting SID 列表;只要两处一起变,任何对比都只能得到「workspace-write 有问题」,无法归因到具体字段。
先复现一个最小对照:
- 新建一个空目录作为工作区,里面只放一个
echo 1的测试脚本。 - 切到 read-only,执行
echo 1,确认能正常返回。 - 切到 workspace-write,执行同一条命令,确认返回
0xC0000142且无输出。 - 记录两次的完整退出码与时间,不要只看界面上的「失败」两个字。
接下来做单变量实验。核心思路是:让 DSH 按原样构造令牌,只在构造完成后覆盖 TokenDefaultDacl,其他字段一律不动。
- 用管理员权限打开一个调试终端,启动 DSH 并复现失败。
- 找到失败子进程的父进程,确认它使用的受限令牌。
- 只把该令牌的
TokenDefaultDacl换成S-1-5-11(Authenticated Users)或当前登录的 logon SID。 - 保持 restricting SID 列表、完整性级别、会话 ID 完全不变,再启动子进程。
- 如果这次能起来,根因就锁定在默认 DACL;如果仍然
0xC0000142,再回去查 restricting 列表或控制台。
这一步的价值在于排除法:只改一处、只观察一个结果,结论才站得住。社区最初也有「System32 上没有能力 SID 的读 ACE」的推测,后来正是用这个实验撤回了,因为真正起决定作用的是默认 DACL。
DSH插件 根因:默认 DACL 指名了派生能力 SID
sandbox-windows-acl/src/index.ts:304 的 setTokenDefaultDaclGrant(api, restrictedToken, this.tempWriteSidPtr ?? this.writeSidPtr ?? worldSid) 决定了默认 DACL 用哪个 SID;workspace-write 下这里落到派生能力 SID,read-only 下落 world SID。
把调用链展开:
- 进入 workspace-write,DSH 需要临时写权限,于是准备
tempWriteSid/writeSid。 - 构造受限令牌后,把默认 DACL 授权给这个能力 SID。
- 子进程创建对象时,默认继承这个 DACL。
- 但子进程在更早的 DLL 初始化阶段就要读取系统路径;能力 SID 在这些路径上没有读 ACE,初始化直接失败。
- 失败表现为
0xC0000142,因为进程还没走到执行命令的那一步。
代码上方的注释(index.ts:301-303)写明了这么做的目的:一个会话临时目录里创建的对象,不应该获得共享工作区的能力。 也就是说,能力 SID 不是写错了,而是有意为之;问题在于它同时被用在了「进程启动所需的默认 DACL」上,范围过窄,导致子进程连初始化都过不了。
DeepSeek Harness 为什么不能直接换成 world SID
把默认 DACL 换成 S-1-5-11 或 Everyone 确实能让进程起来,但等于拆掉了「临时目录对象不继承共享工作区能力」这层隔离;更合适的修法是把 logon SID 与能力 SID 并列,而不是替换。
三种候选的取舍可以这样看:
| 候选 SID | 子进程能否启动 | 隔离强度 | 结论 |
|---|---|---|---|
| 派生能力 SID(现状) | 不能 | 最强 | 需要补一个更宽的 SID |
S-1-5-11 / Everyone | 能 | 最弱,跨会话可见 | 只适合验证,不适合作为修复 |
| 当前登录 logon SID | 能 | 较强,跨会话仍隔离 | 推荐的修复方向 |
社区的建议是:在默认 DACL 里同时保留能力 SID 和 logon SID。这样临时目录对象仍然拿不到共享工作区能力,但子进程启动所需的基础读权限有了。findLogonSid 在同一包的 token.ts 里已经存在,index.ts:55 也已经导入,改动面并不大。
DeepSeek插件 用户侧能做什么:没有开关,只能换宿主或等修复
目前没有面向用户的配置项能改默认 DACL;能立刻恢复工作的只有换宿主或临时放开沙箱,保留 workspace-write 只能等核心修。
按代价从低到高:
- 改用
dsh web:Node 宿主不经过桌面端这条受限令牌链,workspace-write 下的命令通常能正常执行;代价是你要接受浏览器访问的工作方式。 - 临时切
danger-full-access:命令能跑,但沙箱隔离没了,只建议在可信项目里短期用。 - 换一台没有该问题的机器/账户:如果只是临时交付,可以把任务挪到 Linux 或 macOS,绕开 Windows 令牌差异。
- 等核心修复:关注默认 DACL 构造是否改为「能力 SID + logon SID」,不要指望 rc 线的小版本会带上。
如果你有源码环境,也可以自己打补丁:在 setTokenDefaultDaclGrant 调用处追加 logon SID,然后跑一遍 echo 1、文件写入、跨会话访问三组验证。只验证「命令能跑」不够,还要确认跨会话隔离没被破坏。
DeepSeek Harness 和另外两类 0xC0000142 的分界
默认 DACL 这条的判据是「只有 workspace-write 挂、read-only 正常、账户不是内置 Administrator」;三条都满足再往下查,不要一上来就改令牌。
| 判据 | 默认 DACL | 内置 Administrator | 控制台问题 |
|---|---|---|---|
| 账户 SID | 任意 | 以 -500 结尾 | 任意 |
| read-only 是否正常 | 正常 | 通常也挂 | 通常也挂 |
dsh web 是否正常 | 正常 | 正常 | 正常 |
| 关键证据 | 只改默认 DACL 即恢复 | 升级 alpha.2 即恢复 | 不新建控制台即恢复 |
先把这三条判据跑完,再决定要不要动令牌。一次改多个变量,最后只会得到「好像都好了一点」的模糊结论。
DSH插件 注意事项:做实验时的四个坑
下面这些做法会加重问题或掩盖真因,先避开再谈修复。
- 不要在生产会话里直接改令牌。用独立的测试工作区和测试账户,避免把正在跑的任务一起弄挂。
- 不要只验证
echo 1。还要验证写文件、读系统路径、跨会话访问,确认修复没有把隔离一起拆掉。 - 不要把
S-1-5-11当成最终方案。它只适合做单变量验证,范围太宽。 - 不要忽略版本差异。不同 rc 构建的
index.ts行号可能变化,定位时以函数名setTokenDefaultDaclGrant为准,不要死记行号。
这类问题的麻烦之处在于「看起来像权限不足,实际上是令牌构造过窄」。先用单变量实验把根因钉死,再决定是换宿主、等修复,还是自己补 logon SID;不要在账户、ACL、控制台三个方向之间来回试。相关排查见 DeepSeek Harness Windows 子进程报 0xC0000142:受限令牌与宿主定位。
常见问题
优先怀疑是 DeepSeek Harness 受限令牌的默认 DACL:workspace-write 下 setTokenDefaultDaclGrant 用的是派生能力 SID,read-only 用的是 world SID。能力 SID 在部分系统路径上没有读 ACE,子进程在 DLL 初始化阶段就失败,统一返回 0xC0000142。
因为 DeepSeek Harness 的默认 DACL 作用是限制「本次会话临时目录里新建的对象」不要拿到共享工作区能力。换成 S-1-5-11(Authenticated Users)范围太宽,等于把这层隔离拆掉。更合理的做法是把同一登录的 logon SID 与能力 SID 并列加入,既让子进程能起来,又保留跨会话隔离。
read-only 与 workspace-write 在 DeepSeek Harness 里同时改了两处:restricting 列表和默认 DACL,直接对比无法归因。需要保持 restricting 列表不变,只覆盖令牌的 TokenDefaultDacl,把它换成 logon SID 或 S-1-5-11 再启动子进程;只有这一处变、进程能起来,才能把根因钉在默认 DACL 上。
DeepSeek Harness 目前没有直接配置项能绕过这个 0xC0000142。可行的替代是改用 dsh web(Node 宿主,不走这条桌面令牌链),或临时切 danger-full-access。要保留 workspace-write,只能等核心修改默认 DACL 的构造方式,或在有源码的环境里自行打补丁。
先在 DeepSeek Harness 里看账户 SID 是否以 -500 结尾,排除 LUA_TOKEN;再看桌面端与 dsh web 是否表现不同,排除控制台创建标志;最后才做默认 DACL 的单变量覆盖。三步顺序不要颠倒,否则一次改多个变量,结果无法解释。
相关术语
- TokenDefaultDacl
- TokenDefaultDacl 是 Windows 访问令牌里的默认 DACL 字段,决定该令牌创建的对象的默认访问控制项。DeepSeek Harness 在 workspace-write 下把派生能力 SID 写进这里,用来阻止临时目录对象继承共享工作区能力。— https://github.com/deepseek-ai/deepseek-harness/discussions/11448
- 派生能力 SID
- 派生能力 SID 是 Windows 受限令牌中由 `CreateRestrictedToken` 生成的能力 SID(通常以 `S-1-4-` 开头),用于把访问权限收窄到特定能力集合。它不等同于用户或组 SID,在部分系统路径上没有读 ACE。— https://github.com/deepseek-ai/deepseek-harness/discussions/11223
- logon SID
- logon SID 是 Windows 登录会话 SID(`S-1-5-5-X-Y`),标识一次具体登录。它比 `Authenticated Users` 窄,又比派生能力 SID 更接近普通进程所需权限,因此常被用作默认 DACL 的最小修复候选。— https://github.com/deepseek-ai/deepseek-harness/discussions/11448
- restricting SID 列表
- restricting SID 列表是 `CreateRestrictedToken` 的 restricting SIDs 参数,定义受限令牌额外要求通过的访问检查。它与默认 DACL 是两个独立部位,排查时必须分开改动,否则无法判断是哪一处导致子进程失败。— https://github.com/deepseek-ai/deepseek-harness/discussions/11223
来源
- #11223 — Windows workspace-write: 0xC0000142 comes from the token default DACL· deepseek-ai(GitHub Discussions)
- #11448 — Windows 10: workspace-write sandbox kills every child process with 0xC0000142· deepseek-ai(GitHub Discussions)
- #11179 — workspace-write 沙箱让所有 shell 子进程以 0xC0000142 失败· deepseek-ai(GitHub Discussions)
- deepseek-ai/deepseek-harness(源码仓库)· GitHub