chainlesschain 0.163.2 → 0.163.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-DnGsmsqJ.js → AIOps-DIY6KxV-.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-CrO6fcQ7.js → ActionButton-BXH0muPt.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DsBdOaP2.js → Analytics-D39w8vmg.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-DmQslKUB.js → AppLayout-CUU1zvLs.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-BvyrT_QQ.js → Artifacts-D8azHO3T.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-prndoLiC.js → Audit-D0tAwXTg.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DOTFyA55.js → BackgroundAgents-C29mhJll.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-9zOnPp_8.js → Backup-D6Aa1Gro.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-C4PC6sAG.js → BaseInput-B1uoHbPQ.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-BBAF-W9v.js → Chat-Bs4DLOR5.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CWhvvloO.js → ChatBubbleRenderer-DUYIBSle.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-BI9iculd.js → Checkbox-CNcBPEa4.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-3DP9BCKb.js → Codegen-CIY89763.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-df-2TnJX.js → Col-CsPYFmgo.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-CHs8gG_A.js → Community-BBocEge4.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-CDfF6q7V.js → Compact-TQ8Ux0Lf.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CthrDnO7.js → Compliance-DfBsZTft.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-HjIqsWJw.js → Cowork-Cg-I6FVH.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-BKc196fb.js → Cron-DTQzfRTp.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-CJnjdc8W.js → Crosschain-DGxEKGpR.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BHUy-8Q2.js → DID-DLZ1RDSr.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-QDB7sKeY.js → Dashboard-XneICKNI.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DStcVK11.js → Dropdown-BRU00J1-.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-BwSUskMJ.js → EmailListRenderer-BzCbJuAy.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DgF_kurT.js → FamilyGuardDashboard-D7BRyNBG.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-k8wvv-wN.js → Federation-D9DeFIhW.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-C11qoq2r.js → FormItemContext-6rAtA9Ko.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BIIpr9kg.js → GenericCardRenderer-8Hqy80pU.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-BlA1exTe.js → Git-Cmvkj1rt.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CYa1ZYM4.js → Governance-BRiQUjjw.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-6_kKVIt6.js → Inference-CKN_EzVf.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-CB4AwxYy.js → KnowledgeGraph-BCDHwpwQ.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-xf7nHFAe.js → Logs-DK9ovhaB.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-D53pHiHy.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-aPg8e4Jt.js → Marketplace-BILPGakp.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-C-TNlVUf.js → McpTools-DTVCOr9-.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-DMTPIpGa.js → Memory-Hzc1iow4.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BxyvveRq.js → MobileBridge-GXfgnGYV.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-DyPCrAyQ.js → MobileProjects-B-3TJWsS.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-BdhqNXp6.js → Mtc-DwSJMWtK.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-Cz-HFxzW.js → MtcAudit-D2uxf-DA.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-CIUaFNsx.js → Multisig-DG6kbKK0.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CWe6-J17.js → NLProgramming-f5p49EDU.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-vYAKeviU.js → Notes-FUgHyzSD.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-DQL9-2d7.js → NotificationSettings-DCHGee7e.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DnXj1jBn.js → OrderTableRenderer-Dm6LlIoa.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-B3spU8AN.js → Organization-BawA7qAg.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-DgEwo7vm.js → Overflow-D6a5wZU1.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-hujMRH8X.js → P2P-K8WavwnA.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-C_qhdg9h.js → PdhVaultBrowser-4JAJYaqx.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-vp7LMcSf.js → Permissions-BDWvi8PD.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-Dxr5Hgnq.js → PersonalDataHub-DF_K4E9i.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-DEPZqqH4.js → Pipeline-BYeZlesI.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-DTpDiKPe.js → Privacy-BIHlgnIc.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-jDJsRWmF.js → ProjectInit-Cvf-6B5P.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-Dhor5RWH.js → ProjectSettings-CnwfJrRz.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-b-jPXzF4.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-BGs47s1a.js → Providers-CxC7JuYv.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-CjIw-wIb.js → QrScannerModal-VLf3gAfL.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-DX6jcqoi.js → QuickAsk-B5vGjRiF.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-DopXkhar.js → Recommend--UW4iYs-.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-BLvqJE9c.js → RemoteSession-DMSPcfKR.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-DhRj2Me7.js → Reputation-kQXlEHmj.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-DQ53vuPq.js → Row-CxGu3pit.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-BslvwiBD.js → RssFeed-Bi5lo8MZ.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-DJKBDNKZ.js → Search-Cq_H5RTh.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-COhpxHER.js → Security-YFbmkaQS.js} +3 -3
  69. package/src/assets/web-panel/assets/{Services-dbulD6cI.js → Services-DLOGdaLm.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-ciIVNfhC.js → Skeleton-CteIxb3D.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-AiRTkTCk.js → Skills-CccopaIZ.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DDMcWYBc.js → Sla-AzfEQKX1.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-BPxWbpXO.js → SpeechSettings-ekGo5l77.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-BoEOXCyg.js → SyncSettings-BmZmAgYH.js} +2 -2
  75. package/src/assets/web-panel/assets/Tasks-D7q3Z4uL.js +1 -0
  76. package/src/assets/web-panel/assets/{Templates-UowY8Ixe.js → Templates-CX1Tgeka.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-DauSLQXN.js → Tenant-Zc59jF4i.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-DBj7Jneq.js → Terminal-CXOD7y83.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-UMrPwoZC.js → TimelineRenderer-B1aBHPBo.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-4ea96-bn.js → Tokens-zALlOq_U.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger--ISCqt4b.js → Trigger-CQfJMXaq.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-D4GVimRJ.js → Trust-BU0RcM-w.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-Ci-nXRQc.js → UkeySign-IFZ3HsbH.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-Cjk7mKPw.js → VideoEditing-BzslgW0v.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-BgOLOO3Z.js → Wallet-BZAcQNS6.js} +2 -2
  86. package/src/assets/web-panel/assets/{WebAuthn-CCX0K29i.js → WebAuthn-CBK1DMi0.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-Ch3zgIsE.js → WorkflowEditor-BlMTPzO-.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-DKOU_2Vb.js → chat-B51-8QVn.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-6OVo4u3C.js → colors-504RTByU.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-BH78Hlsq.js → compact-item-C-GP6IQM.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-CFRpSbbC.js → createContext-DOkIj7ID.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-kB0nCNwN.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DNQhgcVi.js → hasIn-DN7V-_FW.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-dDkoL4Pe.js → index--n-ZciV3.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CaYqY35Z.js → index-7jIqHbzw.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BYrnm5Ob.js → index-B2-x9bS8.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-sm7W8AXy.js → index-BFIFJ5Dr.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DKN2noIF.js → index-B_vGU-Js.js} +1 -1
  99. package/src/assets/web-panel/assets/{index--rq90rL9.js → index-Bkxhaeta.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CFPJ8yzF.js → index-By6u5IV1.js} +1 -1
  101. package/src/assets/web-panel/assets/index-Bya64sZE.js +1 -0
  102. package/src/assets/web-panel/assets/{index-caye7AOE.js → index-C0k_Nqg9.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BxRy5LPR.js → index-C3TSohzc.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BepBTpa8.js → index-C4DV_5uA.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-D84id2Y4.js → index-C8UltbXK.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Y9MJv28e.js → index-C9_48OzU.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Bb6kDrUx.js → index-CKTfZyfU.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CrcLHN2F.js → index-CP2A226L.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CkNEMANR.js → index-CUhQpiYv.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Cu3L4r5l.js → index-CX78k-qf.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DaDgdJYj.js → index-CYopNjcJ.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Du0rnksC.js → index-Cao5DZy7.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BaVE6E8y.js → index-CfiEM64n.js} +3 -3
  114. package/src/assets/web-panel/assets/{index-BujsOZfc.js → index-ConjaJro.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-hPY0fFrd.js → index-CqcLo5WV.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BDSR7ecX.js → index-CySTJKeq.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BHa4WyTZ.js → index-D1phyca8.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-nbfHl1oV.js → index-D9LyiN-L.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CkqHgG-b.js → index-DA2f1UV3.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Eag8e_mA.js → index-DAOpQvl_.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-cdf7fd7d.js → index-DE-hmI2l.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-bKm3TUDp.js → index-DEMPUVa3.js} +1 -1
  123. package/src/assets/web-panel/assets/index-D_FWpDZM.js +1 -0
  124. package/src/assets/web-panel/assets/{index-7TC3S8SN.js → index-DaA__-q1.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DvHGtNnF.js → index-FcV8J_Qz.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-B_Z-YoxX.js → index-P2mfY27Z.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-AuBhLqOm.js → index-et5JIPHO.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-D3RtTT4K.js → index-f4Bo4KXm.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CNuCHHQT.js → index-kAqQXfP6.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BkN3iTTp.js → index-kt_5rfcZ.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BnnXQot_.js → index-so8eIIxF.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-HNGGVF6o.js → index-yYTQZypF.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-BZ9w6jPL.js → initDefaultProps-CPYEIdpV.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BnwUiOho.js → motion-Cjim9pxs.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-ByUKJnNm.js → move-DuU-cgz3.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-V7yK1KEl.js → mtc-parser-DJHNAkta.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BEXBBt4j.js → omit-DSQBgdBj.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-qDm_46Gi.js → pickAttrs-CTmT_j5m.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BIbhEo_0.js → placementArrow-Lfm6v-r4.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-NbPbDgA_.js → responsiveObserve-CgrcPcDi.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-BnUWglZ0.js → slide-CuE3110i.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BX8_wZI5.js → statusUtils-DiK5nbpf.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BV4OycQX.js → styleChecker-DPEJdQaZ.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-Od_H5llY.js → useFlexGapSupport-CnnodGmp.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-BWI-PyiX.js → useFs-DWF84Yav.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-BqMuhG3g.js → usePersonalDataHub-a5b5Dw2f.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-DNd23z6f.js → vnode-DAluzXOq.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-Cjaxnm7l.js → zoom-Dokg7uAZ.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-help-index.json +1 -1
  151. package/src/commands/agenda.js +6 -5
  152. package/src/commands/agent.js +237 -75
  153. package/src/commands/agents.js +6 -0
  154. package/src/commands/background-session.js +11 -1
  155. package/src/commands/mcp.js +217 -30
  156. package/src/data/changelog.json +14 -1
  157. package/src/harness/mcp-client.js +242 -42
  158. package/src/harness/worktree-isolator.js +26 -3
  159. package/src/lib/agent-worktree.js +41 -2
  160. package/src/lib/background-agent-supervisor.js +790 -186
  161. package/src/lib/background-command-argv.js +318 -0
  162. package/src/lib/background-launch-profile.js +1 -0
  163. package/src/lib/background-phase-reporter.js +22 -2
  164. package/src/lib/background-worktree-policy.js +109 -0
  165. package/src/lib/cowork-mcp-tools.js +51 -6
  166. package/src/lib/cowork-task-runner.js +1 -0
  167. package/src/lib/mcp-headers-helper.js +76 -8
  168. package/src/lib/mcp-sandbox-policy.js +155 -0
  169. package/src/lib/mcp-stdio-execution-authority.js +163 -0
  170. package/src/lib/mcp-stdio-workspace-authority.js +252 -0
  171. package/src/lib/plugin-runtime/sandbox-policy.js +42 -2
  172. package/src/lib/process-execution-broker/index.js +179 -104
  173. package/src/lib/process-execution-broker/platform-sandbox.js +942 -168
  174. package/src/lib/process-tree-termination.js +17 -1
  175. package/src/lib/skill-mcp.js +65 -4
  176. package/src/lib/web-fetch.js +4 -4
  177. package/src/runtime/agent-runtime.js +61 -8
  178. package/src/runtime/mcp-config.js +235 -28
  179. package/src/workers/background-agent-worker.js +492 -71
  180. package/src/assets/web-panel/assets/MarkdownRenderer-Bf3DEqP6.js +0 -1
  181. package/src/assets/web-panel/assets/Projects-08xUDZGt.js +0 -1
  182. package/src/assets/web-panel/assets/Tasks-CEYFJwmr.js +0 -1
  183. package/src/assets/web-panel/assets/devWarning-DEMblAIm.js +0 -1
  184. package/src/assets/web-panel/assets/index-BTvq-36D.js +0 -1
  185. package/src/assets/web-panel/assets/index-hUpvk6Jl.js +0 -1
@@ -108,6 +108,13 @@ const WINDOWS_APPCONTAINER_BACKEND =
108
108
  "windows-appcontainer-job-restricted-token";
109
109
  const MCP_STDIO_CAPSULE_SANDBOX_CONTRACT_KIND = "strict-mcp-node-capsule";
110
110
  const WINDOWS_ADAPTER_IDLE_TTL_MS = 60_000;
111
+ const WINDOWS_ADAPTER_HELPER_NAME = "windows-sandbox-helper.exe";
112
+ const WINDOWS_ADAPTER_IDLE_TTL_ENV = "CC_WINDOWS_SANDBOX_ADAPTER_IDLE_TTL_MS";
113
+ const WINDOWS_ADAPTER_TEMP_ROOT_ENV = "CC_WINDOWS_SANDBOX_ADAPTER_TEMP_ROOT";
114
+ const WINDOWS_ADAPTER_TEMP_ROOT_UNTRUSTED_REASON =
115
+ "windows_adapter_temp_root_untrusted";
116
+ const WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON =
117
+ "windows_adapter_temp_root_changed";
111
118
  // Every native helper operation starts a trusted System32 PowerShell host and
112
119
  // byte-loads the checked-in assembly. Hosted Windows runners can spend well
113
120
  // over ten seconds in that bootstrap before the managed operation begins.
@@ -179,8 +186,8 @@ export const MCP_STDIO_FD_ENTRY_BOOTSTRAP_SHA256 = crypto
179
186
  .digest("hex");
180
187
  const LINUX_NODE_ENTRY_SNAPSHOT_SCOPE = "plugin-entry-source";
181
188
  const LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE = "plugin-entry-executable";
182
- const LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE = "all-pinned-plugin-regular-files";
183
- const LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY = "per-file-pin-to-launch";
189
+ const LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE = "all-pinned-plugin-regular-files";
190
+ const LINUX_PLUGIN_TREE_SNAPSHOT_CONSISTENCY = "per-file-pin-to-launch";
184
191
  const LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE = "0400";
185
192
  const LINUX_NATIVE_ENTRY_SNAPSHOT_TARGET_MODE = "0500";
186
193
  const LINUX_ELF64_HEADER_BYTES = 64;
@@ -311,6 +318,325 @@ function resolveRuntime(overrides = {}) {
311
318
  return { ...DEFAULT_RUNTIME, ...overrides };
312
319
  }
313
320
 
321
+ function windowsNativeRealpath(runtime, candidate) {
322
+ const nativeRealpath = runtime.fs.realpathSync?.native;
323
+ if (typeof nativeRealpath !== "function") {
324
+ throw new Error("Native Windows realpath is unavailable");
325
+ }
326
+ const nativePath = nativeRealpath.call(runtime.fs, candidate);
327
+ const withoutExtendedPrefix = String(nativePath).replace(
328
+ /^\\\\\?\\(?=[A-Za-z]:\\)/,
329
+ "",
330
+ );
331
+ return path.win32.resolve(withoutExtendedPrefix);
332
+ }
333
+
334
+ function windowsPathHasReparseSemantics(stat) {
335
+ return (
336
+ stat.isSymbolicLink?.() ||
337
+ stat.isReparsePoint?.() ||
338
+ stat.reparsePoint === true
339
+ );
340
+ }
341
+
342
+ function windowsLocalPathAncestors(candidate) {
343
+ const resolved = path.win32.resolve(candidate);
344
+ const volumeRoot = path.win32.parse(resolved).root;
345
+ if (!/^[A-Za-z]:\\$/.test(volumeRoot)) return null;
346
+ const relative = path.win32.relative(volumeRoot, resolved);
347
+ if (
348
+ path.win32.isAbsolute(relative) ||
349
+ relative === ".." ||
350
+ relative.startsWith(`..${path.win32.sep}`)
351
+ ) {
352
+ return null;
353
+ }
354
+ const ancestors = [volumeRoot];
355
+ let current = volumeRoot;
356
+ for (const segment of relative.split(path.win32.sep).filter(Boolean)) {
357
+ current = path.win32.join(current, segment);
358
+ ancestors.push(current);
359
+ }
360
+ return ancestors;
361
+ }
362
+
363
+ function windowsDirectoryIdentity(stat) {
364
+ const birthtimeNs =
365
+ stat.birthtimeNs !== undefined
366
+ ? String(stat.birthtimeNs)
367
+ : String(Math.trunc(Number(stat.birthtimeMs || 0) * 1_000_000));
368
+ if (
369
+ stat.dev === undefined ||
370
+ stat.ino === undefined ||
371
+ stat.mode === undefined
372
+ ) {
373
+ throw new Error("Windows directory identity is unavailable");
374
+ }
375
+ return Object.freeze({
376
+ dev: String(stat.dev),
377
+ ino: String(stat.ino),
378
+ mode: String(stat.mode),
379
+ birthtimeNs,
380
+ });
381
+ }
382
+
383
+ function sameWindowsDirectoryIdentity(left, right) {
384
+ return (
385
+ left?.dev === right?.dev &&
386
+ left?.ino === right?.ino &&
387
+ left?.mode === right?.mode &&
388
+ left?.birthtimeNs === right?.birthtimeNs
389
+ );
390
+ }
391
+
392
+ function inspectWindowsAdapterTempRoot(runtime, candidate) {
393
+ if (
394
+ typeof candidate !== "string" ||
395
+ candidate.length === 0 ||
396
+ candidate.includes("\0") ||
397
+ !path.win32.isAbsolute(candidate)
398
+ ) {
399
+ return null;
400
+ }
401
+ const sourcePath = path.win32.resolve(candidate);
402
+ const sourceAncestors = windowsLocalPathAncestors(sourcePath);
403
+ if (!sourceAncestors) return null;
404
+
405
+ try {
406
+ // Keep the caller-visible path in the trust decision even when Windows
407
+ // exposes it through a legitimate 8.3 alias (for example RUNNER~1 on a
408
+ // hosted runner). Every raw ancestor must be a plain directory before the
409
+ // native real path is accepted as the path used for mutations.
410
+ let sourceRootStat = null;
411
+ for (const ancestor of sourceAncestors) {
412
+ const stat = runtime.fs.lstatSync(ancestor, { bigint: true });
413
+ if (windowsPathHasReparseSemantics(stat) || !stat.isDirectory?.()) {
414
+ return null;
415
+ }
416
+ sourceRootStat = stat;
417
+ }
418
+
419
+ const rootPath = windowsNativeRealpath(runtime, sourcePath);
420
+ const rootAncestors = windowsLocalPathAncestors(rootPath);
421
+ if (!rootAncestors) return null;
422
+ let canonicalRootStat = null;
423
+ for (const ancestor of rootAncestors) {
424
+ const stat = runtime.fs.lstatSync(ancestor, { bigint: true });
425
+ if (windowsPathHasReparseSemantics(stat) || !stat.isDirectory?.()) {
426
+ return null;
427
+ }
428
+ canonicalRootStat = stat;
429
+ }
430
+
431
+ const sourceIdentity = windowsDirectoryIdentity(sourceRootStat);
432
+ const identity = windowsDirectoryIdentity(canonicalRootStat);
433
+ if (!sameWindowsDirectoryIdentity(sourceIdentity, identity)) return null;
434
+
435
+ const sourceAfter = runtime.fs.lstatSync(sourcePath, { bigint: true });
436
+ const rootAfter = runtime.fs.lstatSync(rootPath, { bigint: true });
437
+ if (
438
+ windowsPathHasReparseSemantics(sourceAfter) ||
439
+ !sourceAfter.isDirectory?.() ||
440
+ windowsPathHasReparseSemantics(rootAfter) ||
441
+ !rootAfter.isDirectory?.() ||
442
+ windowsCanonicalPathKey(windowsNativeRealpath(runtime, sourcePath)) !==
443
+ windowsCanonicalPathKey(rootPath) ||
444
+ windowsCanonicalPathKey(windowsNativeRealpath(runtime, rootPath)) !==
445
+ windowsCanonicalPathKey(rootPath) ||
446
+ !sameWindowsDirectoryIdentity(
447
+ sourceIdentity,
448
+ windowsDirectoryIdentity(sourceAfter),
449
+ ) ||
450
+ !sameWindowsDirectoryIdentity(
451
+ identity,
452
+ windowsDirectoryIdentity(rootAfter),
453
+ )
454
+ ) {
455
+ return null;
456
+ }
457
+ return Object.freeze({
458
+ sourcePath,
459
+ sourceCanonicalPathKey: windowsCanonicalPathKey(sourcePath),
460
+ rootPath,
461
+ canonicalPathKey: windowsCanonicalPathKey(rootPath),
462
+ volumeRoot: path.win32.parse(rootPath).root.toLowerCase(),
463
+ identity,
464
+ attestation: Object.freeze({
465
+ kind: "windows-adapter-temp-root-path-reattestation-v1",
466
+ localVolume: true,
467
+ nativeRealpathMatched: true,
468
+ sourceAliasCanonicalized:
469
+ windowsCanonicalPathKey(sourcePath) !==
470
+ windowsCanonicalPathKey(rootPath),
471
+ ancestorReparsePointsRejected: true,
472
+ rootIdentityBound: true,
473
+ criticalOperationReattestation: true,
474
+ descriptorRelativeOperations: false,
475
+ handleAtomic: false,
476
+ residualRace:
477
+ "same-principal-path-swap-between-reattestation-and-filesystem-call",
478
+ residualHandling: "fail-closed-on-detected-path-or-identity-change",
479
+ }),
480
+ });
481
+ } catch {
482
+ return null;
483
+ }
484
+ }
485
+
486
+ function sameWindowsAdapterTempRootBinding(left, right) {
487
+ return (
488
+ left?.sourceCanonicalPathKey === right?.sourceCanonicalPathKey &&
489
+ left?.canonicalPathKey === right?.canonicalPathKey &&
490
+ left?.volumeRoot === right?.volumeRoot &&
491
+ sameWindowsDirectoryIdentity(left?.identity, right?.identity)
492
+ );
493
+ }
494
+
495
+ function verifyWindowsAdapterTempRootBinding(runtime, binding) {
496
+ const current = inspectWindowsAdapterTempRoot(
497
+ runtime,
498
+ binding?.sourcePath || binding?.rootPath,
499
+ );
500
+ return Boolean(
501
+ current && sameWindowsAdapterTempRootBinding(binding, current),
502
+ );
503
+ }
504
+
505
+ function windowsAdapterTempRootChangedError() {
506
+ const error = new Error(
507
+ "Windows sandbox adapter temp root changed after attestation",
508
+ );
509
+ error.code = "ERR_WINDOWS_SANDBOX_ADAPTER_TEMP_ROOT_CHANGED";
510
+ error.adapterReason = WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON;
511
+ return error;
512
+ }
513
+
514
+ function assertWindowsAdapterTempRoot(runtime) {
515
+ const binding = runtime.windowsAdapterTempRootBinding;
516
+ if (!binding || !verifyWindowsAdapterTempRootBinding(runtime, binding)) {
517
+ throw windowsAdapterTempRootChangedError();
518
+ }
519
+ return binding;
520
+ }
521
+
522
+ function inspectWindowsAdapterBoundPath(
523
+ runtime,
524
+ targetPath,
525
+ expectedKind,
526
+ { allowMissing = false } = {},
527
+ ) {
528
+ const binding = assertWindowsAdapterTempRoot(runtime);
529
+ if (
530
+ typeof targetPath !== "string" ||
531
+ targetPath.includes("\0") ||
532
+ !path.win32.isAbsolute(targetPath)
533
+ ) {
534
+ throw windowsAdapterTempRootChangedError();
535
+ }
536
+ const resolved = path.win32.resolve(targetPath);
537
+ const relative = path.win32.relative(binding.rootPath, resolved);
538
+ if (
539
+ !relative ||
540
+ path.win32.isAbsolute(relative) ||
541
+ relative === ".." ||
542
+ relative.startsWith(`..${path.win32.sep}`)
543
+ ) {
544
+ throw windowsAdapterTempRootChangedError();
545
+ }
546
+
547
+ const segments = relative.split(path.win32.sep).filter(Boolean);
548
+ let current = binding.rootPath;
549
+ for (let index = 0; index < segments.length; index += 1) {
550
+ current = path.win32.join(current, segments[index]);
551
+ const isTarget = index === segments.length - 1;
552
+ let stat;
553
+ try {
554
+ stat = runtime.fs.lstatSync(current, { bigint: true });
555
+ } catch (error) {
556
+ if (isTarget && allowMissing && error?.code === "ENOENT") {
557
+ return Object.freeze({ exists: false, resolvedPath: resolved });
558
+ }
559
+ throw error;
560
+ }
561
+ if (
562
+ windowsPathHasReparseSemantics(stat) ||
563
+ windowsCanonicalPathKey(windowsNativeRealpath(runtime, current)) !==
564
+ windowsCanonicalPathKey(current)
565
+ ) {
566
+ throw windowsAdapterTempRootChangedError();
567
+ }
568
+ if (!isTarget && !stat.isDirectory?.()) {
569
+ throw windowsAdapterTempRootChangedError();
570
+ }
571
+ if (isTarget) {
572
+ if (
573
+ (expectedKind === "file" && !stat.isFile?.()) ||
574
+ (expectedKind === "directory" && !stat.isDirectory?.())
575
+ ) {
576
+ throw windowsAdapterTempRootChangedError();
577
+ }
578
+ return Object.freeze({
579
+ exists: true,
580
+ resolvedPath: resolved,
581
+ identity:
582
+ expectedKind === "directory"
583
+ ? windowsDirectoryIdentity(stat)
584
+ : windowsFileIdentity(stat),
585
+ });
586
+ }
587
+ }
588
+ throw windowsAdapterTempRootChangedError();
589
+ }
590
+
591
+ function assertWindowsAdapterBoundPathIdentity(
592
+ runtime,
593
+ targetPath,
594
+ expectedKind,
595
+ expectedIdentity,
596
+ ) {
597
+ const inspected = inspectWindowsAdapterBoundPath(
598
+ runtime,
599
+ targetPath,
600
+ expectedKind,
601
+ );
602
+ const matches =
603
+ expectedKind === "directory"
604
+ ? sameWindowsDirectoryIdentity(inspected.identity, expectedIdentity)
605
+ : sameWindowsFileIdentity(inspected.identity, expectedIdentity);
606
+ if (!matches) throw windowsAdapterTempRootChangedError();
607
+ return inspected;
608
+ }
609
+
610
+ function resolveWindowsAdapterTempRoot(runtime, runtimeOverrides = {}) {
611
+ const hasExplicitTmpdir = Object.prototype.hasOwnProperty.call(
612
+ runtimeOverrides,
613
+ "tmpdir",
614
+ );
615
+ const hasExplicitAdapterRoot = Object.prototype.hasOwnProperty.call(
616
+ runtimeOverrides,
617
+ "windowsAdapterTempRoot",
618
+ );
619
+ let candidate;
620
+ try {
621
+ if (hasExplicitTmpdir) {
622
+ if (typeof runtime.tmpdir !== "function") return null;
623
+ candidate = runtime.tmpdir();
624
+ } else if (hasExplicitAdapterRoot) {
625
+ candidate = runtimeOverrides.windowsAdapterTempRoot;
626
+ } else {
627
+ const environmentRoot =
628
+ runtime.env?.[WINDOWS_ADAPTER_TEMP_ROOT_ENV] ??
629
+ process.env[WINDOWS_ADAPTER_TEMP_ROOT_ENV];
630
+ candidate =
631
+ environmentRoot === undefined ? runtime.tmpdir() : environmentRoot;
632
+ }
633
+ } catch {
634
+ return null;
635
+ }
636
+
637
+ return inspectWindowsAdapterTempRoot(runtime, candidate);
638
+ }
639
+
314
640
  function normalizeSandboxRequest(profileOrRequest, explicitRequest) {
315
641
  const legacyRequest =
316
642
  profileOrRequest &&
@@ -788,14 +1114,42 @@ function cleanupWindowsTemporaryPath(
788
1114
  targetPath,
789
1115
  attempts = 100,
790
1116
  delayMs = 10,
1117
+ expectedIdentity = null,
791
1118
  ) {
792
1119
  if (!targetPath) return true;
793
1120
  for (let attempt = 0; attempt < attempts; attempt += 1) {
794
1121
  try {
1122
+ const inspected = inspectWindowsAdapterBoundPath(
1123
+ runtime,
1124
+ targetPath,
1125
+ "file",
1126
+ { allowMissing: true },
1127
+ );
1128
+ if (!inspected.exists) {
1129
+ assertWindowsAdapterTempRoot(runtime);
1130
+ return true;
1131
+ }
1132
+ if (
1133
+ !expectedIdentity ||
1134
+ !sameWindowsFileIdentity(inspected.identity, expectedIdentity)
1135
+ ) {
1136
+ return false;
1137
+ }
795
1138
  runtime.fs.unlinkSync(targetPath);
1139
+ assertWindowsAdapterTempRoot(runtime);
796
1140
  return true;
797
1141
  } catch (error) {
798
- if (error?.code === "ENOENT") return true;
1142
+ if (error?.adapterReason === WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON) {
1143
+ return false;
1144
+ }
1145
+ if (error?.code === "ENOENT") {
1146
+ try {
1147
+ assertWindowsAdapterTempRoot(runtime);
1148
+ return true;
1149
+ } catch {
1150
+ return false;
1151
+ }
1152
+ }
799
1153
  }
800
1154
  if (attempt + 1 < attempts) runtime.sleepSync(delayMs);
801
1155
  }
@@ -806,13 +1160,32 @@ function cleanupOrTrackWindowsTemporaryPath(
806
1160
  runtime,
807
1161
  targetPath,
808
1162
  attempts = 100,
1163
+ entry = null,
1164
+ expectedIdentity = null,
809
1165
  ) {
810
- if (cleanupWindowsTemporaryPath(runtime, targetPath, attempts)) {
1166
+ if (
1167
+ cleanupWindowsTemporaryPath(
1168
+ runtime,
1169
+ targetPath,
1170
+ attempts,
1171
+ 10,
1172
+ expectedIdentity,
1173
+ )
1174
+ ) {
1175
+ const residualEntries = new Set();
811
1176
  for (const residual of [...windowsTemporaryCleanupBacklog]) {
812
- if (residual.runtime === runtime && residual.targetPath === targetPath) {
1177
+ if (
1178
+ residual.kind === "file" &&
1179
+ residual.runtime === runtime &&
1180
+ residual.targetPath === targetPath
1181
+ ) {
813
1182
  windowsTemporaryCleanupBacklog.delete(residual);
1183
+ if (residual.entry) residualEntries.add(residual.entry);
814
1184
  }
815
1185
  }
1186
+ for (const residualEntry of residualEntries) {
1187
+ finalizeWindowsTemporaryCleanupEntry(residualEntry);
1188
+ }
816
1189
  if (
817
1190
  windowsTemporaryCleanupBacklog.size === 0 &&
818
1191
  windowsTemporaryCleanupRetryTimer
@@ -822,13 +1195,25 @@ function cleanupOrTrackWindowsTemporaryPath(
822
1195
  }
823
1196
  return true;
824
1197
  }
825
- if (
826
- ![...windowsTemporaryCleanupBacklog].some(
827
- (residual) =>
828
- residual.runtime === runtime && residual.targetPath === targetPath,
829
- )
830
- ) {
831
- windowsTemporaryCleanupBacklog.add({ runtime, targetPath });
1198
+ const existing = [...windowsTemporaryCleanupBacklog].find(
1199
+ (residual) =>
1200
+ residual.kind === "file" &&
1201
+ residual.runtime === runtime &&
1202
+ residual.targetPath === targetPath,
1203
+ );
1204
+ if (existing) {
1205
+ if (entry) existing.entry = entry;
1206
+ if (!existing.expectedIdentity && expectedIdentity) {
1207
+ existing.expectedIdentity = expectedIdentity;
1208
+ }
1209
+ } else {
1210
+ windowsTemporaryCleanupBacklog.add({
1211
+ kind: "file",
1212
+ runtime,
1213
+ targetPath,
1214
+ entry,
1215
+ expectedIdentity,
1216
+ });
832
1217
  }
833
1218
  registerWindowsAdapterExitCleanup();
834
1219
  scheduleWindowsTemporaryCleanupRetry();
@@ -838,14 +1223,25 @@ function cleanupOrTrackWindowsTemporaryPath(
838
1223
  function retryWindowsTemporaryCleanupBacklog(attempts = 100) {
839
1224
  let cleaned = true;
840
1225
  for (const residual of [...windowsTemporaryCleanupBacklog]) {
841
- if (
842
- cleanupWindowsTemporaryPath(
843
- residual.runtime,
844
- residual.targetPath,
845
- attempts,
846
- )
847
- ) {
1226
+ const residualCleaned =
1227
+ residual.kind === "directory"
1228
+ ? cleanupWindowsTemporaryDirectory(
1229
+ residual.runtime,
1230
+ residual.targetPath,
1231
+ attempts,
1232
+ 10,
1233
+ residual.expectedIdentity,
1234
+ )
1235
+ : cleanupWindowsTemporaryPath(
1236
+ residual.runtime,
1237
+ residual.targetPath,
1238
+ attempts,
1239
+ 10,
1240
+ residual.expectedIdentity,
1241
+ );
1242
+ if (residualCleaned) {
848
1243
  windowsTemporaryCleanupBacklog.delete(residual);
1244
+ if (residual.entry) finalizeWindowsTemporaryCleanupEntry(residual.entry);
849
1245
  } else {
850
1246
  cleaned = false;
851
1247
  }
@@ -860,6 +1256,18 @@ function retryWindowsTemporaryCleanupBacklog(attempts = 100) {
860
1256
  return cleaned;
861
1257
  }
862
1258
 
1259
+ function finalizeWindowsTemporaryCleanupEntry(entry) {
1260
+ if (
1261
+ [...windowsTemporaryCleanupBacklog].some(
1262
+ (residual) => residual.entry === entry,
1263
+ )
1264
+ ) {
1265
+ return;
1266
+ }
1267
+ entry.cleaned = true;
1268
+ windowsAdapterCacheEntries.delete(entry);
1269
+ }
1270
+
863
1271
  function scheduleWindowsTemporaryCleanupRetry() {
864
1272
  if (
865
1273
  windowsTemporaryCleanupRetryTimer ||
@@ -882,20 +1290,112 @@ function cleanupWindowsTemporaryDirectory(
882
1290
  targetPath,
883
1291
  attempts = 100,
884
1292
  delayMs = 10,
1293
+ expectedIdentity = null,
885
1294
  ) {
886
1295
  if (!targetPath) return true;
887
1296
  for (let attempt = 0; attempt < attempts; attempt += 1) {
888
1297
  try {
1298
+ const inspected = inspectWindowsAdapterBoundPath(
1299
+ runtime,
1300
+ targetPath,
1301
+ "directory",
1302
+ { allowMissing: true },
1303
+ );
1304
+ if (!inspected.exists) {
1305
+ assertWindowsAdapterTempRoot(runtime);
1306
+ return true;
1307
+ }
1308
+ if (
1309
+ !expectedIdentity ||
1310
+ !sameWindowsDirectoryIdentity(inspected.identity, expectedIdentity)
1311
+ ) {
1312
+ return false;
1313
+ }
889
1314
  runtime.fs.rmdirSync(targetPath);
1315
+ assertWindowsAdapterTempRoot(runtime);
890
1316
  return true;
891
1317
  } catch (error) {
892
- if (error?.code === "ENOENT") return true;
1318
+ if (error?.adapterReason === WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON) {
1319
+ return false;
1320
+ }
1321
+ if (error?.code === "ENOENT") {
1322
+ try {
1323
+ assertWindowsAdapterTempRoot(runtime);
1324
+ return true;
1325
+ } catch {
1326
+ return false;
1327
+ }
1328
+ }
893
1329
  }
894
1330
  if (attempt + 1 < attempts) runtime.sleepSync(delayMs);
895
1331
  }
896
1332
  return false;
897
1333
  }
898
1334
 
1335
+ function cleanupOrTrackWindowsTemporaryDirectory(
1336
+ runtime,
1337
+ targetPath,
1338
+ attempts = 100,
1339
+ entry = null,
1340
+ expectedIdentity = null,
1341
+ ) {
1342
+ if (
1343
+ cleanupWindowsTemporaryDirectory(
1344
+ runtime,
1345
+ targetPath,
1346
+ attempts,
1347
+ 10,
1348
+ expectedIdentity,
1349
+ )
1350
+ ) {
1351
+ const residualEntries = new Set();
1352
+ for (const residual of [...windowsTemporaryCleanupBacklog]) {
1353
+ if (
1354
+ residual.kind === "directory" &&
1355
+ residual.runtime === runtime &&
1356
+ residual.targetPath === targetPath
1357
+ ) {
1358
+ windowsTemporaryCleanupBacklog.delete(residual);
1359
+ if (residual.entry) residualEntries.add(residual.entry);
1360
+ }
1361
+ }
1362
+ for (const residualEntry of residualEntries) {
1363
+ finalizeWindowsTemporaryCleanupEntry(residualEntry);
1364
+ }
1365
+ if (
1366
+ windowsTemporaryCleanupBacklog.size === 0 &&
1367
+ windowsTemporaryCleanupRetryTimer
1368
+ ) {
1369
+ clearTimeout(windowsTemporaryCleanupRetryTimer);
1370
+ windowsTemporaryCleanupRetryTimer = null;
1371
+ }
1372
+ return true;
1373
+ }
1374
+ const existing = [...windowsTemporaryCleanupBacklog].find(
1375
+ (residual) =>
1376
+ residual.kind === "directory" &&
1377
+ residual.runtime === runtime &&
1378
+ residual.targetPath === targetPath,
1379
+ );
1380
+ if (existing) {
1381
+ if (entry) existing.entry = entry;
1382
+ if (!existing.expectedIdentity && expectedIdentity) {
1383
+ existing.expectedIdentity = expectedIdentity;
1384
+ }
1385
+ } else {
1386
+ windowsTemporaryCleanupBacklog.add({
1387
+ kind: "directory",
1388
+ runtime,
1389
+ targetPath,
1390
+ entry,
1391
+ expectedIdentity,
1392
+ });
1393
+ }
1394
+ registerWindowsAdapterExitCleanup();
1395
+ scheduleWindowsTemporaryCleanupRetry();
1396
+ return false;
1397
+ }
1398
+
899
1399
  function sha256(value) {
900
1400
  return crypto.createHash("sha256").update(value).digest("hex");
901
1401
  }
@@ -1143,15 +1643,11 @@ function sameWindowsFileIdentity(left, right) {
1143
1643
  }
1144
1644
 
1145
1645
  function inspectWindowsAdapterAssembly(runtime, assemblyPath) {
1146
- if (typeof runtime.fs.lstatSync === "function") {
1147
- const linkStat = runtime.fs.lstatSync(assemblyPath, { bigint: true });
1148
- if (
1149
- linkStat.isSymbolicLink?.() ||
1150
- (typeof linkStat.isFile === "function" && !linkStat.isFile())
1151
- ) {
1152
- throw new Error("Windows in-memory adapter is not a regular file");
1153
- }
1154
- }
1646
+ const boundBefore = inspectWindowsAdapterBoundPath(
1647
+ runtime,
1648
+ assemblyPath,
1649
+ "file",
1650
+ );
1155
1651
  const before = runtime.fs.statSync(assemblyPath, { bigint: true });
1156
1652
  if (typeof before.isFile === "function" && !before.isFile()) {
1157
1653
  throw new Error("Windows in-memory adapter is not a regular file");
@@ -1164,6 +1660,15 @@ function inspectWindowsAdapterAssembly(runtime, assemblyPath) {
1164
1660
  if (!sameWindowsFileIdentity(beforeIdentity, afterIdentity)) {
1165
1661
  throw new Error("Windows in-memory adapter changed during attestation");
1166
1662
  }
1663
+ const boundAfter = assertWindowsAdapterBoundPathIdentity(
1664
+ runtime,
1665
+ assemblyPath,
1666
+ "file",
1667
+ boundBefore.identity,
1668
+ );
1669
+ if (!sameWindowsFileIdentity(afterIdentity, boundAfter.identity)) {
1670
+ throw new Error("Windows in-memory adapter changed during attestation");
1671
+ }
1167
1672
  return { assemblyDigest, fileIdentity: afterIdentity };
1168
1673
  }
1169
1674
 
@@ -1173,6 +1678,11 @@ function verifyWindowsAdapterEntry(entry, runtime, source) {
1173
1678
  entry.cleaned ||
1174
1679
  entry.runtimeFs !== runtime.fs ||
1175
1680
  entry.tempDirectory !== source.tempDirectory ||
1681
+ entry.idleTtlMs !== source.idleTtlMs ||
1682
+ !sameWindowsAdapterTempRootBinding(
1683
+ entry.tempRootBinding,
1684
+ source.tempRootBinding,
1685
+ ) ||
1176
1686
  entry.sourceDigest !== source.sourceDigest ||
1177
1687
  entry.sourceContractDigest !== source.sourceContractDigest ||
1178
1688
  entry.loaderMode !== source.loaderMode
@@ -1180,6 +1690,15 @@ function verifyWindowsAdapterEntry(entry, runtime, source) {
1180
1690
  return false;
1181
1691
  }
1182
1692
  try {
1693
+ assertWindowsAdapterTempRoot(runtime);
1694
+ if (entry.adapterDirectory) {
1695
+ assertWindowsAdapterBoundPathIdentity(
1696
+ runtime,
1697
+ entry.adapterDirectory,
1698
+ "directory",
1699
+ entry.adapterDirectoryIdentity,
1700
+ );
1701
+ }
1183
1702
  const attestation = inspectWindowsAdapterAssembly(
1184
1703
  runtime,
1185
1704
  entry.assemblyPath,
@@ -1203,14 +1722,18 @@ function cleanupWindowsAdapterEntry(entry, attempts = 100) {
1203
1722
  entry.runtime,
1204
1723
  entry.assemblyPath,
1205
1724
  attempts,
1725
+ entry,
1726
+ entry.fileIdentity,
1206
1727
  );
1207
- const directoryDeleted =
1208
- deleted &&
1209
- cleanupWindowsTemporaryDirectory(
1210
- entry.runtime,
1211
- entry.adapterDirectory,
1212
- attempts,
1213
- );
1728
+ const directoryDeleted = entry.adapterDirectory
1729
+ ? cleanupOrTrackWindowsTemporaryDirectory(
1730
+ entry.runtime,
1731
+ entry.adapterDirectory,
1732
+ attempts,
1733
+ entry,
1734
+ entry.adapterDirectoryIdentity,
1735
+ )
1736
+ : true;
1214
1737
  if (deleted && directoryDeleted) {
1215
1738
  entry.cleaned = true;
1216
1739
  windowsAdapterCacheEntries.delete(entry);
@@ -1253,15 +1776,35 @@ function registerWindowsAdapterExitCleanup() {
1253
1776
  * this hook at a known quiescent point to prove cleanup.
1254
1777
  */
1255
1778
  export function resetWindowsSandboxAdapterCache() {
1256
- let cleaned = retryWindowsAppContainerCleanupBacklog();
1779
+ const appContainerCleaned = retryWindowsAppContainerCleanupBacklog();
1257
1780
  windowsAdapterCache = null;
1258
1781
  for (const entry of [...windowsAdapterCacheEntries]) {
1259
1782
  entry.retired = true;
1260
1783
  entry.refCount = 0;
1261
- if (!cleanupWindowsAdapterEntry(entry)) cleaned = false;
1784
+ cleanupWindowsAdapterEntry(entry);
1262
1785
  }
1263
- if (!retryWindowsTemporaryCleanupBacklog()) cleaned = false;
1264
- return cleaned;
1786
+ retryWindowsTemporaryCleanupBacklog();
1787
+ return (
1788
+ appContainerCleaned &&
1789
+ windowsTemporaryCleanupBacklog.size === 0 &&
1790
+ windowsAdapterCacheEntries.size === 0
1791
+ );
1792
+ }
1793
+
1794
+ function normalizeWindowsAdapterIdleTtlMs(runtime) {
1795
+ const explicitTtl = runtime.windowsAdapterIdleTtlMs;
1796
+ const environmentTtl =
1797
+ runtime.env?.[WINDOWS_ADAPTER_IDLE_TTL_ENV] ??
1798
+ process.env[WINDOWS_ADAPTER_IDLE_TTL_ENV];
1799
+ const configuredTtl =
1800
+ explicitTtl === undefined ? environmentTtl : explicitTtl;
1801
+ const numericTtl =
1802
+ configuredTtl === "" || configuredTtl === null
1803
+ ? Number.NaN
1804
+ : Number(configuredTtl);
1805
+ return Number.isFinite(numericTtl) && numericTtl >= 0
1806
+ ? numericTtl
1807
+ : WINDOWS_ADAPTER_IDLE_TTL_MS;
1265
1808
  }
1266
1809
 
1267
1810
  function scheduleWindowsAdapterIdleCleanup(entry) {
@@ -1273,11 +1816,11 @@ function scheduleWindowsAdapterIdleCleanup(entry) {
1273
1816
  ) {
1274
1817
  return;
1275
1818
  }
1276
- const configuredTtl = Number(entry.runtime.windowsAdapterIdleTtlMs);
1277
- const idleTtlMs =
1278
- Number.isFinite(configuredTtl) && configuredTtl >= 0
1279
- ? configuredTtl
1280
- : WINDOWS_ADAPTER_IDLE_TTL_MS;
1819
+ const idleTtlMs = entry.idleTtlMs;
1820
+ if (idleTtlMs === 0) {
1821
+ retireWindowsAdapterEntry(entry);
1822
+ return;
1823
+ }
1281
1824
  entry.idleTimer = setTimeout(() => {
1282
1825
  entry.idleTimer = null;
1283
1826
  if (
@@ -1293,6 +1836,7 @@ function scheduleWindowsAdapterIdleCleanup(entry) {
1293
1836
  }
1294
1837
 
1295
1838
  function loadWindowsAdapterSource(runtime, loaderMode) {
1839
+ const tempRootBinding = assertWindowsAdapterTempRoot(runtime);
1296
1840
  const bundled = runtime.windowsAdapterContent === undefined;
1297
1841
  const content = bundled
1298
1842
  ? loaderMode === "managed-executable"
@@ -1307,7 +1851,10 @@ function loadWindowsAdapterSource(runtime, loaderMode) {
1307
1851
  : WINDOWS_SANDBOX_HELPER_ASSEMBLY_DIGEST
1308
1852
  : sha256(content),
1309
1853
  sourceContractDigest: bundled ? WINDOWS_SANDBOX_HELPER_SOURCE_DIGEST : null,
1310
- tempDirectory: runtime.tmpdir(),
1854
+ tempDirectory: tempRootBinding.rootPath,
1855
+ tempRootBinding,
1856
+ tempRootAttestation: tempRootBinding.attestation,
1857
+ idleTtlMs: runtime.windowsAdapterIdleTtlMs,
1311
1858
  loaderMode,
1312
1859
  };
1313
1860
  }
@@ -1329,54 +1876,121 @@ function materializeWindowsAdapter(runtime, source) {
1329
1876
  const assemblyPath = runtime.joinPath(
1330
1877
  adapterDirectory || source.tempDirectory,
1331
1878
  source.loaderMode === "managed-executable"
1332
- ? "windows-sandbox-helper.exe"
1879
+ ? WINDOWS_ADAPTER_HELPER_NAME
1333
1880
  : `${adapterBaseName}.dll`,
1334
1881
  );
1335
- if (
1336
- (adapterDirectory && runtime.fs.existsSync(adapterDirectory)) ||
1337
- runtime.fs.existsSync(assemblyPath)
1338
- ) {
1339
- return { reason: "windows_native_adapter_random_path_collision" };
1340
- }
1882
+ let adapterDirectoryIdentity = null;
1883
+ let assemblyFileIdentity = null;
1341
1884
  try {
1885
+ assertWindowsAdapterTempRoot(runtime);
1342
1886
  if (adapterDirectory) {
1887
+ if (
1888
+ inspectWindowsAdapterBoundPath(runtime, adapterDirectory, "directory", {
1889
+ allowMissing: true,
1890
+ }).exists
1891
+ ) {
1892
+ return { reason: "windows_native_adapter_random_path_collision" };
1893
+ }
1343
1894
  runtime.fs.mkdirSync(adapterDirectory, {
1344
1895
  mode: 0o700,
1345
1896
  recursive: false,
1346
1897
  });
1898
+ adapterDirectoryIdentity = inspectWindowsAdapterBoundPath(
1899
+ runtime,
1900
+ adapterDirectory,
1901
+ "directory",
1902
+ ).identity;
1903
+ }
1904
+ if (
1905
+ inspectWindowsAdapterBoundPath(runtime, assemblyPath, "file", {
1906
+ allowMissing: true,
1907
+ }).exists
1908
+ ) {
1909
+ const collision = new Error(
1910
+ "Windows native adapter random path collision",
1911
+ );
1912
+ collision.code = "EEXIST";
1913
+ throw collision;
1347
1914
  }
1348
1915
  runtime.fs.writeFileSync(assemblyPath, source.content, {
1349
1916
  mode: adapterDirectory ? 0o500 : 0o600,
1350
1917
  flag: "wx",
1351
1918
  });
1919
+ assemblyFileIdentity = inspectWindowsAdapterAssembly(
1920
+ runtime,
1921
+ assemblyPath,
1922
+ ).fileIdentity;
1352
1923
  } catch (error) {
1924
+ if (error?.code === "EEXIST") {
1925
+ const directoryDeleted = cleanupOrTrackWindowsTemporaryDirectory(
1926
+ runtime,
1927
+ adapterDirectory,
1928
+ 100,
1929
+ null,
1930
+ adapterDirectoryIdentity,
1931
+ );
1932
+ return {
1933
+ reason: directoryDeleted
1934
+ ? "windows_native_adapter_random_path_collision"
1935
+ : "windows_native_adapter_compile_cleanup_unverified",
1936
+ };
1937
+ }
1938
+ if (!assemblyFileIdentity) {
1939
+ try {
1940
+ const inspected = inspectWindowsAdapterBoundPath(
1941
+ runtime,
1942
+ assemblyPath,
1943
+ "file",
1944
+ { allowMissing: true },
1945
+ );
1946
+ assemblyFileIdentity = inspected.exists ? inspected.identity : null;
1947
+ } catch {
1948
+ assemblyFileIdentity = null;
1949
+ }
1950
+ }
1353
1951
  const assemblyDeleted = cleanupOrTrackWindowsTemporaryPath(
1354
1952
  runtime,
1355
1953
  assemblyPath,
1954
+ 100,
1955
+ null,
1956
+ assemblyFileIdentity,
1356
1957
  );
1357
- const directoryDeleted =
1358
- assemblyDeleted &&
1359
- cleanupWindowsTemporaryDirectory(runtime, adapterDirectory);
1958
+ const directoryDeleted = cleanupOrTrackWindowsTemporaryDirectory(
1959
+ runtime,
1960
+ adapterDirectory,
1961
+ 100,
1962
+ null,
1963
+ adapterDirectoryIdentity,
1964
+ );
1965
+ if (error?.adapterReason === WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON) {
1966
+ return { reason: WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON };
1967
+ }
1360
1968
  if (!assemblyDeleted || !directoryDeleted) {
1361
1969
  return { reason: "windows_native_adapter_compile_cleanup_unverified" };
1362
1970
  }
1363
- if (error?.code === "EEXIST") {
1364
- return { reason: "windows_native_adapter_random_path_collision" };
1365
- }
1366
1971
  return { reason: "windows_native_adapter_materialize_failed" };
1367
1972
  }
1368
1973
  return {
1369
1974
  adapterDirectory,
1975
+ adapterDirectoryIdentity,
1370
1976
  assemblyPath,
1977
+ assemblyFileIdentity,
1371
1978
  cleanupAssembly: () => {
1372
1979
  const assemblyDeleted = cleanupOrTrackWindowsTemporaryPath(
1373
1980
  runtime,
1374
1981
  assemblyPath,
1982
+ 100,
1983
+ null,
1984
+ assemblyFileIdentity,
1375
1985
  );
1376
- return (
1377
- assemblyDeleted &&
1378
- cleanupWindowsTemporaryDirectory(runtime, adapterDirectory)
1986
+ const directoryDeleted = cleanupOrTrackWindowsTemporaryDirectory(
1987
+ runtime,
1988
+ adapterDirectory,
1989
+ 100,
1990
+ null,
1991
+ adapterDirectoryIdentity,
1379
1992
  );
1993
+ return assemblyDeleted && directoryDeleted;
1380
1994
  },
1381
1995
  };
1382
1996
  }
@@ -1388,9 +2002,7 @@ function materializeWindowsAdapterInvocation(runtime, helperArgs) {
1388
2002
  .randomBytes(24)
1389
2003
  .toString("hex")}.json`,
1390
2004
  );
1391
- if (runtime.fs.existsSync(payloadPath)) {
1392
- return { reason: "windows_adapter_invocation_random_path_collision" };
1393
- }
2005
+ let fileIdentity = null;
1394
2006
  let content;
1395
2007
  try {
1396
2008
  content = Buffer.from(
@@ -1410,16 +2022,57 @@ function materializeWindowsAdapterInvocation(runtime, helperArgs) {
1410
2022
  return { reason: "windows_adapter_invocation_too_large" };
1411
2023
  }
1412
2024
  try {
2025
+ if (
2026
+ inspectWindowsAdapterBoundPath(runtime, payloadPath, "file", {
2027
+ allowMissing: true,
2028
+ }).exists
2029
+ ) {
2030
+ return { reason: "windows_adapter_invocation_random_path_collision" };
2031
+ }
1413
2032
  runtime.fs.writeFileSync(payloadPath, content, {
1414
2033
  mode: 0o600,
1415
2034
  flag: "wx",
1416
2035
  });
2036
+ const inspected = inspectWindowsAdapterAssembly(runtime, payloadPath);
2037
+ if (inspected.assemblyDigest !== sha256(content)) {
2038
+ throw new Error("Windows adapter invocation changed after creation");
2039
+ }
2040
+ fileIdentity = inspected.fileIdentity;
1417
2041
  } catch (error) {
2042
+ if (!fileIdentity) {
2043
+ try {
2044
+ const inspected = inspectWindowsAdapterBoundPath(
2045
+ runtime,
2046
+ payloadPath,
2047
+ "file",
2048
+ { allowMissing: true },
2049
+ );
2050
+ fileIdentity = inspected.exists ? inspected.identity : null;
2051
+ } catch {
2052
+ fileIdentity = null;
2053
+ }
2054
+ }
2055
+ if (error?.adapterReason === WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON) {
2056
+ cleanupOrTrackWindowsTemporaryPath(
2057
+ runtime,
2058
+ payloadPath,
2059
+ 100,
2060
+ null,
2061
+ fileIdentity,
2062
+ );
2063
+ return { reason: WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON };
2064
+ }
1418
2065
  if (error?.code === "EEXIST") {
1419
2066
  return { reason: "windows_adapter_invocation_random_path_collision" };
1420
2067
  }
1421
2068
  return {
1422
- reason: cleanupOrTrackWindowsTemporaryPath(runtime, payloadPath)
2069
+ reason: cleanupOrTrackWindowsTemporaryPath(
2070
+ runtime,
2071
+ payloadPath,
2072
+ 100,
2073
+ null,
2074
+ fileIdentity,
2075
+ )
1423
2076
  ? "windows_adapter_invocation_materialize_failed"
1424
2077
  : "windows_adapter_invocation_cleanup_unverified",
1425
2078
  };
@@ -1427,7 +2080,24 @@ function materializeWindowsAdapterInvocation(runtime, helperArgs) {
1427
2080
  return {
1428
2081
  payloadPath,
1429
2082
  payloadDigest: sha256(content),
1430
- cleanup: () => cleanupOrTrackWindowsTemporaryPath(runtime, payloadPath),
2083
+ fileIdentity,
2084
+ verify: () => {
2085
+ const inspected = assertWindowsAdapterBoundPathIdentity(
2086
+ runtime,
2087
+ payloadPath,
2088
+ "file",
2089
+ fileIdentity,
2090
+ );
2091
+ return inspected.exists;
2092
+ },
2093
+ cleanup: () =>
2094
+ cleanupOrTrackWindowsTemporaryPath(
2095
+ runtime,
2096
+ payloadPath,
2097
+ 100,
2098
+ null,
2099
+ fileIdentity,
2100
+ ),
1431
2101
  };
1432
2102
  }
1433
2103
 
@@ -1483,6 +2153,7 @@ function buildWindowsAdapterInvocation(entry, invocation) {
1483
2153
  invocation.payloadPath,
1484
2154
  invocation.payloadDigest,
1485
2155
  ],
2156
+ verify: invocation.verify,
1486
2157
  cleanup: invocation.cleanup,
1487
2158
  };
1488
2159
  }
@@ -1497,6 +2168,7 @@ function buildWindowsAdapterInvocation(entry, invocation) {
1497
2168
  "-EncodedCommand",
1498
2169
  windowsPowerShellBootstrap(entry, invocation),
1499
2170
  ],
2171
+ verify: invocation.verify,
1500
2172
  cleanup: invocation.cleanup,
1501
2173
  };
1502
2174
  }
@@ -1583,11 +2255,14 @@ function createFreshWindowsAdapter(runtime, source, hostEnvironment) {
1583
2255
  runtime,
1584
2256
  runtimeFs: runtime.fs,
1585
2257
  tempDirectory: source.tempDirectory,
2258
+ tempRootBinding: source.tempRootBinding,
2259
+ idleTtlMs: source.idleTtlMs,
1586
2260
  sourceDigest: source.sourceDigest,
1587
2261
  sourceContractDigest: source.sourceContractDigest,
1588
2262
  loaderMode: source.loaderMode,
1589
2263
  powershellPath: powershellHosts[0] || null,
1590
2264
  adapterDirectory: materialized.adapterDirectory,
2265
+ adapterDirectoryIdentity: materialized.adapterDirectoryIdentity,
1591
2266
  assemblyPath: materialized.assemblyPath,
1592
2267
  assemblyDigest: attestation.assemblyDigest,
1593
2268
  fileIdentity: attestation.fileIdentity,
@@ -1743,7 +2418,9 @@ function createWindowsAdapterController(runtime, source, hostEnvironment) {
1743
2418
  error.adapterReason = payload.reason;
1744
2419
  throw error;
1745
2420
  }
1746
- return buildWindowsAdapterInvocation(entry, payload);
2421
+ const invocation = buildWindowsAdapterInvocation(entry, payload);
2422
+ invocation.verify();
2423
+ return invocation;
1747
2424
  };
1748
2425
 
1749
2426
  return {
@@ -1754,6 +2431,7 @@ function createWindowsAdapterController(runtime, source, hostEnvironment) {
1754
2431
  let result;
1755
2432
  let failure;
1756
2433
  try {
2434
+ invocation.verify();
1757
2435
  result = runtime.spawnSync(invocation.command, invocation.args, {
1758
2436
  ...options,
1759
2437
  cwd: runtime.joinPath(runtime.windowsDir(), "System32"),
@@ -1781,20 +2459,40 @@ function createWindowsAdapterController(runtime, source, hostEnvironment) {
1781
2459
  };
1782
2460
  }
1783
2461
 
2462
+ function readWindowsAdapterBoundFile(runtime, filePath, encoding = null) {
2463
+ const before = inspectWindowsAdapterBoundPath(runtime, filePath, "file");
2464
+ const content = runtime.fs.readFileSync(filePath, encoding || undefined);
2465
+ const after = assertWindowsAdapterBoundPathIdentity(
2466
+ runtime,
2467
+ filePath,
2468
+ "file",
2469
+ before.identity,
2470
+ );
2471
+ if (!sameWindowsFileIdentity(before.identity, after.identity)) {
2472
+ throw windowsAdapterTempRootChangedError();
2473
+ }
2474
+ return { content, fileIdentity: after.identity };
2475
+ }
2476
+
1784
2477
  function waitForWindowsTargetIdentity(
1785
2478
  proc,
1786
2479
  identityPath,
1787
2480
  runtime,
1788
2481
  expectedAppContainer = null,
1789
2482
  timeoutMs = 30_000,
2483
+ onIdentityFileAttested = null,
1790
2484
  ) {
1791
2485
  const deadline = runtime.now() + timeoutMs;
1792
2486
  let lastError;
1793
2487
  while (runtime.now() < deadline) {
1794
2488
  try {
1795
- const identity = JSON.parse(
1796
- runtime.fs.readFileSync(identityPath, "utf8"),
2489
+ const identityFile = readWindowsAdapterBoundFile(
2490
+ runtime,
2491
+ identityPath,
2492
+ "utf8",
1797
2493
  );
2494
+ onIdentityFileAttested?.(identityFile.fileIdentity);
2495
+ const identity = JSON.parse(identityFile.content);
1798
2496
  if (identity?.error) {
1799
2497
  throw new Error(
1800
2498
  `Windows sandbox helper rejected the target: ${identity.error}`,
@@ -2610,11 +3308,33 @@ export function applyWindowsSandbox(
2610
3308
  ) {
2611
3309
  return unavailablePlan("windows_git_nested_process_compatibility");
2612
3310
  }
3311
+ const windowsAdapterTempRootBinding = resolveWindowsAdapterTempRoot(
3312
+ runtime,
3313
+ runtimeOverrides,
3314
+ );
3315
+ if (!windowsAdapterTempRootBinding) {
3316
+ return unavailablePlan(WINDOWS_ADAPTER_TEMP_ROOT_UNTRUSTED_REASON);
3317
+ }
3318
+ const windowsAdapterIdleTtlMs = normalizeWindowsAdapterIdleTtlMs(runtime);
3319
+ runtime = {
3320
+ ...runtime,
3321
+ windowsAdapterTempRoot: windowsAdapterTempRootBinding.rootPath,
3322
+ windowsAdapterTempRootBinding,
3323
+ windowsAdapterIdleTtlMs,
3324
+ tmpdir: () => windowsAdapterTempRootBinding.rootPath,
3325
+ };
2613
3326
  const loaderMode =
2614
3327
  profile !== "strict" && !requiresAppContainer && !entrySnapshot.locks
2615
3328
  ? "managed-executable"
2616
3329
  : "powershell-byte-assembly";
2617
- const adapterSource = loadWindowsAdapterSource(runtime, loaderMode);
3330
+ let adapterSource;
3331
+ try {
3332
+ adapterSource = loadWindowsAdapterSource(runtime, loaderMode);
3333
+ } catch (error) {
3334
+ return unavailablePlan(
3335
+ error?.adapterReason || WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON,
3336
+ );
3337
+ }
2618
3338
  if (!adapterSource.content) {
2619
3339
  return unavailablePlan(adapterSource.reason);
2620
3340
  }
@@ -2659,6 +3379,22 @@ export function applyWindowsSandbox(
2659
3379
  .toString("hex")}.json`,
2660
3380
  )
2661
3381
  : null;
3382
+ let identityFileIdentity = null;
3383
+ if (identityPath) {
3384
+ try {
3385
+ if (
3386
+ inspectWindowsAdapterBoundPath(runtime, identityPath, "file", {
3387
+ allowMissing: true,
3388
+ }).exists
3389
+ ) {
3390
+ return unavailablePlan("windows_identity_random_path_collision");
3391
+ }
3392
+ } catch (error) {
3393
+ return unavailablePlan(
3394
+ error?.adapterReason || WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON,
3395
+ );
3396
+ }
3397
+ }
2662
3398
  const launchSpec = {
2663
3399
  cpuSeconds: Number(limits.cpu || 0),
2664
3400
  processMemoryBytes: Number(
@@ -2699,7 +3435,13 @@ export function applyWindowsSandbox(
2699
3435
  if (identityPath) launchSpec.identityPath = identityPath;
2700
3436
  const cleanupIdentity = () => {
2701
3437
  if (!identityPath) return true;
2702
- return cleanupOrTrackWindowsTemporaryPath(runtime, identityPath);
3438
+ return cleanupOrTrackWindowsTemporaryPath(
3439
+ runtime,
3440
+ identityPath,
3441
+ 100,
3442
+ null,
3443
+ identityFileIdentity,
3444
+ );
2703
3445
  };
2704
3446
  const appContainerProfileName = requiresAppContainer
2705
3447
  ? `ChainlessChain.CliSandbox.${runtime.randomBytes(12).toString("hex")}`
@@ -2973,6 +3715,10 @@ export function applyWindowsSandbox(
2973
3715
  identityPath,
2974
3716
  runtime,
2975
3717
  appContainer,
3718
+ 30_000,
3719
+ (fileIdentity) => {
3720
+ identityFileIdentity = fileIdentity;
3721
+ },
2976
3722
  );
2977
3723
  if (!cleanupIdentity()) {
2978
3724
  throw new Error(
@@ -3064,6 +3810,7 @@ export function applyWindowsSandbox(
3064
3810
  enforcement: backend,
3065
3811
  policyAttested: requiresAppContainer ? true : null,
3066
3812
  policyDigest: appContainerPolicyDigest,
3813
+ adapterTempRootAttestation: adapterSource.tempRootAttestation,
3067
3814
  runtimeProbe: entrySnapshotRuntimeProbe,
3068
3815
  guarantees: [
3069
3816
  ...(requiresAppContainer
@@ -3143,13 +3890,12 @@ function linuxBubblewrapProbe(
3143
3890
  const supervisorDescriptorBound =
3144
3891
  supervisorBinding?.mechanism === LINUX_BWRAP_SUPERVISOR_BINDING_MECHANISM;
3145
3892
  const pluginTreeContentSnapshot =
3146
- !native &&
3147
3893
  attempted &&
3148
3894
  snapshot &&
3149
3895
  runnable &&
3150
3896
  reason === null &&
3151
3897
  supervisorDescriptorBound &&
3152
- pluginTreeSnapshot?.scope === LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE &&
3898
+ pluginTreeSnapshot?.scope === LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE &&
3153
3899
  pluginTreeSnapshot?.mechanism === LINUX_ENTRY_SNAPSHOT_MECHANISM &&
3154
3900
  Number.isSafeInteger(pluginTreeSnapshot?.files) &&
3155
3901
  pluginTreeSnapshot.files > 0 &&
@@ -3160,7 +3906,7 @@ function linuxBubblewrapProbe(
3160
3906
  typeof pluginTreeSnapshot?.digest === "string" &&
3161
3907
  /^[a-f0-9]{64}$/.test(pluginTreeSnapshot.digest) &&
3162
3908
  pluginTreeSnapshot?.consistency ===
3163
- LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY &&
3909
+ LINUX_PLUGIN_TREE_SNAPSHOT_CONSISTENCY &&
3164
3910
  pluginTreeSnapshot?.contractBound === false &&
3165
3911
  pluginTreeSnapshot?.atomic === false;
3166
3912
  const initialDynamicLoadClosureDescriptorBound =
@@ -3775,16 +4521,20 @@ function linuxEntrySnapshotContract(targetRuntime) {
3775
4521
  throw new Error("entry_snapshot_runtime_unsupported");
3776
4522
  }
3777
4523
 
3778
- function linuxNodePluginTreeFileSnapshotContract(sourceMode) {
4524
+ function linuxPluginTreeFileSnapshotContract(sourceMode, targetRuntime) {
4525
+ const native = targetRuntime?.startsWith("native-");
4526
+ const errorPrefix = native
4527
+ ? "native_plugin_tree_snapshot"
4528
+ : "node_plugin_tree_snapshot";
3779
4529
  if (!Number.isSafeInteger(sourceMode) || sourceMode < 0) {
3780
- throw new Error("node_plugin_tree_snapshot_source_mode_invalid");
4530
+ throw new Error(`${errorPrefix}_source_mode_invalid`);
3781
4531
  }
3782
4532
  return {
3783
- scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
4533
+ scope: LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE,
3784
4534
  targetMode: (sourceMode & 0o111) !== 0 ? "0500" : "0400",
3785
4535
  sourceMode: LINUX_ENTRY_SNAPSHOT_SOURCE_MODE,
3786
4536
  minimumBytes: 0,
3787
- errorPrefix: "node_plugin_tree_snapshot",
4537
+ errorPrefix,
3788
4538
  };
3789
4539
  }
3790
4540
 
@@ -3865,9 +4615,9 @@ function createLinuxRegularFileSnapshot(
3865
4615
  throw snapshotError("source_changed");
3866
4616
  }
3867
4617
  if (
3868
- snapshotContract.scope === LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE &&
4618
+ snapshotContract.scope === LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE &&
3869
4619
  snapshotContract.targetMode !==
3870
- linuxNodePluginTreeFileSnapshotContract(sourceFileMode).targetMode
4620
+ ((sourceFileMode & 0o111) !== 0 ? "0500" : "0400")
3871
4621
  ) {
3872
4622
  throw snapshotError("source_mode_changed");
3873
4623
  }
@@ -4328,7 +5078,21 @@ function applyLinuxMcpCapsuleCodeSnapshot(
4328
5078
  });
4329
5079
  }
4330
5080
 
4331
- function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
5081
+ /**
5082
+ * Bind the synthetic plugin tree to the anonymous content snapshots mounted
5083
+ * for launch. Directory membership is fixed by pinLinuxPluginTree; contents
5084
+ * are copied and sealed per file, so the evidence deliberately remains
5085
+ * `atomic: false` rather than claiming one filesystem-wide transaction.
5086
+ */
5087
+ function buildLinuxPluginTreeSnapshotAttestation(
5088
+ mounts,
5089
+ entryDestination,
5090
+ targetRuntime,
5091
+ ) {
5092
+ const native = targetRuntime?.startsWith("native-");
5093
+ const errorPrefix = native
5094
+ ? "native_plugin_tree_snapshot"
5095
+ : "node_plugin_tree_snapshot";
4332
5096
  const members = [];
4333
5097
  const destinations = new Set();
4334
5098
  for (const mount of mounts || []) {
@@ -4337,18 +5101,23 @@ function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
4337
5101
  path.posix.normalize(mount.destination) !== mount.destination ||
4338
5102
  destinations.has(mount.destination)
4339
5103
  ) {
4340
- throw new Error("node_plugin_tree_snapshot_destination_invalid");
5104
+ throw new Error(`${errorPrefix}_destination_invalid`);
4341
5105
  }
4342
5106
  destinations.add(mount.destination);
4343
5107
  const snapshot = mount.contentSnapshot;
4344
5108
  const entry = mount.destination === entryDestination;
4345
5109
  const expectedScope = entry
4346
- ? LINUX_NODE_ENTRY_SNAPSHOT_SCOPE
4347
- : LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE;
5110
+ ? native
5111
+ ? LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE
5112
+ : LINUX_NODE_ENTRY_SNAPSHOT_SCOPE
5113
+ : LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE;
4348
5114
  const sourceMode = Number.parseInt(snapshot?.sourceFileMode, 8);
4349
5115
  const expectedTargetMode = entry
4350
- ? LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE
4351
- : linuxNodePluginTreeFileSnapshotContract(sourceMode).targetMode;
5116
+ ? native
5117
+ ? LINUX_NATIVE_ENTRY_SNAPSHOT_TARGET_MODE
5118
+ : LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE
5119
+ : linuxPluginTreeFileSnapshotContract(sourceMode, targetRuntime)
5120
+ .targetMode;
4352
5121
  if (
4353
5122
  snapshot?.scope !== expectedScope ||
4354
5123
  snapshot?.mechanism !== LINUX_ENTRY_SNAPSHOT_MECHANISM ||
@@ -4371,7 +5140,7 @@ function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
4371
5140
  mount.mountMode !== "ro-bind-data" ||
4372
5141
  mount.mountPermissions !== expectedTargetMode
4373
5142
  ) {
4374
- throw new Error("node_plugin_tree_snapshot_member_invalid");
5143
+ throw new Error(`${errorPrefix}_member_invalid`);
4375
5144
  }
4376
5145
  members.push({
4377
5146
  destination: mount.destination,
@@ -4398,58 +5167,66 @@ function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
4398
5167
  members.length > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES ||
4399
5168
  !members.some((member) => member.destination === entryDestination)
4400
5169
  ) {
4401
- throw new Error("node_plugin_tree_snapshot_file_count_invalid");
5170
+ throw new Error(`${errorPrefix}_file_count_invalid`);
4402
5171
  }
4403
5172
  const bytes = members.reduce((total, member) => total + member.bytes, 0);
4404
5173
  if (
4405
5174
  !Number.isSafeInteger(bytes) ||
4406
5175
  bytes > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES
4407
5176
  ) {
4408
- throw new Error("node_plugin_tree_snapshot_bytes_exceeded");
5177
+ throw new Error(`${errorPrefix}_bytes_exceeded`);
4409
5178
  }
4410
5179
  const digest = crypto
4411
5180
  .createHash("sha256")
4412
5181
  .update(
4413
5182
  JSON.stringify({
4414
5183
  version: 1,
4415
- scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
4416
- consistency: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
5184
+ scope: LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE,
5185
+ ...(native ? { targetRuntime } : {}),
5186
+ consistency: LINUX_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
4417
5187
  members,
4418
5188
  }),
4419
5189
  )
4420
5190
  .digest("hex");
4421
5191
  return Object.freeze({
4422
- scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
5192
+ scope: LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE,
4423
5193
  mechanism: LINUX_ENTRY_SNAPSHOT_MECHANISM,
4424
5194
  files: members.length,
4425
5195
  bytes,
4426
5196
  digest,
4427
- consistency: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
5197
+ consistency: LINUX_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
4428
5198
  contractBound: false,
4429
5199
  atomic: false,
4430
5200
  });
4431
5201
  }
4432
5202
 
4433
- function attestLinuxNodePluginTreeSnapshot(
5203
+ function attestLinuxPluginTreeSnapshot(
4434
5204
  runtime,
4435
5205
  mounts,
4436
5206
  entryDestination,
5207
+ targetRuntime,
4437
5208
  expected,
4438
5209
  ) {
5210
+ const native = targetRuntime?.startsWith("native-");
5211
+ const errorPrefix = native
5212
+ ? "native_plugin_tree_snapshot"
5213
+ : "node_plugin_tree_snapshot";
4439
5214
  for (const mount of mounts || []) {
4440
5215
  if (!linuxPathWithin("/opt/chainless/plugin", mount?.destination)) continue;
4441
5216
  const snapshot = mount.contentSnapshot;
4442
5217
  const contract =
4443
5218
  mount.destination === entryDestination
4444
- ? linuxEntrySnapshotContract("node")
4445
- : linuxNodePluginTreeFileSnapshotContract(
5219
+ ? linuxEntrySnapshotContract(targetRuntime)
5220
+ : linuxPluginTreeFileSnapshotContract(
4446
5221
  Number.parseInt(snapshot?.sourceFileMode, 8),
5222
+ targetRuntime,
4447
5223
  );
4448
5224
  attestLinuxRegularFileSnapshot(runtime, mount, snapshot, contract);
4449
5225
  }
4450
- const actual = buildLinuxNodePluginTreeSnapshotAttestation(
5226
+ const actual = buildLinuxPluginTreeSnapshotAttestation(
4451
5227
  mounts,
4452
5228
  entryDestination,
5229
+ targetRuntime,
4453
5230
  );
4454
5231
  for (const field of [
4455
5232
  "scope",
@@ -4462,7 +5239,7 @@ function attestLinuxNodePluginTreeSnapshot(
4462
5239
  "atomic",
4463
5240
  ]) {
4464
5241
  if (actual[field] !== expected?.[field]) {
4465
- throw new Error("node_plugin_tree_snapshot_identity_changed");
5242
+ throw new Error(`${errorPrefix}_identity_changed`);
4466
5243
  }
4467
5244
  }
4468
5245
  }
@@ -6360,30 +7137,33 @@ export function applyLinuxSandbox(
6360
7137
  const rawPluginMounts = pluginMountIndexes.map(
6361
7138
  (index) => pinnedMounts[index],
6362
7139
  );
6363
- if (validation.entryRuntime === "node") {
6364
- if (
6365
- pluginMountIndexes.length < 1 ||
6366
- pluginMountIndexes.length > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES
6367
- ) {
6368
- throw new Error("node_plugin_tree_snapshot_file_count_invalid");
6369
- }
6370
- const pluginBytes = pluginMountIndexes.reduce((total, index) => {
6371
- const bytes = pinnedMounts[index]?.bytes;
6372
- if (
6373
- !Number.isSafeInteger(bytes) ||
6374
- bytes < 0 ||
6375
- bytes > LINUX_ATTESTED_FILE_MAX_BYTES
6376
- ) {
6377
- throw new Error("node_plugin_tree_snapshot_member_size_invalid");
6378
- }
6379
- return total + bytes;
6380
- }, 0);
7140
+ const pluginSnapshotErrorPrefix = validation.entryRuntime.startsWith(
7141
+ "native-",
7142
+ )
7143
+ ? "native_plugin_tree_snapshot"
7144
+ : "node_plugin_tree_snapshot";
7145
+ if (
7146
+ pluginMountIndexes.length < 1 ||
7147
+ pluginMountIndexes.length > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES
7148
+ ) {
7149
+ throw new Error(`${pluginSnapshotErrorPrefix}_file_count_invalid`);
7150
+ }
7151
+ const pluginBytes = pluginMountIndexes.reduce((total, index) => {
7152
+ const bytes = pinnedMounts[index]?.bytes;
6381
7153
  if (
6382
- !Number.isSafeInteger(pluginBytes) ||
6383
- pluginBytes > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES
7154
+ !Number.isSafeInteger(bytes) ||
7155
+ bytes < 0 ||
7156
+ bytes > LINUX_ATTESTED_FILE_MAX_BYTES
6384
7157
  ) {
6385
- throw new Error("node_plugin_tree_snapshot_bytes_exceeded");
7158
+ throw new Error(`${pluginSnapshotErrorPrefix}_member_size_invalid`);
6386
7159
  }
7160
+ return total + bytes;
7161
+ }, 0);
7162
+ if (
7163
+ !Number.isSafeInteger(pluginBytes) ||
7164
+ pluginBytes > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES
7165
+ ) {
7166
+ throw new Error(`${pluginSnapshotErrorPrefix}_bytes_exceeded`);
6387
7167
  }
6388
7168
  const rawEntryMount = entryMount;
6389
7169
  const snapshot = createLinuxEntrySnapshot(
@@ -6397,37 +7177,36 @@ export function applyLinuxSandbox(
6397
7177
  finalMounts[entryIndex] = snapshot.finalMount;
6398
7178
  probeMounts[entryIndex] = snapshot.probeMount;
6399
7179
  entrySnapshot = snapshot;
6400
- if (validation.entryRuntime === "node") {
6401
- for (const index of pluginMountIndexes) {
6402
- if (index === entryIndex) continue;
6403
- const rawMount = pinnedMounts[index];
6404
- const source = runtime.fs.fstatSync(rawMount.fd);
6405
- const fileSnapshot = createLinuxRegularFileSnapshot(
6406
- runtime,
6407
- rawMount,
6408
- {
6409
- fileId: rawMount.fileId,
6410
- bytes: rawMount.bytes,
6411
- mtimeMs: rawMount.mtimeMs,
6412
- },
6413
- linuxNodePluginTreeFileSnapshotContract(Number(source.mode)),
6414
- );
6415
- pluginTreeFileSnapshots.push(fileSnapshot);
6416
- finalMounts[index] = fileSnapshot.finalMount;
6417
- probeMounts[index] = fileSnapshot.probeMount;
6418
- }
6419
- pluginTreeSnapshot = buildLinuxNodePluginTreeSnapshotAttestation(
6420
- finalMounts,
6421
- sandboxEntry,
7180
+ for (const index of pluginMountIndexes) {
7181
+ if (index === entryIndex) continue;
7182
+ const rawMount = pinnedMounts[index];
7183
+ const source = runtime.fs.fstatSync(rawMount.fd);
7184
+ const fileSnapshot = createLinuxRegularFileSnapshot(
7185
+ runtime,
7186
+ rawMount,
7187
+ {
7188
+ fileId: rawMount.fileId,
7189
+ bytes: rawMount.bytes,
7190
+ mtimeMs: rawMount.mtimeMs,
7191
+ },
7192
+ linuxPluginTreeFileSnapshotContract(
7193
+ Number(source.mode),
7194
+ validation.entryRuntime,
7195
+ ),
6422
7196
  );
7197
+ pluginTreeFileSnapshots.push(fileSnapshot);
7198
+ finalMounts[index] = fileSnapshot.finalMount;
7199
+ probeMounts[index] = fileSnapshot.probeMount;
6423
7200
  }
7201
+ pluginTreeSnapshot = buildLinuxPluginTreeSnapshotAttestation(
7202
+ finalMounts,
7203
+ sandboxEntry,
7204
+ validation.entryRuntime,
7205
+ );
6424
7206
  pinnedMounts = finalMounts;
6425
7207
  probePinnedMounts = probeMounts;
6426
7208
  entryMount = entrySnapshot.finalMount;
6427
- closeLinuxPinnedMounts(
6428
- runtime,
6429
- validation.entryRuntime === "node" ? rawPluginMounts : [rawEntryMount],
6430
- );
7209
+ closeLinuxPinnedMounts(runtime, rawPluginMounts);
6431
7210
  } catch (error) {
6432
7211
  closeLinuxPinnedMounts(runtime, pinnedMounts);
6433
7212
  closeLinuxPinnedMounts(runtime, [
@@ -6607,7 +7386,7 @@ export function applyLinuxSandbox(
6607
7386
  .createHash("sha256")
6608
7387
  .update(
6609
7388
  JSON.stringify({
6610
- version: validation.entryRuntime === "node" ? 4 : 3,
7389
+ version: 4,
6611
7390
  backend: LINUX_BWRAP_BACKEND,
6612
7391
  supervisor: supervisorPin.attestation,
6613
7392
  contract: {
@@ -6651,9 +7430,7 @@ export function applyLinuxSandbox(
6651
7430
  ...(nativeDynamicClosure
6652
7431
  ? { initialDynamicLoadClosure: nativeDynamicClosure }
6653
7432
  : {}),
6654
- ...(validation.entryRuntime === "node"
6655
- ? { pluginTreeContentSnapshot: pluginTreeSnapshot }
6656
- : {}),
7433
+ pluginTreeContentSnapshot: pluginTreeSnapshot,
6657
7434
  seccomp: {
6658
7435
  arch: seccompFilter.arch,
6659
7436
  policy: seccompFilter.policy,
@@ -6726,21 +7503,13 @@ export function applyLinuxSandbox(
6726
7503
  });
6727
7504
  }
6728
7505
  try {
6729
- if (validation.entryRuntime === "node") {
6730
- attestLinuxNodePluginTreeSnapshot(
6731
- runtime,
6732
- pinnedMounts,
6733
- sandboxEntry,
6734
- pluginTreeSnapshot,
6735
- );
6736
- } else {
6737
- attestLinuxEntrySnapshot(
6738
- runtime,
6739
- entryMount,
6740
- entrySnapshot.attestation,
6741
- validation.entryRuntime,
6742
- );
6743
- }
7506
+ attestLinuxPluginTreeSnapshot(
7507
+ runtime,
7508
+ pinnedMounts,
7509
+ sandboxEntry,
7510
+ validation.entryRuntime,
7511
+ pluginTreeSnapshot,
7512
+ );
6744
7513
  } catch (error) {
6745
7514
  closeStrongLinuxResources(supervisorPin, finalLaunch);
6746
7515
  return createSandboxPlan({
@@ -6961,12 +7730,17 @@ export function applySandbox(
6961
7730
  explicitRequest,
6962
7731
  );
6963
7732
  const profileName = sandboxRequest.profile;
7733
+ // Windows does not consume the POSIX allow-write lists. Avoid invoking an
7734
+ // embedding temp-directory hook while eagerly constructing three unused
7735
+ // profiles; applyWindowsSandbox resolves and pins that hook exactly once.
7736
+ const profileTempDirectory =
7737
+ runtime.platform === "win32" ? null : runtime.tmpdir();
6964
7738
  const profiles = {
6965
7739
  default: {
6966
7740
  allowNetwork: false,
6967
7741
  allowExec: true,
6968
7742
  allowRead: [runtime.homedir(), "/tmp"],
6969
- allowWrite: [runtime.tmpdir()],
7743
+ allowWrite: profileTempDirectory ? [profileTempDirectory] : [],
6970
7744
  limits: {
6971
7745
  // RLIMIT_NPROC is intentionally omitted. Linux applies it per real
6972
7746
  // user ID rather than to this child tree, so it can starve unrelated
@@ -6979,7 +7753,7 @@ export function applySandbox(
6979
7753
  allowNetwork: false,
6980
7754
  allowExec: false,
6981
7755
  allowRead: [],
6982
- allowWrite: [runtime.tmpdir()],
7756
+ allowWrite: profileTempDirectory ? [profileTempDirectory] : [],
6983
7757
  limits: {
6984
7758
  cpu: 10,
6985
7759
  as: 256 * 1024 * 1024, // 256MB address space
@@ -6990,7 +7764,7 @@ export function applySandbox(
6990
7764
  allowNetwork: true,
6991
7765
  allowExec: true,
6992
7766
  allowRead: [runtime.homedir()],
6993
- allowWrite: [runtime.tmpdir()],
7767
+ allowWrite: profileTempDirectory ? [profileTempDirectory] : [],
6994
7768
  limits: {
6995
7769
  cpu: 60,
6996
7770
  nofile: 512,