Repository navigation
Replies: 1 comment
|
Thanks for the detailed investigation! My desktop shortcuts were showing blank white icons, and rebuilding the icon cache didn’t help. Restoring the desktop folder’s integrity level from Low to Medium fixed all 25 affected shortcuts. Your findings helped identify the real cause. Really appreciate you documenting this! |
0 replies
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.
Summary
Deleting a workspace from the client does not withdraw the security-descriptor edits the Windows sandbox made to that directory. The capability-SID allow ACE, the
EveryoneFILE_DELETE_CHILDdeny, and the inheritable Low integrity label all stay on disk after the workspace is gone.The orphaned Low label makes Windows' shell classify the whole folder tree as the Internet zone, so Explorer warns on every file the user opens inside it:
Adding the workspace and running anything under
workspace-writewrites those three edits; oneSetNamedSecurityInfoWcall carries all of them. Removing the workspace is the last moment DSH still knows the directory, and the client removes the registration without undoing any of the edits.Why nothing undoes it
The delete path never reaches the sandbox:
packages/client/ui-workspace/src/client/index.ts:deleteWorkspace: async (workspaceId) => { await workspaces.delete(workspaceId) }packages/api/workspace-controller/src/commands.ts:delete()callsctx.workspaceRegistry.delete(...)packages/workspace/workspace/src/index.ts:delete()is documented as "Delete one workspace registration while retaining its directory and every session log"packages/workspace/workspace/README.md: "Removal never deletes data — removing a project leaves its folder, files, and session histories in place"sandbox|acl|grant|integrityunderpackages/workspacereturns nothingThe sandbox side has no other window to do it:
packages/sandbox/sandbox-local/src/index.tsrevokes grants only from the provider's own dispose effect (ctx.effect), and onlyrevocablePaths(the private temp dir).packages/sandbox/sandbox-windows-acl/src/grant.ts:add(path, standing)puts the workspace root instandingPaths;dispose()never touches them.packages/sandbox/sandbox-windows-acl/src/acl.ts:grantWrite()applies the allow ACE, the deny, and the Low label together;revokeWrite()removes only the capability SID's ACEs.The data-preservation contract covers the user's files, not DSH's own security-descriptor edits, and no other layer claims them. They outlive the workspace registration, the session, and the provider process.
The workspace SID is derived deterministically from the canonical workspace path (
workspaceWriteSid), so the delete path could re-derive it and revoke exactly these three edits. The information is available; the step is not implemented.Reproduction (client)
Add a folder as a workspace and run anything under
workspace-writein it. Even a command that fails is enough, because the grant lands before the runner spawns.icacls <workspace>shows the label on the root, inherited by every child directory:plus
Everyone:(CI)(DENY)(DC)andS-1-4-<a>-<b>:(OI)(CI)(W,D,DC).Remove the workspace from the client.
icacls <workspace>still lists all three entries. Nothing in the UI mentioned them.In Explorer, double-click a file anywhere in that tree. A file created years before DSH is enough. The popup above appears.
icacls <workspace> /setintegritylevel Mediumstops the popup and flips the zone decision from 3 to 0.Evidence: the orphaned label is what the shell reads
Measured on Windows 11 26H2 (build 26200) by hooking the path
urlmon!MapUrlToZonetakes:urlmon!MapUrlToZone→urlmon+0x6F712indirect call →iertutil+0x45890CreateFileW→GetKernelObjectSecurity(..., 0x10 /* LABEL_SECURITY_INFORMATION */)and returns(DWORD > 0x1000)0x1000⇒ returns0⇒urlmon+0x6F755writes zone 30x2000⇒ returns1⇒ zone 0AreAccessRulesProtected, the SDDLP/AIflags, andSECURITY_DESCRIPTOR.Controlwere each measured and are not the input, since a zone-3 and a zone-0 directory carry the same value.Observed residue on the reporting machine
Two directories that had been workspaces at some point:
workspaceWriteSid(path))S-1-4-915225578-286341460S-1-4-421311035-171160915workspace-writegrant, because a grant rewrites all three edits and silently overrides a manual fix. Once the workspace was removed, all three were finally withdrawn by hand — but the capability ACE needed the .NET object model (Get-Acl/RemoveAccessRule/Set-Acl):icacls /remove:g *S-1-4-…reportsSuccessfully processed 0 files, exactly the mapping failure the package README warns aboutDirectory A is the case this report is about. The workspace was gone from DSH, but the label it left behind kept the whole tree classified as the Internet zone. The popup it produced was tracked for weeks as a Windows/shell mystery before being pinned to the label.
Together the two cases leave the user no supported way out. Keep the workspace and the next
workspace-writerun re-labels the tree. Delete the workspace and the edits are orphaned: nothing in DSH will ever revoke them, and the shipped recovery path treats the deny as expected rather than as something to remove. The cleanup described above was hand-rolled, and even then needed a different tool than the one the package README recommends for this class of edit. One more registered workspace on the same machine carries the same label, and no DSH surface has told the user.Secondary finding: the deny has no removal path at all
acl.tsrevokeWrite()merges only aREVOKE_ACCESSentry for the capability SID and optionally clears the label. No code path removes the world-SID deny.read-only. The restricting SID list drops the capability SID, so the allow ACE goes inert, but the deny stays effective for every process, because it is an ordinary DACL entry and not a token-scoped one.diagnose-windows-sandbox-aclskill documents that deny as expected ("DSH'sS-1-4-*grants and the EveryoneDeleteSubdirectoriesAndFilesdeny are expected, not conflicts"), so the supported recovery path will not remove it either.DELETEon the object cannot delete it. The package README records this consequence. A normally privileged process is unaffected, because the object DACL still grants itDELETE. This class of failure is easy to misattribute.Why it matters
icacls /setintegritylevel Medium.OI|CI, so it covers files DSH never touched. One measured workspace had 84 child directories carrying it.Suggested directions (maintainer's call)
中文摘要
从客户端删除工作区,不会撤销 Windows 沙箱写进该目录安全描述符的三项改动:能力 SID 允许项、
Everyone的FILE_DELETE_CHILD拒绝、可继承的 Low 完整性标签。删除之后packages/workspace不再认识这个目录,沙箱侧也只在 provider dispose 时回收 temp 授权,三项成为孤儿残留。delete()的契约是"只删注册、保留目录与会话数据",而这三项既不是注册,也不是目录数据,没有任何一层认领。Low 标签会继承到整棵树,让 Windows shell 把该目录判成 Internet 区域(zone 3)。实测判定链是
urlmon!MapUrlToZone→iertutil+0x45890,读的正是目录完整性标签,13/13 零反例;改回 Medium 后弹窗消失、判定回到 zone 0。本机有一个实例:某目录早已不在工作区注册表里(其会话进了归档集),标记却留在磁盘上,后续被当作"Windows 的怪问题"排查了很久。那条
Everyone拒绝在源码里没有任何删除路径,降到read-only也照样对所有进程生效。它还移除了受限令牌依赖的"凭父目录删除子项"这条退路(README 已记录该后果),普通全权进程不受影响。这类故障因此极易被误判成"工具坏了"。工作区 SID 按规范路径确定性派生,删除时完全有能力反算并撤销,缺的只是这一步。建议:删除工作区时回收(可尽力而为、失败只记日志)、为已泄漏的标记提供显式回收途径、首次授权时告知用户改了什么、把标签从工作区根目录上移开或去掉继承。
All reactions