Replies: 1 comment
|
This is the most complete mechanism chain in the family, and I can confirm each link from the shipped source (
What I shipped, and what I deliberately did not. I maintain It ships no removal command. Removing an integrity label needs One correction to your own text, offered because it is the kind of thing that gets requoted: you write that the failure "cannot be reproduced any more" on your machine while the mechanism is confirmed at every link. That is the strongest form this report can take — a mechanism verified per link is more useful than a repro that only worked once, and the reproduction steps you did record ( npm: |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
碳基话:
在从0.2-rc1升级到0.2-rc2时发现发现eslint一直在报错, 无法移除temp下的临时目录. 而我因为写插件图方便将harness源码目录导入进了harness. 导致无法升级,
因为之前的sanbox问题我手动改过完全访问权限, 我以为这个问题, 遂还原无用. 使用其他agent排查老半天终于解决了. 方案简单来说就是给完整性标签全部覆盖回去.
硅基话:
摘要
在 Windows 上以
workspace-write模式执行受限命令时,@deepseek-ai/dsh-sandbox-windows-acl会向工作区根写入三条常驻的安全描述符改动,其中一条是可继承(OI|CI)的 Low 完整性标签(S-1-16-4096+NO_WRITE_UP)。SetNamedSecurityInfoW会立即把可继承 ACE 整树传播,因此该标签也会落在node_modules内指向 pnpm 内容寻址 store 的硬链接上;NTFS 硬链接共享同一个安全描述符,标签于是被写到 store 的共享文件对象上并在那里常驻。此后从该 store 硬链出来的可执行文件(
esbuild.exe、lefthook.exe…)会带上 Low 标签。按 Windows 强制完整性规则,进程以min(用户完整性, 镜像文件完整性)创建,因此这些程序以 Low 完整性进程运行,无法删除 Medium 完整性的临时文件/钩子文件,于是vite build、pnpm install等与 DSH 无关的流程持续失败,且官方自带的diagnose-windows-sandbox-acl技能按设计无法移除完整性标签。当前状态(重要):报告人机器上的故障已通过手工清理消失,本文投稿时无法再复现该故障;但机制链条的每一环都已在源码、仓库自带测试、官方文档与本机实测中得到独立确认(见「证据」与附录)。
环境
D:\.pnpm-store\v11(与工程同卷,packageImportMethod为默认 hardlink)D:\work\DS-Harness\deepseek-harnessD:\work\DS-Harness(仓库位于其子目录内)0.2.0-rc.2639ed015397290b3745d163aafe02ffee4aa3f84现象
pnpm run build在apps/web的vite build阶段失败:每次构建在
%TEMP%留下一个约 1.5 MB 的esbuild-<hex>残留文件。pnpm install的根postinstall失败:两者是同一类故障:一个以 Low 完整性运行的可执行文件(esbuild / lefthook)删不掉 Medium 完整性的文件。
最小复现
准备 Windows + pnpm 工程,pnpm store 与工程在同一卷(hardlink 生效)。
以
workspace-write模式、工作区根设为该工程(或含该工程的父目录)执行一次受限命令,使AclSandboxprovision 工作区 grant。之后任何时刻:
对 >1 MB 输入调用 esbuild transform(等价于 Vite 对大 chunk 的
vite:esbuild-transpile)即稳定失败:根因
1. grant 写入三条改动,其中第三条是可继承的 Low 标签(已确认)
workspace-write的 grant 在一次SetNamedSecurityInfoW调用里应用三条改动(src/acl.ts):S-1-4-x-y:(OI)(CI)(W,D,DC);FILE_DELETE_CHILD拒绝项Everyone:(CI)(DENY)(DC)(仅容器继承);S-1-16-4096+SYSTEM_MANDATORY_LABEL_NO_WRITE_UP+(OI|CI)。第 3 条可继承,会整树传播(README 自述 "eager full-tree propagation")。
2. 硬链接让标签落到树外的共享文件对象(已确认)
工作区树的
node_modules含大量指向D:\.pnpm-store的硬链接。NTFS 硬链接的两个名字指向同一个文件对象、共享同一个安全描述符,所以对链接路径设置安全描述符等于对 store 里的原对象设置。仓库自带测试已把这一行为钉死(tests/runner.spec.ts,用例名 "partial boundary: a workspace hard link lets the grant reach an external file object"),其注释也写明 "pnpm workspaces commonly contain hard links, so rejecting every multiply-linked file is not a viable profile"。结果:Low 标签被写到 store 的共享文件对象上(带继承标记
(I));store 目录自身没有标签,排除了"从 store 目录继承"的可能。3. 进程按
min(用户, 镜像)取得完整性(官方依据)因此带 Low 标签的
esbuild.exe/lefthook.exe被普通(Medium)用户启动时,进程实际以 Low 完整性运行。NO_WRITE_UP不允许它写入/删除 Medium 对象。4. esbuild 的具体失败路径(机制推断,行号已更正)
esbuild的临时文件是 Node 父进程以 Medium 完整性创建的(lib/main.js中randomFileName()=<tmpdir>/esbuild-<hex>),而执行清理的服务端是那个 Low 完整性的esbuild.exe;删除 Medium 文件触发NO_WRITE_UP拒绝,服务端报remove <path>: Access is denied.。注意:Node 侧的删除是静默失败的(
lib/main.js的fsSync.readFile对unlinkSync用了空 catch),因此能在用户面前冒出来的只有服务端那条错误,行号请勿再引lib/main.js:774(该行在 0.28.1 里是 entryPoints 解析)。残留文件恰好约等于输入体积(≈1.5 MB),与"父进程写入成功、子进程删除失败"一致。证据
已独立确认(本机实测)
icacls "<workspace-root>"曾出现S-1-4-<sid>:(OI)(CI)(W,D,DC)、Everyone:(CI)(DENY)(DC)、Mandatory Label\Low Mandatory Level:(OI)(CI)(NW)sha256(path)前 8 字节,每 4 字节 LE 取% 2^30-1再+1(src/workspace-sid.ts)。该值随路径变化,不随机器/用户变化fsutil hardlink list显示esbuild.exe→\.pnpm-store\v11\files\c0\ea92…-exec;lefthook.exe→\.pnpm-store\v11\files\b3\1d74…-exec*-exec:Low=0/40 且全部为显式 Medium;store 目录本身也是 Medium(I)Low不是继承自 store 目录)transform: OK, code bytes = 1275374,%TEMP%残留 0%TEMP%写→读→删 1.5 MB × 多次全部成功;提权后实测 Medium 进程可删除 Low 标签文件(ok=True)@esbuild+win32-x64@0.28.1、lefthook-windows-x64@2.1.9)未能复现(诚实声明)
GetTokenInformation(TokenIntegrityLevel)打在那个 esbuild 子进程上)没有取到:标签清除后才具备完整命令能力。E2/E3 与官方规则、仓库测试共同支撑该推断。pnpm run build已恢复正常,无法再给出失败日志的实时复现。影响
dispose()对工作区 grant 是 standing,永不撤销,官方诊断技能又明确保留完整性标签(README 的 Known Limitations 自述 "Diagnosis preserves integrity labels — it cannot fix Low-labeled executables affecting programs launched outside DSH")。这是报告人在自救过程中踩到的二级故障,请一并处理。
执行
icacls <workspace-root> /reset /T /C /Q之后,工作区根的 DACL 只剩从卷根继承的条目(原始输出):当前用户没有任何显式 ACE,而
Authenticated Users:(M)不含WRITE_OWNER。实测CreateFileW(WRITE_DAC | WRITE_OWNER):工作区根 DENIED win32=5、D:\.pnpm-storeDENIED(对照:仓库子目录与%TEMP%均为 OK)。而
grantWrite要求调用者拥有WRITE_OWNER(标签在 SACL;owner 的隐式权利只覆盖READ_CONTROL与WRITE_DAC—— 见src/acl.ts的 JSDoc)。于是此后每一条受限命令都在 provision 阶段失败:DSH 的 sandbox 是 fail-closed,因此命令根本不执行(连
Write-Output "ping"也一样),该会话里只剩下不经过沙箱的文件读写工具可用。这与"再跑一次受限命令只是让 grant 重新写入"完全不同。可自救的修复(用户此时仍持有
WRITE_DAC,无需提权):请一并澄清的文档事实:
icacls /reset不会清除 Low 标签——标签在 SACL,/reset只重建 DACL。真正移除标签的是icacls <path> /setintegritylevel Medium,而它需要WRITE_OWNER,普通权限下会Access is denied(本机实测 exit=5),必须提权执行。期望行为
WRITE_OWNER时,应在授权前明确报出"工作区需要 FullControl 而非 Modify",而不是让整个会话瘫痪;建议修复
GetFileInformationByHandle的nNumberOfLinks找出多链接对象,并判定其其它链接名是否在工作区内;命中树外链接时拒绝或降级。README 目前以 "rejecting multiply-linked files is not viable for ordinary pnpm installations" 为由未处理——但"拒绝多链接文件"与"探测并在命中树外链接时不施加可继承标签"是两回事,后者正是本 issue 的触发条件。revokeWrite只覆盖可撤销的 temp 授权),并提供清理命令;Dev Note 目前把"回收改名工作区残留 ACE 的清理命令"列为 undecided。diagnose-windows-sandbox-acl:使其能移除 standing 标签/ACE。当前该技能既不能移除标签,其自身文档也未提示这个盲区(只有包 README 的 Known Limitations 提到),用户按技能跑完不会被告知标签仍在。WRITE_OWNER时给出可执行的指引(例如提示执行上面的icacls /grant),避免 fail-closed 变成"整个会话不可用"。规避与恢复
日常规避:
danger-full-access,绕开 Windows ACL 后端;$env:npm_config_package_import_method='copy'后重装。相关代码与文档
packages/sandbox/sandbox-windows-acl/src/workspace-sid.ts(SID 推导:sha256前 8 字节)packages/sandbox/sandbox-windows-acl/src/acl.ts(三条改动的合并应用;grantWrite/revokeWrite;WRITE_OWNER要求见其 JSDoc)packages/sandbox/sandbox-windows-acl/src/token.ts(restrictTokenIntegrity:把受限 token 降到 Low)packages/sandbox/sandbox-windows-acl/src/grant.ts、src/index.ts(workspace grant 为 standing、dispose()不撤销)packages/sandbox/sandbox-windows-acl/README.md(硬链接边界、standing 编辑、Diagnosis preserves integrity labels、eager full-tree propagation)packages/sandbox/sandbox-windows-acl/tests/runner.spec.ts("a workspace hard link lets the grant reach an external file object").agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.md(Low 标签与 token 降权的设计取舍)packages/sandbox/sandbox-windows-acl/assets/diagnose-windows-sandbox-acl/SKILL.md("never … erase a deny, or replace child permissions recursively";以及 "A confined run of the script is never useful")min(用户, 镜像))需要复现 E2 的读者:对你自己的工作区根运行附录第 1 节的脚本即可,SID 必然随路径不同而变化——关键是"ACL 里那条 SID == 按该算法算出的值"这一一致性,而非某个固定数值。
附录:Windows ACL / 硬链接 / 完整性标签 证据复现手册
配套 issue:
issue-windows-acl-hardlink-store.md。所有命令均为只读,除非明确标注「会改动」。
示位符约定:
<workspace-root>= 报告人的工作区根(即仓库的父目录),下游只有一个pnpm工程;<repo>=<workspace-root>内的仓库目录(含node_modules)。0. 环境对照
观测:
→ 与 issue 环境表一致(同 commit 也挂在
refs/tags/dsh-v0.2.0-rc.2)。1. 工作区 SID 的独立复算(E2)
观测(报告人机器):
→ 复算结果与
icacls "<workspace-root>"输出里那条能力 SID 逐字一致。算法等价于src/workspace-sid.ts:sha256(canonicalPath)前 8 字节,每 4 字节 LE 取% (2^30-1)后+1。判据不是某个固定数值,而是一致性:ACL 里出现的那条
S-1-4-*必须等于按上式对工作区路径算出的值。读者换用自己的路径,数值必然不同。2. store 硬链接关系(E3)
观测(
<repo>段已替换):→ 两个可执行文件都是 store 的硬链接(同一文件对象的两个名字)。这里的
<repo>位于<workspace-root>之内,而 store 位于其之外——正是本 issue 的前提:grant 沿树传播时会顺着硬链接摸到树外的共享对象。3. 标签现状(清理后,E4)
观测:
单个样本(
icacls <store 文件>):→ 投稿时 store 没有 Low 标签。这是清理后的状态;它同时说明该标签可以被移除,且
(I)继承标记对应的传播源不可能是 store 目录(store 目录自己的标签不带 Low)。4. 工作区根的 DACL 与标签(原始输出)
icacls '<workspace-root>'观测(
/reset之后;仅首行路径被替换):关键点:
(I))。Authenticated Users:(I)(M)只给 Modify,不含WRITE_OWNER。(I)的显式 Medium,带(OI)(CI)—— 与icacls <path> /setintegritylevel的写法一致。对照:store 内的
esbuild.exe(同一个硬链接对象):5.
WRITE_DAC | WRITE_OWNER有效权限探测(本 issue 的二级故障)观测:
win32=5 即
ERROR_ACCESS_DENIED。grantWrite的标签写入需要WRITE_OWNER,因此工作区根上 provision 必然失败:(DSH 是 fail-closed,报此错时命令不会执行。)
6.
icacls /setintegritylevel的权限要求(会改动,仅新建的临时目录)观测:
在提权会话中同一操作
exit=0,并得到:→ 说明:设置/移除完整性标签需要
WRITE_OWNER;普通权限的工作区(只有 Modify)无法完成"清除标签"这一步,必须提权。7. 完整性策略的语义对照(提权,仅在
%TEMP%新建的子目录内)在一个全新
%TEMP%子目录里执行(脚本结束即删除):Mandatory Label\Low Mandatory Level:(NW)ok=True(删除成功)ok=True,新文件标签为Mandatory Label\Low Mandatory Level:(I)(NW)ok=True→ 强制完整性策略只禁止向上写(Low → Medium 被拒),不禁止向下写(Medium → Low 允许)。这正是"Low 进程删不掉 Medium 临时文件"与"用户自己能清理 Low 对象"能同时成立的原因。
8. esbuild 复现脚本(标签清除后:应当成功)
观测(标签已清除的状态):
→ 与 issue 中"标签在 =
remove … Access is denied.+ 约 1.5 MB 残留"形成对照;也说明故障与标签状态强相关。9. 关于
.acl-reports\acl-report-*.jsonl(既有诊断报告,请勿据此归因)工作区里存在一份由
diagnose-windows-sandbox-acl生成的报告。其调用者 token 记录为:{"kind":"observation","operation":"caller","status":"read","details":{"integrity":"S-1-16-4096 (Low)", "...":"..."}}随后对
C:\Users\<user>((OI)(CI)(F))等路径全部判定:{"writeDac":false,"writeOwner":false}并以
PRECONDITION结束、"fixed":0、"status":"skipped","reason":"No mutation mode was requested; all reported ACLs were left unchanged."这是受限会话里的假警报:脚本自己就是 Low 完整性 token,当然读不到 Medium 对象的
WRITE_DAC/WRITE_OWNER。SKILL.md 本身要求 "Run it unconfined… A confined run of the script is never useful"。此报告为只读 + skipped,未对 ACL 做任何改动——可据此排除"诊断技能改坏了 ACL"。10. 本次核验产生的临时文件
全部位于
<workspace-root>顶层,均可删除:.acl-verify*/.acl-final/.acl-snap/.acl-verify2/3/4(测试目录,已逐个清理).acl-verify-esbuild.cjs(第 8 节脚本,已删除)acl-verify-elevated.ps1+.log(提权测试,已删除)未改动 pnpm store 内容、未改动任何既有对象的安全描述符。
All reactions