Replies: 3 comments
你的机制与包内文档吻合——而且文档自己承认了那个"精确跳过"的优化1. 三处文档依据(都指向你说的那条链)
⇒ 把你的推理对上去:标签( 2. 建议把"精确跳过"这条单独点出来
3. 你这条与同族报告的合并价值同一天另有报告指 standing Low 标签会让工作区内其他工具无法写入(#7846),而 README 4. 请补一条(把"静默"变成可复现)你说"这个故障是静默的、自查代价高"。建议给出一条最小判定命令:在工作区一个已存在的子目录里写一个文件(应当失败),再在授权后新建的目录里写一个(应当成功)——同一台机器、同一会话、两次对比。这比让维护者自己设计实验有力得多。 5. 版本提醒
一条边界我确认的是包内文档对"三样一起写、精确跳过、内核按标签拒绝 write-up"的描述(这足以支撑你的机制)。你机器上的实际 DACL/SACL 状态以你实测为准——我没有在 Windows 上跑沙箱授权。 |
|
补一组量化数据,给「修复成本」一个可比量级(Windows 11 / NTFS / 非提权;0.1.7-rc.2 与 0.2.0-rc.1 行为一致,我在两版上都复现过)。 被测工作区:
补充两条实测细节,可能对定位有用:
建议(如果打算修):(a) provisioning 后做一次写入自检 —— 根 / 既有子目录 / 新建子目录各写一次,失败就显式报错,而不是静默退化成"只能写根";(b) 提供一条官方的一次性修复路径(逐对象 WRITE_OWNER + 逐对象标签 + 恢复根标签),或在文档里把"工作区里不要放 scratch/构建产物"写成硬要求;(c) 文档里"标签会急切传播全树""大工作区数十秒"这两句与实测差距较大,建议按 0.27 ms/对象 的量级修正。 |
|
补两组证据,都指向"结构性、跨版本不变"。 1. 代码级:
|
| 版本 | 文件 | hasExactLabel |
三连判定(grant + deny + label 全中即跳过整条写路径) |
|---|---|---|---|
| 0.1.7-rc.1 | lib/types-DxezulnA.js |
:439-446 |
:590 |
| 0.2.0-rc.1 | lib/types-Cl_DXjhk.js |
:444-451 |
:595 |
两版注释是同一句:"True when the label ACL already carries the EXACT label this module would add (mandatory-label ACE, OI|CI inheritance, no-write-up policy, the Low SID), so a re-grant can skip the eager full-tree propagation."
⇒ 这坐实了 PerryLink 指出的那点:"再授权一次"永远救不回来,而且这个形状从 0.1.7 到最新版没有变过。默认 grant 只在根上做一次 SetNamedSecurityInfoW,而标签(SYSTEM_MANDATORY_LABEL_ACE)在 SACL —— 不会向既有子对象追溯,与本票诊断一致。
2. 0.2.0-rc.1 的官方新动作:diagnose-windows-sandbox-acl
release note 里有:
The built-in Windows sandbox includes a permission-diagnosis skill that identifies supported causes of access denials and provides backed-up, recoverable permission repairs within a permitted directory. by @Elevator14B
对应代码:ACL_DIAGNOSIS_SKILL = "diagnose-windows-sandbox-acl"(lib/types-Cl_DXjhk.js:1071,registerAclDiagnosisSkill 在 :1090),资产在 assets/diagnose-windows-sandbox-acl/(SKILL.md 6.9 KB + 41.6 KB 的 .ps1,内含 S-1-16-4096、TOKEN_MANDATORY_LABEL、recovery 记录与 restore 流程)。
它回应的是"提供可回滚的一次性修复路径"这个方向;但没有改本票的根因 —— 授权仍只在根上写一次,hasExactLabel 仍让再授权走跳过分支。所以对已经存在的大工作区,它解决的是"诊断 + 补救",不是"下一次 provisioning 不再产生无标签子树"。
3. 一组中间规模的本机数据
| 项 | 值 |
|---|---|
| 工作区 | 既有工作区:66,923 文件 / 14,082 目录 / 2.2 GB |
| 根与两个既存子目录的完整性标签 | Mandatory Label\Low Mandatory Level:(OI)(CI)(NW),不带 (I)(= 显式写入,不是继承而来) |
| 授权后新建对象 | 正常继承标签 |
⇒ 在 6.7 万对象这一档,"把 Low 标签按 (OI)(CI) 显式补到目录上"是可行路径,成本远低于 7.66M 那档(34 分钟起步 / icacls 约 3 小时)。
边界说明:我没有在这个工作区根上重跑一次"内容有变化"的 SetNamedSecurityInfoW(避免改动它的 ACL),所以这里不给单次写入耗时;那个量级以 huangsijun17 的数据为准。
4. 建议把诉求合并成三句
- provisioning 后做写入自检:根 / 既存子目录 / 新建子目录各写一次,失败显式报错,而不是静默退化成"只能写根";
- 明确"补标签"是唯一修复路径(逐对象
WRITE_OWNER+ 标签,或按可继承 ACE 分层补),因为exact-label skip让"再授权"永远无效; - 修正文档中"标签会急切传播全树""大工作区数十秒"的说法,按实测量级(≈0.27 ms/对象)标注。
Uh oh!
There was an error while loading. Please reload this page.
摘要
Windows 上,
workspace-write的授权是对「工作区根目录」的一次SetNamedSecurityInfoW调用,一次写入三样东西:能力 SID 允许 ACE、FILE_DELETE_CHILD环境性拒绝项、以及 Low 完整性标签。Windows 会把可继承的 DACL ACE 传播到已有的子对象,但不会传播完整性标签(
SYSTEM_MANDATORY_LABEL_ACE位于 SACL)。再加上grantWrite有「精确 ACE 已存在就跳过重新传播」的优化,于是任何在首次授权之前就存在的目录,永远不会有标签。后果:
workspace-write下,受限子进程能写工作区根、以及授权之后新建的树,但写不了任何既有目录,也写不了它们下面的任何东西——未标记对象按 Medium 完整性计算,Low 令牌写它就是 MIC 的 write-up,被内核直接拒绝。这个故障是静默的,且极易被误读成「沙箱根本没给 workspace-write」,自查代价非常高(见「为什么难以自查」)。
环境
@deepseek-ai/dsh-sandbox-windows-acl@0.1.7-rc.1@deepseek-ai/dsh@0.1.7-rc.1(打包版deepseek-harness-pkg@0.1.7-alpha.2)<workspace>(既有目录,约 5 万文件 / 1.7 GB)复现步骤
.tmp、docs等)。workspace-write调用即可),或直接用公开 API 复现:期望 vs 实际
期望(据 README:「在
workspace-write下,子进程可以写入工作区及其私有临时目录」):实际:
证据
以下三次写入使用同一个受限令牌(
workspace-write),在同一次进程内完成:<workspace>\_p2.txt(工作区根)Low Mandatory Level:(OI)(CI)(NW)(显式)<workspace>\_lowtest\a\b.txt(授权之后新建的树)<workspace>\.tmp\_p1.txt(既有目录)<workspace>\.tmp\_newdir\x.txt(无标签既有目录下新建的目录)DACL 一侧是正确的,缺的只有标签:
根上的能力 SID 与 seam 为该工作区算出的 SID 完全一致:
从提升的 shell 给该既有目录补上标签后,同一个受限令牌立刻写入成功——证明是因果关系而非巧合:
根因
一次调用,只作用于根。 授权只落在被授权目录自身。
lib/types-DxezulnA.jsbuildLowLabelAcl()(约 L420)构造那一条(OI)(CI)Low 标签 ACE;mergeAndApply()(约 L465)把合并后的 DACL 与标签放在同一次setNamedSecurityInfoW(path, …, kind = 20, …, labelAcl)中应用(约 L480)。没有任何代码遍历后代。
Windows 不传播标签。 可继承的 DACL ACE 确实到达了已有子对象(在
<workspace>\.tmp上可见为(I)ACE),但完整性标签没有:标签继承发生在对象创建时,不会在祖先 SACL 变更时追溯应用。所以<workspace>\.tmp有 ACE 却没有标签。复用优化让它永久化。
hasExactEntry()(约 L506)/hasExactLabel()(约 L446)只针对被授权目录自身求值,而dsh-sandbox-local/lib/index.js的materializeAclGrant()(约 L392,注释写明 "once per provider lifetime")把「精确 ACE 已存在」当作「授权已完成」——于是缺失的标签在任何后续会话或重启中都不会被重新审视。这也与
README.zh.mdL177 的表述不符(「授权物化是急切的全树传播……会立即遍历每个后代」):该传播只对 DACL 那一面成立,对标签那一面不成立。另外,
dispose()明确保留常驻工作区授权,因此这个「半应用」状态按设计就是常驻的——不存在任何会修复它的代码路径。为什么难以自查
EACCES(git clone、New-Item),不是沙箱自己给出的提示。workspace-write都会静默退化为「只能写根 + 新建的树」——而这恰好就是人们真正在用的那批工作区。建议修复
以下任一条都能修好,第一条改动最小:
在物化常驻工作区授权时传播标签。 根授权应用后,遍历既有树,对每个缺少精确标签的后代补上标签——模块里已经有
hasExactLabel()可用于判定。(只处理目录不够:既有的文件也需要标签,否则「就地修改」会被拒而「新建文件」却能成功。)或者绑定
TreeSetNamedSecurityInfoW(advapi32,未文档化但存在),用它做标签编辑,一次调用遍历整个子树。不要把「精确 ACE 已存在」当作「授权已完成」。 后代的标签状态属于授权完整性的一部分;可考虑在传播完成后记一个每工作区的标记(例如 DSH 状态目录里),让这次遍历每台机器/每个工作区只付一次,而不是每次启动都付。
在修好之前,把绕过方式写进文档,因为这个失败模式太难归因:
之后新对象会继承标签,所以修复是持久的——但它需要为每个工作区根开一个提升的 shell,而这恰恰是沙箱本应避免的事。
临时绕过
提升权限,每个工作区根一次(之后新对象自动继承):
副作用是该后端已记录在案的既有代价(
README.zh.mdL121):该树会变成「同一用户下任何处于 Low 完整性的进程都可写」。All reactions