Repository navigation
[Bug] 工作区不可写问题可能原因:低完整性标签没有铺到已存在的子目录:同一个机制导致的三种故障 / workspace-write writes fail in every pre-existing subdirectory: the Low-integrity label is never applied to objects that already existed — one mechanism behind three failure modes #9416
Replies: 1 comment 1 reply
|
Confirmed from source at
Two notes on the suggested direction, from the same code:
The cross-report framing (one primitive, three failure modes: not retroactive → this denial; inherited onto new objects → For reference, the npm package |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Summary
On Windows in
workspace-writemode, the sandbox can write to the workspace root but not to any subdirectory that already existed when the sandbox provisioned it. Every write fails withAccess to the path '<dir>\_wtest' is denied.This breaks ordinary work, not just an edge case:
py_compile/mypy(which write__pycache__),git,node, and any build tool that writes inside a project directory all fail. Reproduced with a workspace whose project lives in subdirectories (D:\projects, 8 first-level subdirectories — all denied).The mechanism behind it is a persistent, inheritable
Mandatory Label\Low Mandatory Levelon the workspace root plus a Low-integrity sandbox token. Two other reports document the same mechanism failing in different directions (#9314: executables built in the workspace inherit Low IL and break outside the sandbox; #9407: the label breaks Explorer icons and persists after the session). A section below analyses all three together, because they share one root cause and cannot all be fixed by adjusting how the label is applied.Reproduction
0.2.0-rc.2, workspaceD:\projects, file policyworkspace-write.Access to the path 'D:\projects\python\_wtest' is denied.Probe over every first-level subdirectory (stop descending once one fails):
Ruled out (all verified):
FullControl(WRITE_DACandWRITE_OWNERboth true). Granting explicit Full Control changes nothing.diagnose-windows-sandbox-aclscanned 1085 objects in the subtree;PACKAGE_SOURCES=0.Everyone … (DENY)(DC)entry: documented as expected behaviour.Current behavior
D:\projectsAccess to the path ... is denied❌Downstream failures, same session:
python -m py_compile→ exit 1 with[Errno 13] Permission denied: '__pycache__\\game_stats.cpython-311.pyc';mypyfails the same way;gitreports access denied;node tools\align-tables.js <file under a subdirectory>→EPERM: operation not permitted.Expected behavior
In
workspace-write, the whole workspace subtree should be writable. A directory must not be excluded merely because it existed before the sandbox provisioned the workspace.Root cause (read from source + measured)
dsh-sandbox-windows-acldoes two things:grantWrite()→buildLowLabelAcl(), with(OI)(CI)inheritance.restrictTokenIntegrity()→SetTokenInformation(token, TokenIntegrityLevel = 25, S-1-16-4096).writableDirs: parsed.mode === "workspace-write" ? [parsed.workspace] : [](lib/runner.js).Under Windows Mandatory Integrity Control, a Low-integrity process can only write to objects whose label is Low. Two facts then collide:
(OI)(CI)affects only objects created afterwards.grantWrite()'s idempotence short-circuit inspects only the object being granted:hasExactLabel(oldAcl, lowLabelSidPtr). Its own source comment states: "so a re-grant can skip the eager full-tree propagation." Once the root carries the label, every later provision returns early, so pre-existing children are never labeled.Measured with the bundled diagnostic skill on the same tree (it reports
LOW_LABELper object):So: root = Low (writable by a Low token), pre-existing children = Medium (not writable). A newly created directory and its descendants inherit Low and are writable, which matches the asymmetry above.
Same mechanism, three failure modes (cross-report analysis)
%TEMP%/%LOCALAPPDATA%; rebuilding does not help; only a copy out of the workspace does#9407's statement that "the whole tree inherits the label" and my measurement that pre-existing children carry no label are not in conflict: both are explained by when an object was created relative to labelling. Objects created after labelling inherit Low (→ #9407, #9314); objects that existed before do not (→ the write denial above). One rule, two opposite outcomes, deterministic.
Under the DACL-only approach used by the 0.1.5-line package, none of these three would occur: a capability-SID allow entry grants writes without touching an attribute that outlives the session or follows files around.
Why this primitive is a poor fit for session-scoped write control (argued, not verified upstream)
0.1.5-line used)0.2.x)(OI)(CI)), repairableThe last row is the core problem: the label is load-bearing for the write grant, so there is no coherent release step. Revoke it → everything created during the session becomes unwritable again; keep it → the user's files stay Low indefinitely (#9407, #9314). A session-scoped capability should not be implemented with an attribute that must outlive the session to keep working.
A second-order consequence: because the token is lowered, every write the toolchain performs must land on a Low object, so the sandbox cannot simply declare "this tree is writable" — it must keep the label aligned with everything the tools create, which is precisely where the retroactivity gap and the exe leakage come from.
Suggested direction
ERROR_NONE_MAPPEDunless the*SIDform is used).0.1.5-line package appears to do); if Low-integrity isolation is wanted, confine it to a dedicated subtree (e.g.<workspace>\.dsh_sandbox\) that user content and build outputs never enter, and tie the label to that subtree's lifecycle.Access to the path … is denied, which reads as an ordinary permission problem (I spent a long time granting Full Control and repairing ACLs before finding the integrity label).User-side workaround (one-off)
After this, newly created directories inherit Low automatically, so it is a one-time fix. Measured after applying it: the same probe over 200 directories of the workspace returned OK for all of them;
py_compileexit 0;mypyreportsSuccess;nodecan write files under a subdirectory;gitruns.Notes on version history (please confirm from your side)
@deepseek-ai/dsh-sandbox-windows-acl@0.1.5-rc.2contains no integrity-level code at all (noLowLabel,integrity, orS-1-16matches in itslib/*.js; writes appear to be authorized by the capability SID's inheritable DACL grant alone), whereas0.2.0-rc.2introduces the label plus the token lowering. However, #9407 reports the label already being applied with runtime0.1.5-rc.2, so the exact version that introduced this behaviour should be confirmed upstream — I can only testify to the two package versions I compared locally.Environment
@deepseek-ai/dsh@0.2.0-rc.2(all sibling packages same version)@deepseek-ai/dsh-sandbox-windows-acl@0.2.0-rc.2@deepseek-ai/dsh-sandbox-windows-acl@0.1.5-rc.2workspace-writeD:\projects(owned by the signed-in user)Appendix: a separate, easily misdiagnosed symptom (not this bug)
Inside the sandboxed shell, invoking a program through a pipe / redirection (e.g. PowerShell
& exe ... 2>&1 | Out-String, or| Select-Object) makes process creation itself fail with程序'x'运行失败:拒绝访问("failed to run program … access denied"). This looks like "the sandbox blocks this executable", but the same executable runs fine without the pipe — it is a stdio-boundary restriction, unrelated to the integrity-label issue above. Recording it here so others do not chase the wrong cause.中文
摘要
Windows 上以
workspace-write模式运行时,沙箱能写工作区根目录,却写不进任何一个"在沙箱配置之前就已经存在"的子目录,每次写入都报Access to the path '<dir>\_wtest' is denied。这不是边角情况,而是普通干活就废:
py_compile/mypy(要写__pycache__)、git、node、以及任何往项目目录里落文件的构建工具全部失败。复现环境的工作区里,项目都放在子目录下(D:\projects,8 个一级子目录全部被拒)。背后的机制是:DSH 给工作区根打了一个持久、可继承的
Mandatory Label\Low Mandatory Level(低完整性强制标签),同时把沙箱子进程令牌降到 Low 完整性。另有两份报告记录了同一机制在另外两个方向上的故障(#9314:工作区里构建出的 exe 继承 Low 完整性,在沙箱之外运行时写不了%TEMP%; #9407:该标签让 Explorer 图标全变白纸,且会话结束后仍然存在)。下面有一节把三者放在一起分析,因为我认为它们同一个根因,而且不可能靠"调整打标签的方式"全部解决。复现步骤
Windows、DSH
0.2.0-rc.2、工作区D:\projects、文件策略workspace-write;找一个已经存在的子目录,让沙箱往里写:
结果:
Access to the path 'D:\projects\python\_wtest' is denied.对每个一级子目录做同一探测(失败即不再下探):
已排除的原因(均经验证):
FullControl(WRITE_DAC、WRITE_OWNER均为真);显式授予"完全控制"没有任何改变;diagnose-windows-sandbox-acl扫描子树 1085 个对象,PACKAGE_SOURCES=0;Everyone … (DENY)(DC):文档说明它是预期行为;实际行为
D:\projectsAccess to the path ... is denied❌同一次会话里的连带失败:
python -m py_compile→ 退出码 1,[Errno 13] Permission denied: '__pycache__\\game_stats.cpython-311.pyc';mypy同样失败;git报拒绝访问;node tools\align-tables.js <子目录下的文件>→EPERM: operation not permitted。期望行为
workspace-write模式下,工作区整棵子树都应可写;不能仅仅因为某个目录"在沙箱配置工作区之前就存在"就把它排除在外。根因(读源码 + 实测)
dsh-sandbox-windows-acl做两件事:grantWrite()→buildLowLabelAcl(),带(OI)(CI)继承;restrictTokenIntegrity()→SetTokenInformation(token, TokenIntegrityLevel = 25, S-1-16-4096);writableDirs: parsed.mode === "workspace-write" ? [parsed.workspace] : [](lib/runner.js)。按 Windows 强制完整性控制(MIC),低完整性进程只能写"标签为低完整性"的对象。于是两个事实相撞:
(OI)(CI)给根打标签,只影响之后新建的对象;grantWrite()的幂等短路只检查当前正在授权的那个对象:hasExactLabel(oldAcl, lowLabelSidPtr)。它自己的源码注释写着:"so a re-grant can skip the eager full-tree propagation." 根一旦带上标签,后续每次配置都会提前返回,所以已存在的子目录永远不会被补标。用内置诊断技能在同一棵树上实测(它逐对象报告
LOW_LABEL):即:根 = 低完整性(低完整性令牌可写),已存在的子目录 = 中完整性(不可写)。而新建的目录及其子目录会继承低完整性、因此可写——这与上面的不对称完全吻合。
同一机制,三种故障(跨报告分析)
%TEMP%/%LOCALAPPDATA%全部失败;重建无效,只有把项目复制出工作区才行#9407 说"整棵树都继承了标签",与"已存在的子目录没有标签"并不矛盾:两者都能用**"对象是在打标签之前还是之后创建的"**解释——之后创建的继承低完整性(→ #9407、#9314),之前已存在的不会(→ 本报告的写入被拒)。同一条规则,两种相反结果,而且是确定性的。
如果采用
0.1.5那条线的纯 DACL 方案,这三种故障都不会出现:能力 SID 的允许项就能授权写入,不必动一个"会活得比会话更久、还会跟着文件走"的属性。为什么这个原语不适合做"会话级写入控制"(论证,非上游确认)
0.1.5线似乎用的就是这个)0.2.x新增)(OI)(CI)),可修最后一行是核心问题:标签是这次写入授权的承重结构,于是根本不存在一个干净的"释放"步骤——撤销它 → 本次会话创建的东西又不可写;留着它 → 用户的文件永久停在低完整性(#9407、#9314)。一个会话级的授权,不该用一个"必须活得比会话久才能继续生效"的属性来实现。
二阶后果:因为令牌被降级,工具链的每一次写入都必须落在低完整性对象上,所以沙箱无法简单地声明"这棵树可写"——它必须让标签跟着工具创建的一切保持同步,而"不追溯"和"exe 继承"两个问题恰恰出在这里。
建议方向
ERROR_NONE_MAPPED,必须用*SID形式)。0.1.5线那个包一样);若确实需要低完整性隔离,就把它限制在一个专用子树(例如<workspace>\.dsh_sandbox\)里,让用户内容与构建产物永不进入该范围,并把标签的生命周期绑定在这个子树上。Access to the path … is denied,看起来就像普通的权限问题(我为此花了很久去授"完全控制"、修 ACL,才找到完整性标签这一层)。用户侧变通办法(一次性)
之后新建目录会自动继承低完整性,所以这是一次性修复。实测结果:对工作区 200 个目录重跑同一探测全部 OK;
py_compile退出码 0;mypy判Success;node能写子目录下的文件;git可运行。版本史说明(请上游确认)
@deepseek-ai/dsh-sandbox-windows-acl@0.1.5-rc.2里完全没有完整性级别相关代码(其lib/*.js中LowLabel、integrity、S-1-16均无命中;写入似乎仅靠能力 SID 的可继承 DACL 允许项),而0.2.0-rc.2新增了标签 + 降令牌。但 #9407 报告在 runtime0.1.5-rc.2下就已经出现该标签,所以具体是哪个版本引入的,应由上游确认——我只能对本地对比过的这两个包作证。环境
@deepseek-ai/dsh@0.2.0-rc.2(同级包版本一致)@deepseek-ai/dsh-sandbox-windows-acl@0.2.0-rc.2@deepseek-ai/dsh-sandbox-windows-acl@0.1.5-rc.2workspace-writeD:\projects(所有者为当前登录用户)附录:另一个容易被误诊的现象(与本 bug 无关)
在沙箱 shell 里,如果通过管道 / 重定向调用程序(例如 PowerShell 的
& exe ... 2>&1 | Out-String,或| Select-Object),进程创建本身就会失败,报程序'x'运行失败:拒绝访问。看起来像"沙箱拦了这个可执行文件",但同一个 exe 不带管道直接跑就正常——这是 stdio 边界限制,与上面讨论的完整性标签无关。记录在此,免得别人查错方向。All reactions