Repository navigation
[Bug] Windows: TEMP moved to a data volume makes every workspace-write command fail before spawn #10765
metal-huang
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
TEMP 挪到数据盘后,workspace-write 下一条命令都跑不起来
这是「沙箱授权需要 WRITE_OWNER」这一族的另一个触发入口,位置在会话的私有临时目录,不是工作区。已被报告过的几条(#7750 / #7771 / #7804 / #7816)说的都是工作区侧;#2287 虽然也在临时目录,但成因不同——那条是「授权后创建者失去访问」,我这边是标签根本写不进去。
现象
workspace-write下任何命令都失败,连Get-Location都不行。切到danger-full-access立刻恢复。失败发生在 spawn 之前:日志里
startedAt == finishedAt,2~6 毫秒,子进程从未创建。环境
app.asar内TEMP/TMP故意指向D:\Temp,目的是不往 C 盘写临时文件——所以 windows-acl: private temp dir under C:\Windows\Temp loses creator access after ACL re-apply (all sandboxed commands unusable) #2287 那条「把 TEMP 改回%LOCALAPPDATA%\Temp」的 workaround 在这里不可用为什么
materializeAclGrant每个会话调用一次mkdtempSync(join(tmpdir(), "dsh-")),在 TEMP 根下新建一个私有目录。这个新目录继承卷根的默认 DACL,其唯一的写授权是Authenticated Users: Modify。而
grantWrite要做的是一次合并写入(DACL | LABEL_SECURITY_INFORMATION),其中写 Low 完整性标签需要WRITE_OWNER:Modify的掩码不含WRITE_OWNER;READ_CONTROL + WRITE_DAC;BUILTIN\Administrators是 deny-only,组的 FullControl 用不上;SeSecurityPrivilege。→
SetNamedSecurityInfoW返回ERROR_ACCESS_DENIED (5)。catch 分支里的grant.dispose()会再次尝试同一处写入(同样失败),removeTempDir随后也失败,于是抛出聚合错误——这就是报错里那句「and its cleanup also failed」的由来。关键点:这是每个会话一个全新目录,没有幂等跳过可用,所以失败是确定性的、100% 复现,不是竞态。
两处授权,失败的是临时目录那一处
materializeAclGrant做两次授权:工作区一次,私有临时目录一次。授权成功过的工作区已经带着精确的 ACE + Deny + 标签,命中幂等跳过;临时目录每次都是新建的,必须重新授权:随包的
diagnose-windows-sandbox-acl只检查工作区及其祖先目录,因此输出verdict: NOT_THIS_CLASS、writeDac=true、writeOwner=true、FIXED=0、nextAction: stop——它不检查临时目录。对照实验
同一账户、同一令牌,只改变目标目录的继承形态:
Authenticated Users: Modify)——mkdtemp在数据盘上的默认结果ERROR_ACCESS_DENIED (5),6 次全失败owner:(OI)(CI)FullControl0,6 次全成功(下探四层子目录也全部为 0)一个容易踩的点:父目录上加不带继承标志的
owner:(F)没有用——子目录看不到它,必须带(OI)(CI)。修法(用户侧,已验证)
给 TEMP 根补一条可继承的完全控制。改完用
app.asar里 0.2.1-rc.2 的原版代码 + 随包 runner 实际 spawn 验证:隔离没有被削弱:capability ACE、world
FILE_DELETE_CHILDDeny、Low 标签都正常落地。(stderr 里whoami的报错是 Git-Bash 的whoami.exe在受限令牌下失败,包 README 已记录,与 PowerShell cmdlet 无关。)边界
这条只解决临时目录侧。从来没有授权过的数据盘工作区,工作区授权仍会 Win32 5——那属于 #7750 / #7804 的范围。两边都处理好之后
workspace-write才完全可用。一条建议
临时根和工作区根也许不该同等对待:
至少在报错里区分「DACL 写入失败」和「标签写入失败」,或者在临时目录这条路径上提供一个不需要标签的方案。
—— 本人与 Claude Code agent 协作完成:agent 负责复现、对照实验与撰写,本人执行操作、复核并发布。
English version
With
TEMPmoved to a data volume, noworkspace-writecommand runs at allThis is another entry point into the "sandbox grant needs WRITE_OWNER" family, and it sits on the session's private temp directory rather than the workspace. The existing reports (#7750 / #7771 / #7804 / #7816) all describe the workspace side; #2287 is also about the temp directory but a different cause — that one is "the creator loses access after the grant", whereas here the label simply cannot be written.
Symptom
Under
workspace-writeevery command fails, not evenGet-Locationruns. Switching todanger-full-accessrestores everything immediately.The failure happens before spawn:
startedAt == finishedAtin the job record, 2–6 ms, and no child process is ever created.Environment
app.asarTEMP/TMPdeliberately set toD:\Tempto keep temp files off C: — which is why windows-acl: private temp dir under C:\Windows\Temp loses creator access after ACL re-apply (all sandboxed commands unusable) #2287's workaround (point TEMP back at%LOCALAPPDATA%\Temp) is not available hereWhy
materializeAclGrantcallsmkdtempSync(join(tmpdir(), "dsh-"))once per session, creating a private directory under the TEMP root. That new directory inherits the volume root's default DACL, whose only write grant isAuthenticated Users: Modify.grantWritethen performs a single merged write (DACL | LABEL_SECURITY_INFORMATION), and writing the Low integrity label requiresWRITE_OWNER:Modifymask does not includeWRITE_OWNER;READ_CONTROL + WRITE_DAC;BUILTIN\Administratorsis deny-only, so the group's FullControl is unusable;SeSecurityPrivilege.→
SetNamedSecurityInfoWreturnsERROR_ACCESS_DENIED (5). The catch block'sgrant.dispose()retries the same write (and fails again), thenremoveTempDirfails too, producing the aggregate error — hence the "and its cleanup also failed" in the message.The decisive part: this is a brand-new directory every session, so no idempotent skip can apply. The failure is deterministic and reproduces 100% of the time; it is not a race.
Two grants; the one that fails is the temp directory
materializeAclGrantperforms two grants: one on the workspace, one on the private temp directory. A workspace granted earlier already carries the exact ACE + Deny + label and takes the skip path; the temp directory is new every session and must be granted from scratch:The bundled
diagnose-windows-sandbox-aclonly inspects the workspace and its ancestors, so it reportsverdict: NOT_THIS_CLASS,writeDac=true,writeOwner=true,FIXED=0,nextAction: stop— it does not look at the temp directory.Controlled experiment
Same account, same token; only the target directory's inheritance shape changes:
Authenticated Users: Modify) — whatmkdtempgets on a data volumeERROR_ACCESS_DENIED (5), 6 of 6owner:(OI)(CI)FullControlon the parent0, 6 of 6 (four levels of children: all0as well)One easy trap: a non-inheritable
owner:(F)on the parent does nothing — the child never sees it; it must carry(OI)(CI).Fix (user side, verified)
Add an inheritable FullControl on the TEMP root. Verified afterwards by actually spawning the confined runner with the 0.2.1-rc.2 packages taken from
app.asar:Confinement is intact: the capability ACE, the world
FILE_DELETE_CHILDDeny and the Low label all materialize normally. (Thewhoamierror on stderr is Git-Bash'swhoami.exefailing under the restricted token — documented in the package README, and unrelated to PowerShell cmdlets.)Boundary
This covers the temp side only. A data-volume workspace that has never been granted still fails its workspace grant with Win32 5 — that belongs to #7750 / #7804. Both sides have to be right before
workspace-writeis fully usable.One suggestion
The temp root and the workspace root arguably should not be treated alike:
At minimum, distinguish "DACL write failed" from "label write failed" in the error, or give the temp path a route that does not need the label.
— Written with a Claude Code agent (it ran the reproduction, controlled experiments and drafting); the human ran the operations, reviewed the results and is posting.
All reactions