chainlesschain 0.163.2 → 0.163.4

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 (222) hide show
  1. package/package.json +3 -2
  2. package/scripts/command-lifecycle-report.mjs +330 -38
  3. package/src/assets/web-panel/assets/{AIOps-DnGsmsqJ.js → AIOps-DB5X7qqV.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CrO6fcQ7.js → ActionButton-DxHRtKLk.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-DsBdOaP2.js → Analytics-DIH5QrxT.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-DmQslKUB.js → AppLayout-uu0oxVz7.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-BvyrT_QQ.js → Artifacts-BNSMiW9Y.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-prndoLiC.js → Audit-DcAzB4_x.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-DOTFyA55.js → BackgroundAgents-ClSsvjIc.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-9zOnPp_8.js → Backup-BMciY0js.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-C4PC6sAG.js → BaseInput-Hn3b57Sh.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-BBAF-W9v.js → Chat-CoSlC53V.js} +6 -6
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CWhvvloO.js → ChatBubbleRenderer-CFevJ3So.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-BI9iculd.js → Checkbox-B4L4Eu4w.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-3DP9BCKb.js → Codegen-C77FgRxF.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-df-2TnJX.js → Col-BrLtJLH3.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-CHs8gG_A.js → Community-Be7pXXhA.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-CDfF6q7V.js → Compact-DuGPecjo.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-CthrDnO7.js → Compliance-CzS66mjP.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-HjIqsWJw.js → Cowork-CEhjCoyK.js} +2 -2
  21. package/src/assets/web-panel/assets/{Cron-BKc196fb.js → Cron-D7ir8Vja.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-CJnjdc8W.js → Crosschain-BcXmuLxf.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-BHUy-8Q2.js → DID-Cto1dtBy.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-QDB7sKeY.js → Dashboard-B7KjgwOX.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-DStcVK11.js → Dropdown-COMqxijg.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-BwSUskMJ.js → EmailListRenderer-BZrya-nf.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DgF_kurT.js → FamilyGuardDashboard-AmVeMq0J.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-k8wvv-wN.js → Federation-kZU8c-27.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-C11qoq2r.js → FormItemContext-sOV22v2-.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-BIIpr9kg.js → GenericCardRenderer-BUuLtGCz.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-BlA1exTe.js → Git-K-ZxsLsH.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CYa1ZYM4.js → Governance-D3OEnawI.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-6_kKVIt6.js → Inference-Bll0gRmP.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-CB4AwxYy.js → KnowledgeGraph-DBXwjr53.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-xf7nHFAe.js → Logs-CEzPwEfP.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-Bf3DEqP6.js → MarkdownRenderer-CLmCbHx6.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-aPg8e4Jt.js → Marketplace-BYyRyOPz.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-C-TNlVUf.js → McpTools-DVu8w_rM.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-DMTPIpGa.js → Memory-tenuZ_hO.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-BxyvveRq.js → MobileBridge-Bxgy4ozS.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-DyPCrAyQ.js → MobileProjects-Dgx1D6sc.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-BdhqNXp6.js → Mtc-CRfJ8r8j.js} +5 -5
  43. package/src/assets/web-panel/assets/{MtcAudit-Cz-HFxzW.js → MtcAudit-BBJq4Aiz.js} +5 -5
  44. package/src/assets/web-panel/assets/{Multisig-CIUaFNsx.js → Multisig-B3ii9lai.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-CWe6-J17.js → NLProgramming-FIi-Cubv.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-vYAKeviU.js → Notes-DIq8MPf1.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-DQL9-2d7.js → NotificationSettings-BFOD9eGd.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-DnXj1jBn.js → OrderTableRenderer--41u7vre.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-B3spU8AN.js → Organization-B_0AWztW.js} +2 -2
  50. package/src/assets/web-panel/assets/{Overflow-DgEwo7vm.js → Overflow-B6I7TuXv.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-hujMRH8X.js → P2P-Cdf6qens.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-C_qhdg9h.js → PdhVaultBrowser-B_0FcQJY.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-vp7LMcSf.js → Permissions-6nFBjsQp.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-Dxr5Hgnq.js → PersonalDataHub-nQYV7xpn.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-DEPZqqH4.js → Pipeline-CBVod5jm.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-DTpDiKPe.js → Privacy-D52jW1Nt.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-jDJsRWmF.js → ProjectInit-Bdx3BYhf.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-Dhor5RWH.js → ProjectSettings-b7xr4DUa.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-DhfifCKx.js +1 -0
  60. package/src/assets/web-panel/assets/Providers-Dy_PgDMr.js +1 -0
  61. package/src/assets/web-panel/assets/{QrScannerModal-CjIw-wIb.js → QrScannerModal-FQKSmc3e.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-DX6jcqoi.js → QuickAsk-CSTrhzcF.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-DopXkhar.js → Recommend-CxMABOUh.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-BLvqJE9c.js → RemoteSession-Cv2T8yRa.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-DhRj2Me7.js → Reputation-CE1k4Vvu.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-DQ53vuPq.js → Row-BNgNcAV_.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-BslvwiBD.js → RssFeed-5ZiBZ3ph.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-DJKBDNKZ.js → Search-mDOIF1UV.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-COhpxHER.js → Security-CtuCTC-e.js} +3 -3
  70. package/src/assets/web-panel/assets/{Services-dbulD6cI.js → Services-DxFO2Zg7.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-ciIVNfhC.js → Skeleton-CKmKkH9y.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-AiRTkTCk.js → Skills-Bmzl9qkk.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-DDMcWYBc.js → Sla-9RqaWHOC.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-BPxWbpXO.js → SpeechSettings-CGdzFIYw.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-BoEOXCyg.js → SyncSettings-CvNuCeBT.js} +2 -2
  76. package/src/assets/web-panel/assets/Tasks-B_sLdcnw.js +1 -0
  77. package/src/assets/web-panel/assets/{Templates-UowY8Ixe.js → Templates-D9_YijdK.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DauSLQXN.js → Tenant-Cuc7BUSP.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-DBj7Jneq.js → Terminal-6VYRnJgd.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-UMrPwoZC.js → TimelineRenderer-GtK8xd8Y.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-4ea96-bn.js → Tokens-BZ94Lt5W.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger--ISCqt4b.js → Trigger-DEcZgDaX.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-D4GVimRJ.js → Trust-CC3z9THe.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-Ci-nXRQc.js → UkeySign-Cvtw8cV3.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-Cjk7mKPw.js → VideoEditing-CL4e6Bn3.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-BgOLOO3Z.js → Wallet-ycEemP27.js} +2 -2
  87. package/src/assets/web-panel/assets/{WebAuthn-CCX0K29i.js → WebAuthn-DgZ4X_aw.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-Ch3zgIsE.js → WorkflowEditor-D0qMhSv7.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DKOU_2Vb.js → chat-DsAg01Zn.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-6OVo4u3C.js → colors-B0oqAhrQ.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BH78Hlsq.js → compact-item-CmX0Rabz.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-CFRpSbbC.js → createContext-Be4cn1Ub.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-CP2qzG6D.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-DNQhgcVi.js → hasIn-D5v12kVq.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BkN3iTTp.js → index-0eWLkQKT.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CkNEMANR.js → index-301ogenv.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-D3RtTT4K.js → index-5LGY0JbG.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-dDkoL4Pe.js → index-7VKDn-t1.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BHa4WyTZ.js → index-8WvYGnON.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CaYqY35Z.js → index-B1YGjOWC.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-bKm3TUDp.js → index-B3mUHuVc.js} +1 -1
  102. package/src/assets/web-panel/assets/{index--rq90rL9.js → index-BA4oulaP.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DaDgdJYj.js → index-BBREnn1V.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B_Z-YoxX.js → index-BBvyHguy.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CkqHgG-b.js → index-BKmsS2jj.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CrcLHN2F.js → index-BRIqLy1m.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BaVE6E8y.js → index-BXGSbelg.js} +3 -3
  108. package/src/assets/web-panel/assets/{index-BYrnm5Ob.js → index-BaE0Ui07.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BDSR7ecX.js → index-BoAIAZ5C.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CNuCHHQT.js → index-BqltWojQ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Eag8e_mA.js → index-CGiIyTZw.js} +1 -1
  112. package/src/assets/web-panel/assets/index-CKB0vbJU.js +1 -0
  113. package/src/assets/web-panel/assets/{index-BepBTpa8.js → index-CZ2wUEgr.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-HNGGVF6o.js → index-CZpfzcuy.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-AuBhLqOm.js → index-Ckrk3pIV.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-7TC3S8SN.js → index-CqoCZ5vR.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CFPJ8yzF.js → index-CyWBir7N.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Bb6kDrUx.js → index-CyyGTMKd.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DKN2noIF.js → index-D0BSkxYQ.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Du0rnksC.js → index-D5YVopyr.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BxRy5LPR.js → index-DKUBj36_.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-sm7W8AXy.js → index-DM724NgU.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BujsOZfc.js → index-DQRsQen0.js} +1 -1
  124. package/src/assets/web-panel/assets/index-DgoXmwqB.js +1 -0
  125. package/src/assets/web-panel/assets/{index-Y9MJv28e.js → index-DikJ0Qqe.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-caye7AOE.js → index-Dn2dSdXx.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-cdf7fd7d.js → index-DyUaFWEk.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BnnXQot_.js → index-Hfe_dEGx.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DvHGtNnF.js → index-_JjRLSD0.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-nbfHl1oV.js → index-frSojpHj.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Cu3L4r5l.js → index-jK3ax6Z6.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D84id2Y4.js → index-qMuZ-NRR.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-hPY0fFrd.js → index-q_fQ94SE.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-BZ9w6jPL.js → initDefaultProps-D4VdhJ9_.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BnwUiOho.js → motion-CZJA0MQW.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-ByUKJnNm.js → move-BqEs7Sb1.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-V7yK1KEl.js → mtc-parser-BpHEtsu1.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-BEXBBt4j.js → omit-BW94tY9W.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-qDm_46Gi.js → pickAttrs-BnBjPWP9.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-BIbhEo_0.js → placementArrow-uUkjQ_hu.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-NbPbDgA_.js → responsiveObserve-B-klUqXK.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-BnUWglZ0.js → slide-CvY1cNAs.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-BX8_wZI5.js → statusUtils-Ix5sEDT2.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BV4OycQX.js → styleChecker-C8qcXOne.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-Od_H5llY.js → useFlexGapSupport-KNF6Kj8f.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-BWI-PyiX.js → useFs-DGJr345n.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-BqMuhG3g.js → usePersonalDataHub-BboN99rI.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-DNd23z6f.js → vnode-Bgt8Vgte.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-Cjaxnm7l.js → zoom-BhJtErgt.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +2 -2
  152. package/src/commands/agenda.js +6 -5
  153. package/src/commands/agent.js +237 -75
  154. package/src/commands/agents.js +6 -0
  155. package/src/commands/artifacts.js +98 -0
  156. package/src/commands/background-session.js +11 -1
  157. package/src/commands/mcp.js +218 -31
  158. package/src/commands/review.js +221 -22
  159. package/src/commands/session-mcp-recovery.js +5 -5
  160. package/src/data/changelog.json +27 -1
  161. package/src/gateways/ws/ws-agent-handler.js +492 -14
  162. package/src/harness/jsonl-session-store.js +39 -1
  163. package/src/harness/mcp-client.js +414 -71
  164. package/src/harness/prompt-compressor.js +24 -0
  165. package/src/harness/provider-backed-compaction.js +205 -0
  166. package/src/harness/worktree-isolator.js +26 -3
  167. package/src/lib/agent-worktree.js +41 -2
  168. package/src/lib/app-preview.js +3 -0
  169. package/src/lib/background-agent-supervisor.js +790 -186
  170. package/src/lib/background-command-argv.js +318 -0
  171. package/src/lib/background-launch-profile.js +1 -0
  172. package/src/lib/background-phase-reporter.js +22 -2
  173. package/src/lib/background-worktree-policy.js +109 -0
  174. package/src/lib/command-lifecycle-report.js +1340 -145
  175. package/src/lib/cowork-mcp-tools.js +51 -6
  176. package/src/lib/cowork-task-runner.js +1 -0
  177. package/src/lib/delivery-coordinator.js +30 -5
  178. package/src/lib/delivery-production-adapter.js +1777 -0
  179. package/src/lib/delivery-production-runner.js +240 -0
  180. package/src/lib/durable-security-store.js +29 -17
  181. package/src/lib/execution-trace/trace-context.cjs +179 -0
  182. package/src/lib/execution-trace/trace-context.js +8 -175
  183. package/src/lib/mcp-call-ledger-store.js +58 -6
  184. package/src/lib/mcp-headers-helper.js +76 -8
  185. package/src/lib/mcp-host-recovery-runtime.js +2 -0
  186. package/src/lib/mcp-ledger-recovery-admission.js +39 -3
  187. package/src/lib/mcp-recovery-adjudication.js +256 -19
  188. package/src/lib/mcp-sandbox-policy.js +184 -0
  189. package/src/lib/mcp-stdio-capsule-builder-worker.cjs +495 -0
  190. package/src/lib/mcp-stdio-executable-identity.js +33 -2
  191. package/src/lib/mcp-stdio-execution-authority.js +163 -0
  192. package/src/lib/mcp-stdio-immutable-vfs-resolver.cjs +830 -0
  193. package/src/lib/mcp-stdio-package-materialization.js +721 -333
  194. package/src/lib/mcp-stdio-workspace-authority.js +252 -0
  195. package/src/lib/observability/index.js +2 -0
  196. package/src/lib/observability/otlp-exporter.js +2 -2
  197. package/src/lib/packer/pack-update-applier.js +8 -1
  198. package/src/lib/packer/pkg-config-generator.js +34 -0
  199. package/src/lib/plugin-runtime/sandbox-policy.js +42 -2
  200. package/src/lib/process-execution-broker/index.js +213 -109
  201. package/src/lib/process-execution-broker/platform-sandbox.js +1024 -169
  202. package/src/lib/process-tree-termination.js +17 -1
  203. package/src/lib/scheduler-kernel/contract.js +315 -0
  204. package/src/lib/scheduler-kernel/store.js +1345 -0
  205. package/src/lib/session-host-lease.js +403 -30
  206. package/src/lib/session-persistence-failure.js +4 -1
  207. package/src/lib/skill-mcp.js +65 -4
  208. package/src/lib/sub-agent-context.js +8 -0
  209. package/src/lib/web-fetch.js +4 -4
  210. package/src/repl/agent-repl.js +285 -38
  211. package/src/runtime/agent-core.js +1044 -90
  212. package/src/runtime/agent-runtime.js +61 -8
  213. package/src/runtime/headless-runner.js +260 -166
  214. package/src/runtime/headless-stream.js +254 -25
  215. package/src/runtime/mcp-config.js +235 -28
  216. package/src/workers/background-agent-worker.js +492 -71
  217. package/src/assets/web-panel/assets/Projects-08xUDZGt.js +0 -1
  218. package/src/assets/web-panel/assets/Providers-BGs47s1a.js +0 -1
  219. package/src/assets/web-panel/assets/Tasks-CEYFJwmr.js +0 -1
  220. package/src/assets/web-panel/assets/devWarning-DEMblAIm.js +0 -1
  221. package/src/assets/web-panel/assets/index-BTvq-36D.js +0 -1
  222. 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.
@@ -152,6 +159,9 @@ const LINUX_BWRAP_PATH = "/usr/bin/bwrap";
152
159
  const LINUX_LDD_PATH = "/usr/bin/ldd";
153
160
  const LINUX_BWRAP_BACKEND = "linux-bwrap";
154
161
  const LINUX_BWRAP_NODE_PROBE_SENTINEL = "chainless-linux-bwrap-plugin-node-v1";
162
+ const LINUX_BWRAP_CHILD_RUNTIME_PROBE_SENTINEL =
163
+ "chainless-linux-bwrap-child-runtime-v1";
164
+ const LINUX_BWRAP_CHILD_RUNTIME_PROBE_FAILURE_STATUS = 86;
155
165
  const LINUX_BWRAP_MAX_PLUGIN_ENTRIES = 512;
156
166
  const LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES = 256;
157
167
  const LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES = 256 * 1024 * 1024;
@@ -179,8 +189,8 @@ export const MCP_STDIO_FD_ENTRY_BOOTSTRAP_SHA256 = crypto
179
189
  .digest("hex");
180
190
  const LINUX_NODE_ENTRY_SNAPSHOT_SCOPE = "plugin-entry-source";
181
191
  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";
192
+ const LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE = "all-pinned-plugin-regular-files";
193
+ const LINUX_PLUGIN_TREE_SNAPSHOT_CONSISTENCY = "per-file-pin-to-launch";
184
194
  const LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE = "0400";
185
195
  const LINUX_NATIVE_ENTRY_SNAPSHOT_TARGET_MODE = "0500";
186
196
  const LINUX_ELF64_HEADER_BYTES = 64;
@@ -311,6 +321,325 @@ function resolveRuntime(overrides = {}) {
311
321
  return { ...DEFAULT_RUNTIME, ...overrides };
312
322
  }
313
323
 
324
+ function windowsNativeRealpath(runtime, candidate) {
325
+ const nativeRealpath = runtime.fs.realpathSync?.native;
326
+ if (typeof nativeRealpath !== "function") {
327
+ throw new Error("Native Windows realpath is unavailable");
328
+ }
329
+ const nativePath = nativeRealpath.call(runtime.fs, candidate);
330
+ const withoutExtendedPrefix = String(nativePath).replace(
331
+ /^\\\\\?\\(?=[A-Za-z]:\\)/,
332
+ "",
333
+ );
334
+ return path.win32.resolve(withoutExtendedPrefix);
335
+ }
336
+
337
+ function windowsPathHasReparseSemantics(stat) {
338
+ return (
339
+ stat.isSymbolicLink?.() ||
340
+ stat.isReparsePoint?.() ||
341
+ stat.reparsePoint === true
342
+ );
343
+ }
344
+
345
+ function windowsLocalPathAncestors(candidate) {
346
+ const resolved = path.win32.resolve(candidate);
347
+ const volumeRoot = path.win32.parse(resolved).root;
348
+ if (!/^[A-Za-z]:\\$/.test(volumeRoot)) return null;
349
+ const relative = path.win32.relative(volumeRoot, resolved);
350
+ if (
351
+ path.win32.isAbsolute(relative) ||
352
+ relative === ".." ||
353
+ relative.startsWith(`..${path.win32.sep}`)
354
+ ) {
355
+ return null;
356
+ }
357
+ const ancestors = [volumeRoot];
358
+ let current = volumeRoot;
359
+ for (const segment of relative.split(path.win32.sep).filter(Boolean)) {
360
+ current = path.win32.join(current, segment);
361
+ ancestors.push(current);
362
+ }
363
+ return ancestors;
364
+ }
365
+
366
+ function windowsDirectoryIdentity(stat) {
367
+ const birthtimeNs =
368
+ stat.birthtimeNs !== undefined
369
+ ? String(stat.birthtimeNs)
370
+ : String(Math.trunc(Number(stat.birthtimeMs || 0) * 1_000_000));
371
+ if (
372
+ stat.dev === undefined ||
373
+ stat.ino === undefined ||
374
+ stat.mode === undefined
375
+ ) {
376
+ throw new Error("Windows directory identity is unavailable");
377
+ }
378
+ return Object.freeze({
379
+ dev: String(stat.dev),
380
+ ino: String(stat.ino),
381
+ mode: String(stat.mode),
382
+ birthtimeNs,
383
+ });
384
+ }
385
+
386
+ function sameWindowsDirectoryIdentity(left, right) {
387
+ return (
388
+ left?.dev === right?.dev &&
389
+ left?.ino === right?.ino &&
390
+ left?.mode === right?.mode &&
391
+ left?.birthtimeNs === right?.birthtimeNs
392
+ );
393
+ }
394
+
395
+ function inspectWindowsAdapterTempRoot(runtime, candidate) {
396
+ if (
397
+ typeof candidate !== "string" ||
398
+ candidate.length === 0 ||
399
+ candidate.includes("\0") ||
400
+ !path.win32.isAbsolute(candidate)
401
+ ) {
402
+ return null;
403
+ }
404
+ const sourcePath = path.win32.resolve(candidate);
405
+ const sourceAncestors = windowsLocalPathAncestors(sourcePath);
406
+ if (!sourceAncestors) return null;
407
+
408
+ try {
409
+ // Keep the caller-visible path in the trust decision even when Windows
410
+ // exposes it through a legitimate 8.3 alias (for example RUNNER~1 on a
411
+ // hosted runner). Every raw ancestor must be a plain directory before the
412
+ // native real path is accepted as the path used for mutations.
413
+ let sourceRootStat = null;
414
+ for (const ancestor of sourceAncestors) {
415
+ const stat = runtime.fs.lstatSync(ancestor, { bigint: true });
416
+ if (windowsPathHasReparseSemantics(stat) || !stat.isDirectory?.()) {
417
+ return null;
418
+ }
419
+ sourceRootStat = stat;
420
+ }
421
+
422
+ const rootPath = windowsNativeRealpath(runtime, sourcePath);
423
+ const rootAncestors = windowsLocalPathAncestors(rootPath);
424
+ if (!rootAncestors) return null;
425
+ let canonicalRootStat = null;
426
+ for (const ancestor of rootAncestors) {
427
+ const stat = runtime.fs.lstatSync(ancestor, { bigint: true });
428
+ if (windowsPathHasReparseSemantics(stat) || !stat.isDirectory?.()) {
429
+ return null;
430
+ }
431
+ canonicalRootStat = stat;
432
+ }
433
+
434
+ const sourceIdentity = windowsDirectoryIdentity(sourceRootStat);
435
+ const identity = windowsDirectoryIdentity(canonicalRootStat);
436
+ if (!sameWindowsDirectoryIdentity(sourceIdentity, identity)) return null;
437
+
438
+ const sourceAfter = runtime.fs.lstatSync(sourcePath, { bigint: true });
439
+ const rootAfter = runtime.fs.lstatSync(rootPath, { bigint: true });
440
+ if (
441
+ windowsPathHasReparseSemantics(sourceAfter) ||
442
+ !sourceAfter.isDirectory?.() ||
443
+ windowsPathHasReparseSemantics(rootAfter) ||
444
+ !rootAfter.isDirectory?.() ||
445
+ windowsCanonicalPathKey(windowsNativeRealpath(runtime, sourcePath)) !==
446
+ windowsCanonicalPathKey(rootPath) ||
447
+ windowsCanonicalPathKey(windowsNativeRealpath(runtime, rootPath)) !==
448
+ windowsCanonicalPathKey(rootPath) ||
449
+ !sameWindowsDirectoryIdentity(
450
+ sourceIdentity,
451
+ windowsDirectoryIdentity(sourceAfter),
452
+ ) ||
453
+ !sameWindowsDirectoryIdentity(
454
+ identity,
455
+ windowsDirectoryIdentity(rootAfter),
456
+ )
457
+ ) {
458
+ return null;
459
+ }
460
+ return Object.freeze({
461
+ sourcePath,
462
+ sourceCanonicalPathKey: windowsCanonicalPathKey(sourcePath),
463
+ rootPath,
464
+ canonicalPathKey: windowsCanonicalPathKey(rootPath),
465
+ volumeRoot: path.win32.parse(rootPath).root.toLowerCase(),
466
+ identity,
467
+ attestation: Object.freeze({
468
+ kind: "windows-adapter-temp-root-path-reattestation-v1",
469
+ localVolume: true,
470
+ nativeRealpathMatched: true,
471
+ sourceAliasCanonicalized:
472
+ windowsCanonicalPathKey(sourcePath) !==
473
+ windowsCanonicalPathKey(rootPath),
474
+ ancestorReparsePointsRejected: true,
475
+ rootIdentityBound: true,
476
+ criticalOperationReattestation: true,
477
+ descriptorRelativeOperations: false,
478
+ handleAtomic: false,
479
+ residualRace:
480
+ "same-principal-path-swap-between-reattestation-and-filesystem-call",
481
+ residualHandling: "fail-closed-on-detected-path-or-identity-change",
482
+ }),
483
+ });
484
+ } catch {
485
+ return null;
486
+ }
487
+ }
488
+
489
+ function sameWindowsAdapterTempRootBinding(left, right) {
490
+ return (
491
+ left?.sourceCanonicalPathKey === right?.sourceCanonicalPathKey &&
492
+ left?.canonicalPathKey === right?.canonicalPathKey &&
493
+ left?.volumeRoot === right?.volumeRoot &&
494
+ sameWindowsDirectoryIdentity(left?.identity, right?.identity)
495
+ );
496
+ }
497
+
498
+ function verifyWindowsAdapterTempRootBinding(runtime, binding) {
499
+ const current = inspectWindowsAdapterTempRoot(
500
+ runtime,
501
+ binding?.sourcePath || binding?.rootPath,
502
+ );
503
+ return Boolean(
504
+ current && sameWindowsAdapterTempRootBinding(binding, current),
505
+ );
506
+ }
507
+
508
+ function windowsAdapterTempRootChangedError() {
509
+ const error = new Error(
510
+ "Windows sandbox adapter temp root changed after attestation",
511
+ );
512
+ error.code = "ERR_WINDOWS_SANDBOX_ADAPTER_TEMP_ROOT_CHANGED";
513
+ error.adapterReason = WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON;
514
+ return error;
515
+ }
516
+
517
+ function assertWindowsAdapterTempRoot(runtime) {
518
+ const binding = runtime.windowsAdapterTempRootBinding;
519
+ if (!binding || !verifyWindowsAdapterTempRootBinding(runtime, binding)) {
520
+ throw windowsAdapterTempRootChangedError();
521
+ }
522
+ return binding;
523
+ }
524
+
525
+ function inspectWindowsAdapterBoundPath(
526
+ runtime,
527
+ targetPath,
528
+ expectedKind,
529
+ { allowMissing = false } = {},
530
+ ) {
531
+ const binding = assertWindowsAdapterTempRoot(runtime);
532
+ if (
533
+ typeof targetPath !== "string" ||
534
+ targetPath.includes("\0") ||
535
+ !path.win32.isAbsolute(targetPath)
536
+ ) {
537
+ throw windowsAdapterTempRootChangedError();
538
+ }
539
+ const resolved = path.win32.resolve(targetPath);
540
+ const relative = path.win32.relative(binding.rootPath, resolved);
541
+ if (
542
+ !relative ||
543
+ path.win32.isAbsolute(relative) ||
544
+ relative === ".." ||
545
+ relative.startsWith(`..${path.win32.sep}`)
546
+ ) {
547
+ throw windowsAdapterTempRootChangedError();
548
+ }
549
+
550
+ const segments = relative.split(path.win32.sep).filter(Boolean);
551
+ let current = binding.rootPath;
552
+ for (let index = 0; index < segments.length; index += 1) {
553
+ current = path.win32.join(current, segments[index]);
554
+ const isTarget = index === segments.length - 1;
555
+ let stat;
556
+ try {
557
+ stat = runtime.fs.lstatSync(current, { bigint: true });
558
+ } catch (error) {
559
+ if (isTarget && allowMissing && error?.code === "ENOENT") {
560
+ return Object.freeze({ exists: false, resolvedPath: resolved });
561
+ }
562
+ throw error;
563
+ }
564
+ if (
565
+ windowsPathHasReparseSemantics(stat) ||
566
+ windowsCanonicalPathKey(windowsNativeRealpath(runtime, current)) !==
567
+ windowsCanonicalPathKey(current)
568
+ ) {
569
+ throw windowsAdapterTempRootChangedError();
570
+ }
571
+ if (!isTarget && !stat.isDirectory?.()) {
572
+ throw windowsAdapterTempRootChangedError();
573
+ }
574
+ if (isTarget) {
575
+ if (
576
+ (expectedKind === "file" && !stat.isFile?.()) ||
577
+ (expectedKind === "directory" && !stat.isDirectory?.())
578
+ ) {
579
+ throw windowsAdapterTempRootChangedError();
580
+ }
581
+ return Object.freeze({
582
+ exists: true,
583
+ resolvedPath: resolved,
584
+ identity:
585
+ expectedKind === "directory"
586
+ ? windowsDirectoryIdentity(stat)
587
+ : windowsFileIdentity(stat),
588
+ });
589
+ }
590
+ }
591
+ throw windowsAdapterTempRootChangedError();
592
+ }
593
+
594
+ function assertWindowsAdapterBoundPathIdentity(
595
+ runtime,
596
+ targetPath,
597
+ expectedKind,
598
+ expectedIdentity,
599
+ ) {
600
+ const inspected = inspectWindowsAdapterBoundPath(
601
+ runtime,
602
+ targetPath,
603
+ expectedKind,
604
+ );
605
+ const matches =
606
+ expectedKind === "directory"
607
+ ? sameWindowsDirectoryIdentity(inspected.identity, expectedIdentity)
608
+ : sameWindowsFileIdentity(inspected.identity, expectedIdentity);
609
+ if (!matches) throw windowsAdapterTempRootChangedError();
610
+ return inspected;
611
+ }
612
+
613
+ function resolveWindowsAdapterTempRoot(runtime, runtimeOverrides = {}) {
614
+ const hasExplicitTmpdir = Object.prototype.hasOwnProperty.call(
615
+ runtimeOverrides,
616
+ "tmpdir",
617
+ );
618
+ const hasExplicitAdapterRoot = Object.prototype.hasOwnProperty.call(
619
+ runtimeOverrides,
620
+ "windowsAdapterTempRoot",
621
+ );
622
+ let candidate;
623
+ try {
624
+ if (hasExplicitTmpdir) {
625
+ if (typeof runtime.tmpdir !== "function") return null;
626
+ candidate = runtime.tmpdir();
627
+ } else if (hasExplicitAdapterRoot) {
628
+ candidate = runtimeOverrides.windowsAdapterTempRoot;
629
+ } else {
630
+ const environmentRoot =
631
+ runtime.env?.[WINDOWS_ADAPTER_TEMP_ROOT_ENV] ??
632
+ process.env[WINDOWS_ADAPTER_TEMP_ROOT_ENV];
633
+ candidate =
634
+ environmentRoot === undefined ? runtime.tmpdir() : environmentRoot;
635
+ }
636
+ } catch {
637
+ return null;
638
+ }
639
+
640
+ return inspectWindowsAdapterTempRoot(runtime, candidate);
641
+ }
642
+
314
643
  function normalizeSandboxRequest(profileOrRequest, explicitRequest) {
315
644
  const legacyRequest =
316
645
  profileOrRequest &&
@@ -788,14 +1117,42 @@ function cleanupWindowsTemporaryPath(
788
1117
  targetPath,
789
1118
  attempts = 100,
790
1119
  delayMs = 10,
1120
+ expectedIdentity = null,
791
1121
  ) {
792
1122
  if (!targetPath) return true;
793
1123
  for (let attempt = 0; attempt < attempts; attempt += 1) {
794
1124
  try {
1125
+ const inspected = inspectWindowsAdapterBoundPath(
1126
+ runtime,
1127
+ targetPath,
1128
+ "file",
1129
+ { allowMissing: true },
1130
+ );
1131
+ if (!inspected.exists) {
1132
+ assertWindowsAdapterTempRoot(runtime);
1133
+ return true;
1134
+ }
1135
+ if (
1136
+ !expectedIdentity ||
1137
+ !sameWindowsFileIdentity(inspected.identity, expectedIdentity)
1138
+ ) {
1139
+ return false;
1140
+ }
795
1141
  runtime.fs.unlinkSync(targetPath);
1142
+ assertWindowsAdapterTempRoot(runtime);
796
1143
  return true;
797
1144
  } catch (error) {
798
- if (error?.code === "ENOENT") return true;
1145
+ if (error?.adapterReason === WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON) {
1146
+ return false;
1147
+ }
1148
+ if (error?.code === "ENOENT") {
1149
+ try {
1150
+ assertWindowsAdapterTempRoot(runtime);
1151
+ return true;
1152
+ } catch {
1153
+ return false;
1154
+ }
1155
+ }
799
1156
  }
800
1157
  if (attempt + 1 < attempts) runtime.sleepSync(delayMs);
801
1158
  }
@@ -806,13 +1163,32 @@ function cleanupOrTrackWindowsTemporaryPath(
806
1163
  runtime,
807
1164
  targetPath,
808
1165
  attempts = 100,
1166
+ entry = null,
1167
+ expectedIdentity = null,
809
1168
  ) {
810
- if (cleanupWindowsTemporaryPath(runtime, targetPath, attempts)) {
1169
+ if (
1170
+ cleanupWindowsTemporaryPath(
1171
+ runtime,
1172
+ targetPath,
1173
+ attempts,
1174
+ 10,
1175
+ expectedIdentity,
1176
+ )
1177
+ ) {
1178
+ const residualEntries = new Set();
811
1179
  for (const residual of [...windowsTemporaryCleanupBacklog]) {
812
- if (residual.runtime === runtime && residual.targetPath === targetPath) {
1180
+ if (
1181
+ residual.kind === "file" &&
1182
+ residual.runtime === runtime &&
1183
+ residual.targetPath === targetPath
1184
+ ) {
813
1185
  windowsTemporaryCleanupBacklog.delete(residual);
1186
+ if (residual.entry) residualEntries.add(residual.entry);
814
1187
  }
815
1188
  }
1189
+ for (const residualEntry of residualEntries) {
1190
+ finalizeWindowsTemporaryCleanupEntry(residualEntry);
1191
+ }
816
1192
  if (
817
1193
  windowsTemporaryCleanupBacklog.size === 0 &&
818
1194
  windowsTemporaryCleanupRetryTimer
@@ -822,13 +1198,25 @@ function cleanupOrTrackWindowsTemporaryPath(
822
1198
  }
823
1199
  return true;
824
1200
  }
825
- if (
826
- ![...windowsTemporaryCleanupBacklog].some(
827
- (residual) =>
828
- residual.runtime === runtime && residual.targetPath === targetPath,
829
- )
830
- ) {
831
- windowsTemporaryCleanupBacklog.add({ runtime, targetPath });
1201
+ const existing = [...windowsTemporaryCleanupBacklog].find(
1202
+ (residual) =>
1203
+ residual.kind === "file" &&
1204
+ residual.runtime === runtime &&
1205
+ residual.targetPath === targetPath,
1206
+ );
1207
+ if (existing) {
1208
+ if (entry) existing.entry = entry;
1209
+ if (!existing.expectedIdentity && expectedIdentity) {
1210
+ existing.expectedIdentity = expectedIdentity;
1211
+ }
1212
+ } else {
1213
+ windowsTemporaryCleanupBacklog.add({
1214
+ kind: "file",
1215
+ runtime,
1216
+ targetPath,
1217
+ entry,
1218
+ expectedIdentity,
1219
+ });
832
1220
  }
833
1221
  registerWindowsAdapterExitCleanup();
834
1222
  scheduleWindowsTemporaryCleanupRetry();
@@ -838,14 +1226,25 @@ function cleanupOrTrackWindowsTemporaryPath(
838
1226
  function retryWindowsTemporaryCleanupBacklog(attempts = 100) {
839
1227
  let cleaned = true;
840
1228
  for (const residual of [...windowsTemporaryCleanupBacklog]) {
841
- if (
842
- cleanupWindowsTemporaryPath(
843
- residual.runtime,
844
- residual.targetPath,
845
- attempts,
846
- )
847
- ) {
1229
+ const residualCleaned =
1230
+ residual.kind === "directory"
1231
+ ? cleanupWindowsTemporaryDirectory(
1232
+ residual.runtime,
1233
+ residual.targetPath,
1234
+ attempts,
1235
+ 10,
1236
+ residual.expectedIdentity,
1237
+ )
1238
+ : cleanupWindowsTemporaryPath(
1239
+ residual.runtime,
1240
+ residual.targetPath,
1241
+ attempts,
1242
+ 10,
1243
+ residual.expectedIdentity,
1244
+ );
1245
+ if (residualCleaned) {
848
1246
  windowsTemporaryCleanupBacklog.delete(residual);
1247
+ if (residual.entry) finalizeWindowsTemporaryCleanupEntry(residual.entry);
849
1248
  } else {
850
1249
  cleaned = false;
851
1250
  }
@@ -860,6 +1259,18 @@ function retryWindowsTemporaryCleanupBacklog(attempts = 100) {
860
1259
  return cleaned;
861
1260
  }
862
1261
 
1262
+ function finalizeWindowsTemporaryCleanupEntry(entry) {
1263
+ if (
1264
+ [...windowsTemporaryCleanupBacklog].some(
1265
+ (residual) => residual.entry === entry,
1266
+ )
1267
+ ) {
1268
+ return;
1269
+ }
1270
+ entry.cleaned = true;
1271
+ windowsAdapterCacheEntries.delete(entry);
1272
+ }
1273
+
863
1274
  function scheduleWindowsTemporaryCleanupRetry() {
864
1275
  if (
865
1276
  windowsTemporaryCleanupRetryTimer ||
@@ -882,20 +1293,112 @@ function cleanupWindowsTemporaryDirectory(
882
1293
  targetPath,
883
1294
  attempts = 100,
884
1295
  delayMs = 10,
1296
+ expectedIdentity = null,
885
1297
  ) {
886
1298
  if (!targetPath) return true;
887
1299
  for (let attempt = 0; attempt < attempts; attempt += 1) {
888
1300
  try {
1301
+ const inspected = inspectWindowsAdapterBoundPath(
1302
+ runtime,
1303
+ targetPath,
1304
+ "directory",
1305
+ { allowMissing: true },
1306
+ );
1307
+ if (!inspected.exists) {
1308
+ assertWindowsAdapterTempRoot(runtime);
1309
+ return true;
1310
+ }
1311
+ if (
1312
+ !expectedIdentity ||
1313
+ !sameWindowsDirectoryIdentity(inspected.identity, expectedIdentity)
1314
+ ) {
1315
+ return false;
1316
+ }
889
1317
  runtime.fs.rmdirSync(targetPath);
1318
+ assertWindowsAdapterTempRoot(runtime);
890
1319
  return true;
891
1320
  } catch (error) {
892
- if (error?.code === "ENOENT") return true;
1321
+ if (error?.adapterReason === WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON) {
1322
+ return false;
1323
+ }
1324
+ if (error?.code === "ENOENT") {
1325
+ try {
1326
+ assertWindowsAdapterTempRoot(runtime);
1327
+ return true;
1328
+ } catch {
1329
+ return false;
1330
+ }
1331
+ }
893
1332
  }
894
1333
  if (attempt + 1 < attempts) runtime.sleepSync(delayMs);
895
1334
  }
896
1335
  return false;
897
1336
  }
898
1337
 
1338
+ function cleanupOrTrackWindowsTemporaryDirectory(
1339
+ runtime,
1340
+ targetPath,
1341
+ attempts = 100,
1342
+ entry = null,
1343
+ expectedIdentity = null,
1344
+ ) {
1345
+ if (
1346
+ cleanupWindowsTemporaryDirectory(
1347
+ runtime,
1348
+ targetPath,
1349
+ attempts,
1350
+ 10,
1351
+ expectedIdentity,
1352
+ )
1353
+ ) {
1354
+ const residualEntries = new Set();
1355
+ for (const residual of [...windowsTemporaryCleanupBacklog]) {
1356
+ if (
1357
+ residual.kind === "directory" &&
1358
+ residual.runtime === runtime &&
1359
+ residual.targetPath === targetPath
1360
+ ) {
1361
+ windowsTemporaryCleanupBacklog.delete(residual);
1362
+ if (residual.entry) residualEntries.add(residual.entry);
1363
+ }
1364
+ }
1365
+ for (const residualEntry of residualEntries) {
1366
+ finalizeWindowsTemporaryCleanupEntry(residualEntry);
1367
+ }
1368
+ if (
1369
+ windowsTemporaryCleanupBacklog.size === 0 &&
1370
+ windowsTemporaryCleanupRetryTimer
1371
+ ) {
1372
+ clearTimeout(windowsTemporaryCleanupRetryTimer);
1373
+ windowsTemporaryCleanupRetryTimer = null;
1374
+ }
1375
+ return true;
1376
+ }
1377
+ const existing = [...windowsTemporaryCleanupBacklog].find(
1378
+ (residual) =>
1379
+ residual.kind === "directory" &&
1380
+ residual.runtime === runtime &&
1381
+ residual.targetPath === targetPath,
1382
+ );
1383
+ if (existing) {
1384
+ if (entry) existing.entry = entry;
1385
+ if (!existing.expectedIdentity && expectedIdentity) {
1386
+ existing.expectedIdentity = expectedIdentity;
1387
+ }
1388
+ } else {
1389
+ windowsTemporaryCleanupBacklog.add({
1390
+ kind: "directory",
1391
+ runtime,
1392
+ targetPath,
1393
+ entry,
1394
+ expectedIdentity,
1395
+ });
1396
+ }
1397
+ registerWindowsAdapterExitCleanup();
1398
+ scheduleWindowsTemporaryCleanupRetry();
1399
+ return false;
1400
+ }
1401
+
899
1402
  function sha256(value) {
900
1403
  return crypto.createHash("sha256").update(value).digest("hex");
901
1404
  }
@@ -1143,15 +1646,11 @@ function sameWindowsFileIdentity(left, right) {
1143
1646
  }
1144
1647
 
1145
1648
  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
- }
1649
+ const boundBefore = inspectWindowsAdapterBoundPath(
1650
+ runtime,
1651
+ assemblyPath,
1652
+ "file",
1653
+ );
1155
1654
  const before = runtime.fs.statSync(assemblyPath, { bigint: true });
1156
1655
  if (typeof before.isFile === "function" && !before.isFile()) {
1157
1656
  throw new Error("Windows in-memory adapter is not a regular file");
@@ -1164,6 +1663,15 @@ function inspectWindowsAdapterAssembly(runtime, assemblyPath) {
1164
1663
  if (!sameWindowsFileIdentity(beforeIdentity, afterIdentity)) {
1165
1664
  throw new Error("Windows in-memory adapter changed during attestation");
1166
1665
  }
1666
+ const boundAfter = assertWindowsAdapterBoundPathIdentity(
1667
+ runtime,
1668
+ assemblyPath,
1669
+ "file",
1670
+ boundBefore.identity,
1671
+ );
1672
+ if (!sameWindowsFileIdentity(afterIdentity, boundAfter.identity)) {
1673
+ throw new Error("Windows in-memory adapter changed during attestation");
1674
+ }
1167
1675
  return { assemblyDigest, fileIdentity: afterIdentity };
1168
1676
  }
1169
1677
 
@@ -1173,6 +1681,11 @@ function verifyWindowsAdapterEntry(entry, runtime, source) {
1173
1681
  entry.cleaned ||
1174
1682
  entry.runtimeFs !== runtime.fs ||
1175
1683
  entry.tempDirectory !== source.tempDirectory ||
1684
+ entry.idleTtlMs !== source.idleTtlMs ||
1685
+ !sameWindowsAdapterTempRootBinding(
1686
+ entry.tempRootBinding,
1687
+ source.tempRootBinding,
1688
+ ) ||
1176
1689
  entry.sourceDigest !== source.sourceDigest ||
1177
1690
  entry.sourceContractDigest !== source.sourceContractDigest ||
1178
1691
  entry.loaderMode !== source.loaderMode
@@ -1180,6 +1693,15 @@ function verifyWindowsAdapterEntry(entry, runtime, source) {
1180
1693
  return false;
1181
1694
  }
1182
1695
  try {
1696
+ assertWindowsAdapterTempRoot(runtime);
1697
+ if (entry.adapterDirectory) {
1698
+ assertWindowsAdapterBoundPathIdentity(
1699
+ runtime,
1700
+ entry.adapterDirectory,
1701
+ "directory",
1702
+ entry.adapterDirectoryIdentity,
1703
+ );
1704
+ }
1183
1705
  const attestation = inspectWindowsAdapterAssembly(
1184
1706
  runtime,
1185
1707
  entry.assemblyPath,
@@ -1203,14 +1725,18 @@ function cleanupWindowsAdapterEntry(entry, attempts = 100) {
1203
1725
  entry.runtime,
1204
1726
  entry.assemblyPath,
1205
1727
  attempts,
1728
+ entry,
1729
+ entry.fileIdentity,
1206
1730
  );
1207
- const directoryDeleted =
1208
- deleted &&
1209
- cleanupWindowsTemporaryDirectory(
1210
- entry.runtime,
1211
- entry.adapterDirectory,
1212
- attempts,
1213
- );
1731
+ const directoryDeleted = entry.adapterDirectory
1732
+ ? cleanupOrTrackWindowsTemporaryDirectory(
1733
+ entry.runtime,
1734
+ entry.adapterDirectory,
1735
+ attempts,
1736
+ entry,
1737
+ entry.adapterDirectoryIdentity,
1738
+ )
1739
+ : true;
1214
1740
  if (deleted && directoryDeleted) {
1215
1741
  entry.cleaned = true;
1216
1742
  windowsAdapterCacheEntries.delete(entry);
@@ -1253,15 +1779,35 @@ function registerWindowsAdapterExitCleanup() {
1253
1779
  * this hook at a known quiescent point to prove cleanup.
1254
1780
  */
1255
1781
  export function resetWindowsSandboxAdapterCache() {
1256
- let cleaned = retryWindowsAppContainerCleanupBacklog();
1782
+ const appContainerCleaned = retryWindowsAppContainerCleanupBacklog();
1257
1783
  windowsAdapterCache = null;
1258
1784
  for (const entry of [...windowsAdapterCacheEntries]) {
1259
1785
  entry.retired = true;
1260
1786
  entry.refCount = 0;
1261
- if (!cleanupWindowsAdapterEntry(entry)) cleaned = false;
1787
+ cleanupWindowsAdapterEntry(entry);
1262
1788
  }
1263
- if (!retryWindowsTemporaryCleanupBacklog()) cleaned = false;
1264
- return cleaned;
1789
+ retryWindowsTemporaryCleanupBacklog();
1790
+ return (
1791
+ appContainerCleaned &&
1792
+ windowsTemporaryCleanupBacklog.size === 0 &&
1793
+ windowsAdapterCacheEntries.size === 0
1794
+ );
1795
+ }
1796
+
1797
+ function normalizeWindowsAdapterIdleTtlMs(runtime) {
1798
+ const explicitTtl = runtime.windowsAdapterIdleTtlMs;
1799
+ const environmentTtl =
1800
+ runtime.env?.[WINDOWS_ADAPTER_IDLE_TTL_ENV] ??
1801
+ process.env[WINDOWS_ADAPTER_IDLE_TTL_ENV];
1802
+ const configuredTtl =
1803
+ explicitTtl === undefined ? environmentTtl : explicitTtl;
1804
+ const numericTtl =
1805
+ configuredTtl === "" || configuredTtl === null
1806
+ ? Number.NaN
1807
+ : Number(configuredTtl);
1808
+ return Number.isFinite(numericTtl) && numericTtl >= 0
1809
+ ? numericTtl
1810
+ : WINDOWS_ADAPTER_IDLE_TTL_MS;
1265
1811
  }
1266
1812
 
1267
1813
  function scheduleWindowsAdapterIdleCleanup(entry) {
@@ -1273,11 +1819,11 @@ function scheduleWindowsAdapterIdleCleanup(entry) {
1273
1819
  ) {
1274
1820
  return;
1275
1821
  }
1276
- const configuredTtl = Number(entry.runtime.windowsAdapterIdleTtlMs);
1277
- const idleTtlMs =
1278
- Number.isFinite(configuredTtl) && configuredTtl >= 0
1279
- ? configuredTtl
1280
- : WINDOWS_ADAPTER_IDLE_TTL_MS;
1822
+ const idleTtlMs = entry.idleTtlMs;
1823
+ if (idleTtlMs === 0) {
1824
+ retireWindowsAdapterEntry(entry);
1825
+ return;
1826
+ }
1281
1827
  entry.idleTimer = setTimeout(() => {
1282
1828
  entry.idleTimer = null;
1283
1829
  if (
@@ -1293,6 +1839,7 @@ function scheduleWindowsAdapterIdleCleanup(entry) {
1293
1839
  }
1294
1840
 
1295
1841
  function loadWindowsAdapterSource(runtime, loaderMode) {
1842
+ const tempRootBinding = assertWindowsAdapterTempRoot(runtime);
1296
1843
  const bundled = runtime.windowsAdapterContent === undefined;
1297
1844
  const content = bundled
1298
1845
  ? loaderMode === "managed-executable"
@@ -1307,7 +1854,10 @@ function loadWindowsAdapterSource(runtime, loaderMode) {
1307
1854
  : WINDOWS_SANDBOX_HELPER_ASSEMBLY_DIGEST
1308
1855
  : sha256(content),
1309
1856
  sourceContractDigest: bundled ? WINDOWS_SANDBOX_HELPER_SOURCE_DIGEST : null,
1310
- tempDirectory: runtime.tmpdir(),
1857
+ tempDirectory: tempRootBinding.rootPath,
1858
+ tempRootBinding,
1859
+ tempRootAttestation: tempRootBinding.attestation,
1860
+ idleTtlMs: runtime.windowsAdapterIdleTtlMs,
1311
1861
  loaderMode,
1312
1862
  };
1313
1863
  }
@@ -1329,54 +1879,121 @@ function materializeWindowsAdapter(runtime, source) {
1329
1879
  const assemblyPath = runtime.joinPath(
1330
1880
  adapterDirectory || source.tempDirectory,
1331
1881
  source.loaderMode === "managed-executable"
1332
- ? "windows-sandbox-helper.exe"
1882
+ ? WINDOWS_ADAPTER_HELPER_NAME
1333
1883
  : `${adapterBaseName}.dll`,
1334
1884
  );
1335
- if (
1336
- (adapterDirectory && runtime.fs.existsSync(adapterDirectory)) ||
1337
- runtime.fs.existsSync(assemblyPath)
1338
- ) {
1339
- return { reason: "windows_native_adapter_random_path_collision" };
1340
- }
1885
+ let adapterDirectoryIdentity = null;
1886
+ let assemblyFileIdentity = null;
1341
1887
  try {
1888
+ assertWindowsAdapterTempRoot(runtime);
1342
1889
  if (adapterDirectory) {
1890
+ if (
1891
+ inspectWindowsAdapterBoundPath(runtime, adapterDirectory, "directory", {
1892
+ allowMissing: true,
1893
+ }).exists
1894
+ ) {
1895
+ return { reason: "windows_native_adapter_random_path_collision" };
1896
+ }
1343
1897
  runtime.fs.mkdirSync(adapterDirectory, {
1344
1898
  mode: 0o700,
1345
1899
  recursive: false,
1346
1900
  });
1901
+ adapterDirectoryIdentity = inspectWindowsAdapterBoundPath(
1902
+ runtime,
1903
+ adapterDirectory,
1904
+ "directory",
1905
+ ).identity;
1906
+ }
1907
+ if (
1908
+ inspectWindowsAdapterBoundPath(runtime, assemblyPath, "file", {
1909
+ allowMissing: true,
1910
+ }).exists
1911
+ ) {
1912
+ const collision = new Error(
1913
+ "Windows native adapter random path collision",
1914
+ );
1915
+ collision.code = "EEXIST";
1916
+ throw collision;
1347
1917
  }
1348
1918
  runtime.fs.writeFileSync(assemblyPath, source.content, {
1349
1919
  mode: adapterDirectory ? 0o500 : 0o600,
1350
1920
  flag: "wx",
1351
1921
  });
1922
+ assemblyFileIdentity = inspectWindowsAdapterAssembly(
1923
+ runtime,
1924
+ assemblyPath,
1925
+ ).fileIdentity;
1352
1926
  } catch (error) {
1927
+ if (error?.code === "EEXIST") {
1928
+ const directoryDeleted = cleanupOrTrackWindowsTemporaryDirectory(
1929
+ runtime,
1930
+ adapterDirectory,
1931
+ 100,
1932
+ null,
1933
+ adapterDirectoryIdentity,
1934
+ );
1935
+ return {
1936
+ reason: directoryDeleted
1937
+ ? "windows_native_adapter_random_path_collision"
1938
+ : "windows_native_adapter_compile_cleanup_unverified",
1939
+ };
1940
+ }
1941
+ if (!assemblyFileIdentity) {
1942
+ try {
1943
+ const inspected = inspectWindowsAdapterBoundPath(
1944
+ runtime,
1945
+ assemblyPath,
1946
+ "file",
1947
+ { allowMissing: true },
1948
+ );
1949
+ assemblyFileIdentity = inspected.exists ? inspected.identity : null;
1950
+ } catch {
1951
+ assemblyFileIdentity = null;
1952
+ }
1953
+ }
1353
1954
  const assemblyDeleted = cleanupOrTrackWindowsTemporaryPath(
1354
1955
  runtime,
1355
1956
  assemblyPath,
1957
+ 100,
1958
+ null,
1959
+ assemblyFileIdentity,
1960
+ );
1961
+ const directoryDeleted = cleanupOrTrackWindowsTemporaryDirectory(
1962
+ runtime,
1963
+ adapterDirectory,
1964
+ 100,
1965
+ null,
1966
+ adapterDirectoryIdentity,
1356
1967
  );
1357
- const directoryDeleted =
1358
- assemblyDeleted &&
1359
- cleanupWindowsTemporaryDirectory(runtime, adapterDirectory);
1968
+ if (error?.adapterReason === WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON) {
1969
+ return { reason: WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON };
1970
+ }
1360
1971
  if (!assemblyDeleted || !directoryDeleted) {
1361
1972
  return { reason: "windows_native_adapter_compile_cleanup_unverified" };
1362
1973
  }
1363
- if (error?.code === "EEXIST") {
1364
- return { reason: "windows_native_adapter_random_path_collision" };
1365
- }
1366
1974
  return { reason: "windows_native_adapter_materialize_failed" };
1367
1975
  }
1368
1976
  return {
1369
1977
  adapterDirectory,
1978
+ adapterDirectoryIdentity,
1370
1979
  assemblyPath,
1980
+ assemblyFileIdentity,
1371
1981
  cleanupAssembly: () => {
1372
1982
  const assemblyDeleted = cleanupOrTrackWindowsTemporaryPath(
1373
1983
  runtime,
1374
1984
  assemblyPath,
1985
+ 100,
1986
+ null,
1987
+ assemblyFileIdentity,
1375
1988
  );
1376
- return (
1377
- assemblyDeleted &&
1378
- cleanupWindowsTemporaryDirectory(runtime, adapterDirectory)
1989
+ const directoryDeleted = cleanupOrTrackWindowsTemporaryDirectory(
1990
+ runtime,
1991
+ adapterDirectory,
1992
+ 100,
1993
+ null,
1994
+ adapterDirectoryIdentity,
1379
1995
  );
1996
+ return assemblyDeleted && directoryDeleted;
1380
1997
  },
1381
1998
  };
1382
1999
  }
@@ -1388,9 +2005,7 @@ function materializeWindowsAdapterInvocation(runtime, helperArgs) {
1388
2005
  .randomBytes(24)
1389
2006
  .toString("hex")}.json`,
1390
2007
  );
1391
- if (runtime.fs.existsSync(payloadPath)) {
1392
- return { reason: "windows_adapter_invocation_random_path_collision" };
1393
- }
2008
+ let fileIdentity = null;
1394
2009
  let content;
1395
2010
  try {
1396
2011
  content = Buffer.from(
@@ -1410,16 +2025,57 @@ function materializeWindowsAdapterInvocation(runtime, helperArgs) {
1410
2025
  return { reason: "windows_adapter_invocation_too_large" };
1411
2026
  }
1412
2027
  try {
2028
+ if (
2029
+ inspectWindowsAdapterBoundPath(runtime, payloadPath, "file", {
2030
+ allowMissing: true,
2031
+ }).exists
2032
+ ) {
2033
+ return { reason: "windows_adapter_invocation_random_path_collision" };
2034
+ }
1413
2035
  runtime.fs.writeFileSync(payloadPath, content, {
1414
2036
  mode: 0o600,
1415
2037
  flag: "wx",
1416
2038
  });
2039
+ const inspected = inspectWindowsAdapterAssembly(runtime, payloadPath);
2040
+ if (inspected.assemblyDigest !== sha256(content)) {
2041
+ throw new Error("Windows adapter invocation changed after creation");
2042
+ }
2043
+ fileIdentity = inspected.fileIdentity;
1417
2044
  } catch (error) {
2045
+ if (!fileIdentity) {
2046
+ try {
2047
+ const inspected = inspectWindowsAdapterBoundPath(
2048
+ runtime,
2049
+ payloadPath,
2050
+ "file",
2051
+ { allowMissing: true },
2052
+ );
2053
+ fileIdentity = inspected.exists ? inspected.identity : null;
2054
+ } catch {
2055
+ fileIdentity = null;
2056
+ }
2057
+ }
2058
+ if (error?.adapterReason === WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON) {
2059
+ cleanupOrTrackWindowsTemporaryPath(
2060
+ runtime,
2061
+ payloadPath,
2062
+ 100,
2063
+ null,
2064
+ fileIdentity,
2065
+ );
2066
+ return { reason: WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON };
2067
+ }
1418
2068
  if (error?.code === "EEXIST") {
1419
2069
  return { reason: "windows_adapter_invocation_random_path_collision" };
1420
2070
  }
1421
2071
  return {
1422
- reason: cleanupOrTrackWindowsTemporaryPath(runtime, payloadPath)
2072
+ reason: cleanupOrTrackWindowsTemporaryPath(
2073
+ runtime,
2074
+ payloadPath,
2075
+ 100,
2076
+ null,
2077
+ fileIdentity,
2078
+ )
1423
2079
  ? "windows_adapter_invocation_materialize_failed"
1424
2080
  : "windows_adapter_invocation_cleanup_unverified",
1425
2081
  };
@@ -1427,7 +2083,24 @@ function materializeWindowsAdapterInvocation(runtime, helperArgs) {
1427
2083
  return {
1428
2084
  payloadPath,
1429
2085
  payloadDigest: sha256(content),
1430
- cleanup: () => cleanupOrTrackWindowsTemporaryPath(runtime, payloadPath),
2086
+ fileIdentity,
2087
+ verify: () => {
2088
+ const inspected = assertWindowsAdapterBoundPathIdentity(
2089
+ runtime,
2090
+ payloadPath,
2091
+ "file",
2092
+ fileIdentity,
2093
+ );
2094
+ return inspected.exists;
2095
+ },
2096
+ cleanup: () =>
2097
+ cleanupOrTrackWindowsTemporaryPath(
2098
+ runtime,
2099
+ payloadPath,
2100
+ 100,
2101
+ null,
2102
+ fileIdentity,
2103
+ ),
1431
2104
  };
1432
2105
  }
1433
2106
 
@@ -1483,6 +2156,7 @@ function buildWindowsAdapterInvocation(entry, invocation) {
1483
2156
  invocation.payloadPath,
1484
2157
  invocation.payloadDigest,
1485
2158
  ],
2159
+ verify: invocation.verify,
1486
2160
  cleanup: invocation.cleanup,
1487
2161
  };
1488
2162
  }
@@ -1497,6 +2171,7 @@ function buildWindowsAdapterInvocation(entry, invocation) {
1497
2171
  "-EncodedCommand",
1498
2172
  windowsPowerShellBootstrap(entry, invocation),
1499
2173
  ],
2174
+ verify: invocation.verify,
1500
2175
  cleanup: invocation.cleanup,
1501
2176
  };
1502
2177
  }
@@ -1583,11 +2258,14 @@ function createFreshWindowsAdapter(runtime, source, hostEnvironment) {
1583
2258
  runtime,
1584
2259
  runtimeFs: runtime.fs,
1585
2260
  tempDirectory: source.tempDirectory,
2261
+ tempRootBinding: source.tempRootBinding,
2262
+ idleTtlMs: source.idleTtlMs,
1586
2263
  sourceDigest: source.sourceDigest,
1587
2264
  sourceContractDigest: source.sourceContractDigest,
1588
2265
  loaderMode: source.loaderMode,
1589
2266
  powershellPath: powershellHosts[0] || null,
1590
2267
  adapterDirectory: materialized.adapterDirectory,
2268
+ adapterDirectoryIdentity: materialized.adapterDirectoryIdentity,
1591
2269
  assemblyPath: materialized.assemblyPath,
1592
2270
  assemblyDigest: attestation.assemblyDigest,
1593
2271
  fileIdentity: attestation.fileIdentity,
@@ -1743,7 +2421,9 @@ function createWindowsAdapterController(runtime, source, hostEnvironment) {
1743
2421
  error.adapterReason = payload.reason;
1744
2422
  throw error;
1745
2423
  }
1746
- return buildWindowsAdapterInvocation(entry, payload);
2424
+ const invocation = buildWindowsAdapterInvocation(entry, payload);
2425
+ invocation.verify();
2426
+ return invocation;
1747
2427
  };
1748
2428
 
1749
2429
  return {
@@ -1754,6 +2434,7 @@ function createWindowsAdapterController(runtime, source, hostEnvironment) {
1754
2434
  let result;
1755
2435
  let failure;
1756
2436
  try {
2437
+ invocation.verify();
1757
2438
  result = runtime.spawnSync(invocation.command, invocation.args, {
1758
2439
  ...options,
1759
2440
  cwd: runtime.joinPath(runtime.windowsDir(), "System32"),
@@ -1781,20 +2462,40 @@ function createWindowsAdapterController(runtime, source, hostEnvironment) {
1781
2462
  };
1782
2463
  }
1783
2464
 
2465
+ function readWindowsAdapterBoundFile(runtime, filePath, encoding = null) {
2466
+ const before = inspectWindowsAdapterBoundPath(runtime, filePath, "file");
2467
+ const content = runtime.fs.readFileSync(filePath, encoding || undefined);
2468
+ const after = assertWindowsAdapterBoundPathIdentity(
2469
+ runtime,
2470
+ filePath,
2471
+ "file",
2472
+ before.identity,
2473
+ );
2474
+ if (!sameWindowsFileIdentity(before.identity, after.identity)) {
2475
+ throw windowsAdapterTempRootChangedError();
2476
+ }
2477
+ return { content, fileIdentity: after.identity };
2478
+ }
2479
+
1784
2480
  function waitForWindowsTargetIdentity(
1785
2481
  proc,
1786
2482
  identityPath,
1787
2483
  runtime,
1788
2484
  expectedAppContainer = null,
1789
2485
  timeoutMs = 30_000,
2486
+ onIdentityFileAttested = null,
1790
2487
  ) {
1791
2488
  const deadline = runtime.now() + timeoutMs;
1792
2489
  let lastError;
1793
2490
  while (runtime.now() < deadline) {
1794
2491
  try {
1795
- const identity = JSON.parse(
1796
- runtime.fs.readFileSync(identityPath, "utf8"),
2492
+ const identityFile = readWindowsAdapterBoundFile(
2493
+ runtime,
2494
+ identityPath,
2495
+ "utf8",
1797
2496
  );
2497
+ onIdentityFileAttested?.(identityFile.fileIdentity);
2498
+ const identity = JSON.parse(identityFile.content);
1798
2499
  if (identity?.error) {
1799
2500
  throw new Error(
1800
2501
  `Windows sandbox helper rejected the target: ${identity.error}`,
@@ -1846,6 +2547,7 @@ function waitForWindowsTargetIdentity(
1846
2547
  proc.sandboxAppContainerProfile =
1847
2548
  expectedAppContainer.appContainerProfileName;
1848
2549
  proc.sandboxAppContainerSid = expectedAppContainer.appContainerSid;
2550
+ proc.sandboxAppContainerCapabilityCount = identity.capabilityCount;
1849
2551
  }
1850
2552
  // ChildProcess.pid is a writable data property. The native ChildProcess
1851
2553
  // handle still points at the helper, so kill()/ref()/unref() retain Job
@@ -2049,6 +2751,7 @@ function appContainerReadinessResult(
2049
2751
  ...runtimeProbe,
2050
2752
  runnable: true,
2051
2753
  reason: null,
2754
+ capabilityCount: readiness.capabilityCount,
2052
2755
  ...(targetRuntime
2053
2756
  ? {
2054
2757
  probeRuntime: targetRuntime,
@@ -2610,11 +3313,33 @@ export function applyWindowsSandbox(
2610
3313
  ) {
2611
3314
  return unavailablePlan("windows_git_nested_process_compatibility");
2612
3315
  }
3316
+ const windowsAdapterTempRootBinding = resolveWindowsAdapterTempRoot(
3317
+ runtime,
3318
+ runtimeOverrides,
3319
+ );
3320
+ if (!windowsAdapterTempRootBinding) {
3321
+ return unavailablePlan(WINDOWS_ADAPTER_TEMP_ROOT_UNTRUSTED_REASON);
3322
+ }
3323
+ const windowsAdapterIdleTtlMs = normalizeWindowsAdapterIdleTtlMs(runtime);
3324
+ runtime = {
3325
+ ...runtime,
3326
+ windowsAdapterTempRoot: windowsAdapterTempRootBinding.rootPath,
3327
+ windowsAdapterTempRootBinding,
3328
+ windowsAdapterIdleTtlMs,
3329
+ tmpdir: () => windowsAdapterTempRootBinding.rootPath,
3330
+ };
2613
3331
  const loaderMode =
2614
3332
  profile !== "strict" && !requiresAppContainer && !entrySnapshot.locks
2615
3333
  ? "managed-executable"
2616
3334
  : "powershell-byte-assembly";
2617
- const adapterSource = loadWindowsAdapterSource(runtime, loaderMode);
3335
+ let adapterSource;
3336
+ try {
3337
+ adapterSource = loadWindowsAdapterSource(runtime, loaderMode);
3338
+ } catch (error) {
3339
+ return unavailablePlan(
3340
+ error?.adapterReason || WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON,
3341
+ );
3342
+ }
2618
3343
  if (!adapterSource.content) {
2619
3344
  return unavailablePlan(adapterSource.reason);
2620
3345
  }
@@ -2659,6 +3384,22 @@ export function applyWindowsSandbox(
2659
3384
  .toString("hex")}.json`,
2660
3385
  )
2661
3386
  : null;
3387
+ let identityFileIdentity = null;
3388
+ if (identityPath) {
3389
+ try {
3390
+ if (
3391
+ inspectWindowsAdapterBoundPath(runtime, identityPath, "file", {
3392
+ allowMissing: true,
3393
+ }).exists
3394
+ ) {
3395
+ return unavailablePlan("windows_identity_random_path_collision");
3396
+ }
3397
+ } catch (error) {
3398
+ return unavailablePlan(
3399
+ error?.adapterReason || WINDOWS_ADAPTER_TEMP_ROOT_CHANGED_REASON,
3400
+ );
3401
+ }
3402
+ }
2662
3403
  const launchSpec = {
2663
3404
  cpuSeconds: Number(limits.cpu || 0),
2664
3405
  processMemoryBytes: Number(
@@ -2699,7 +3440,13 @@ export function applyWindowsSandbox(
2699
3440
  if (identityPath) launchSpec.identityPath = identityPath;
2700
3441
  const cleanupIdentity = () => {
2701
3442
  if (!identityPath) return true;
2702
- return cleanupOrTrackWindowsTemporaryPath(runtime, identityPath);
3443
+ return cleanupOrTrackWindowsTemporaryPath(
3444
+ runtime,
3445
+ identityPath,
3446
+ 100,
3447
+ null,
3448
+ identityFileIdentity,
3449
+ );
2703
3450
  };
2704
3451
  const appContainerProfileName = requiresAppContainer
2705
3452
  ? `ChainlessChain.CliSandbox.${runtime.randomBytes(12).toString("hex")}`
@@ -2973,6 +3720,10 @@ export function applyWindowsSandbox(
2973
3720
  identityPath,
2974
3721
  runtime,
2975
3722
  appContainer,
3723
+ 30_000,
3724
+ (fileIdentity) => {
3725
+ identityFileIdentity = fileIdentity;
3726
+ },
2976
3727
  );
2977
3728
  if (!cleanupIdentity()) {
2978
3729
  throw new Error(
@@ -3064,6 +3815,7 @@ export function applyWindowsSandbox(
3064
3815
  enforcement: backend,
3065
3816
  policyAttested: requiresAppContainer ? true : null,
3066
3817
  policyDigest: appContainerPolicyDigest,
3818
+ adapterTempRootAttestation: adapterSource.tempRootAttestation,
3067
3819
  runtimeProbe: entrySnapshotRuntimeProbe,
3068
3820
  guarantees: [
3069
3821
  ...(requiresAppContainer
@@ -3130,6 +3882,7 @@ function linuxBubblewrapProbe(
3130
3882
  supervisorBinding = null,
3131
3883
  pluginTreeSnapshot = null,
3132
3884
  nativeDynamicClosure = null,
3885
+ runtimeDetachedChildSpawnVerified = false,
3133
3886
  ) {
3134
3887
  const native =
3135
3888
  typeof targetRuntime === "string" && targetRuntime.startsWith("native-");
@@ -3143,13 +3896,12 @@ function linuxBubblewrapProbe(
3143
3896
  const supervisorDescriptorBound =
3144
3897
  supervisorBinding?.mechanism === LINUX_BWRAP_SUPERVISOR_BINDING_MECHANISM;
3145
3898
  const pluginTreeContentSnapshot =
3146
- !native &&
3147
3899
  attempted &&
3148
3900
  snapshot &&
3149
3901
  runnable &&
3150
3902
  reason === null &&
3151
3903
  supervisorDescriptorBound &&
3152
- pluginTreeSnapshot?.scope === LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE &&
3904
+ pluginTreeSnapshot?.scope === LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE &&
3153
3905
  pluginTreeSnapshot?.mechanism === LINUX_ENTRY_SNAPSHOT_MECHANISM &&
3154
3906
  Number.isSafeInteger(pluginTreeSnapshot?.files) &&
3155
3907
  pluginTreeSnapshot.files > 0 &&
@@ -3160,7 +3912,7 @@ function linuxBubblewrapProbe(
3160
3912
  typeof pluginTreeSnapshot?.digest === "string" &&
3161
3913
  /^[a-f0-9]{64}$/.test(pluginTreeSnapshot.digest) &&
3162
3914
  pluginTreeSnapshot?.consistency ===
3163
- LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY &&
3915
+ LINUX_PLUGIN_TREE_SNAPSHOT_CONSISTENCY &&
3164
3916
  pluginTreeSnapshot?.contractBound === false &&
3165
3917
  pluginTreeSnapshot?.atomic === false;
3166
3918
  const initialDynamicLoadClosureDescriptorBound =
@@ -3196,6 +3948,7 @@ function linuxBubblewrapProbe(
3196
3948
  reason,
3197
3949
  probeRuntime: "node",
3198
3950
  targetRuntime,
3951
+ runtimeDetachedChildSpawnVerified,
3199
3952
  contentSnapshot: snapshot,
3200
3953
  ...(snapshot
3201
3954
  ? {
@@ -3775,16 +4528,20 @@ function linuxEntrySnapshotContract(targetRuntime) {
3775
4528
  throw new Error("entry_snapshot_runtime_unsupported");
3776
4529
  }
3777
4530
 
3778
- function linuxNodePluginTreeFileSnapshotContract(sourceMode) {
4531
+ function linuxPluginTreeFileSnapshotContract(sourceMode, targetRuntime) {
4532
+ const native = targetRuntime?.startsWith("native-");
4533
+ const errorPrefix = native
4534
+ ? "native_plugin_tree_snapshot"
4535
+ : "node_plugin_tree_snapshot";
3779
4536
  if (!Number.isSafeInteger(sourceMode) || sourceMode < 0) {
3780
- throw new Error("node_plugin_tree_snapshot_source_mode_invalid");
4537
+ throw new Error(`${errorPrefix}_source_mode_invalid`);
3781
4538
  }
3782
4539
  return {
3783
- scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
4540
+ scope: LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE,
3784
4541
  targetMode: (sourceMode & 0o111) !== 0 ? "0500" : "0400",
3785
4542
  sourceMode: LINUX_ENTRY_SNAPSHOT_SOURCE_MODE,
3786
4543
  minimumBytes: 0,
3787
- errorPrefix: "node_plugin_tree_snapshot",
4544
+ errorPrefix,
3788
4545
  };
3789
4546
  }
3790
4547
 
@@ -3865,9 +4622,9 @@ function createLinuxRegularFileSnapshot(
3865
4622
  throw snapshotError("source_changed");
3866
4623
  }
3867
4624
  if (
3868
- snapshotContract.scope === LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE &&
4625
+ snapshotContract.scope === LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE &&
3869
4626
  snapshotContract.targetMode !==
3870
- linuxNodePluginTreeFileSnapshotContract(sourceFileMode).targetMode
4627
+ ((sourceFileMode & 0o111) !== 0 ? "0500" : "0400")
3871
4628
  ) {
3872
4629
  throw snapshotError("source_mode_changed");
3873
4630
  }
@@ -4328,7 +5085,21 @@ function applyLinuxMcpCapsuleCodeSnapshot(
4328
5085
  });
4329
5086
  }
4330
5087
 
4331
- function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
5088
+ /**
5089
+ * Bind the synthetic plugin tree to the anonymous content snapshots mounted
5090
+ * for launch. Directory membership is fixed by pinLinuxPluginTree; contents
5091
+ * are copied and sealed per file, so the evidence deliberately remains
5092
+ * `atomic: false` rather than claiming one filesystem-wide transaction.
5093
+ */
5094
+ function buildLinuxPluginTreeSnapshotAttestation(
5095
+ mounts,
5096
+ entryDestination,
5097
+ targetRuntime,
5098
+ ) {
5099
+ const native = targetRuntime?.startsWith("native-");
5100
+ const errorPrefix = native
5101
+ ? "native_plugin_tree_snapshot"
5102
+ : "node_plugin_tree_snapshot";
4332
5103
  const members = [];
4333
5104
  const destinations = new Set();
4334
5105
  for (const mount of mounts || []) {
@@ -4337,18 +5108,23 @@ function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
4337
5108
  path.posix.normalize(mount.destination) !== mount.destination ||
4338
5109
  destinations.has(mount.destination)
4339
5110
  ) {
4340
- throw new Error("node_plugin_tree_snapshot_destination_invalid");
5111
+ throw new Error(`${errorPrefix}_destination_invalid`);
4341
5112
  }
4342
5113
  destinations.add(mount.destination);
4343
5114
  const snapshot = mount.contentSnapshot;
4344
5115
  const entry = mount.destination === entryDestination;
4345
5116
  const expectedScope = entry
4346
- ? LINUX_NODE_ENTRY_SNAPSHOT_SCOPE
4347
- : LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE;
5117
+ ? native
5118
+ ? LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE
5119
+ : LINUX_NODE_ENTRY_SNAPSHOT_SCOPE
5120
+ : LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE;
4348
5121
  const sourceMode = Number.parseInt(snapshot?.sourceFileMode, 8);
4349
5122
  const expectedTargetMode = entry
4350
- ? LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE
4351
- : linuxNodePluginTreeFileSnapshotContract(sourceMode).targetMode;
5123
+ ? native
5124
+ ? LINUX_NATIVE_ENTRY_SNAPSHOT_TARGET_MODE
5125
+ : LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE
5126
+ : linuxPluginTreeFileSnapshotContract(sourceMode, targetRuntime)
5127
+ .targetMode;
4352
5128
  if (
4353
5129
  snapshot?.scope !== expectedScope ||
4354
5130
  snapshot?.mechanism !== LINUX_ENTRY_SNAPSHOT_MECHANISM ||
@@ -4371,7 +5147,7 @@ function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
4371
5147
  mount.mountMode !== "ro-bind-data" ||
4372
5148
  mount.mountPermissions !== expectedTargetMode
4373
5149
  ) {
4374
- throw new Error("node_plugin_tree_snapshot_member_invalid");
5150
+ throw new Error(`${errorPrefix}_member_invalid`);
4375
5151
  }
4376
5152
  members.push({
4377
5153
  destination: mount.destination,
@@ -4398,58 +5174,66 @@ function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
4398
5174
  members.length > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES ||
4399
5175
  !members.some((member) => member.destination === entryDestination)
4400
5176
  ) {
4401
- throw new Error("node_plugin_tree_snapshot_file_count_invalid");
5177
+ throw new Error(`${errorPrefix}_file_count_invalid`);
4402
5178
  }
4403
5179
  const bytes = members.reduce((total, member) => total + member.bytes, 0);
4404
5180
  if (
4405
5181
  !Number.isSafeInteger(bytes) ||
4406
5182
  bytes > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES
4407
5183
  ) {
4408
- throw new Error("node_plugin_tree_snapshot_bytes_exceeded");
5184
+ throw new Error(`${errorPrefix}_bytes_exceeded`);
4409
5185
  }
4410
5186
  const digest = crypto
4411
5187
  .createHash("sha256")
4412
5188
  .update(
4413
5189
  JSON.stringify({
4414
5190
  version: 1,
4415
- scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
4416
- consistency: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
5191
+ scope: LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE,
5192
+ ...(native ? { targetRuntime } : {}),
5193
+ consistency: LINUX_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
4417
5194
  members,
4418
5195
  }),
4419
5196
  )
4420
5197
  .digest("hex");
4421
5198
  return Object.freeze({
4422
- scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
5199
+ scope: LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE,
4423
5200
  mechanism: LINUX_ENTRY_SNAPSHOT_MECHANISM,
4424
5201
  files: members.length,
4425
5202
  bytes,
4426
5203
  digest,
4427
- consistency: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
5204
+ consistency: LINUX_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
4428
5205
  contractBound: false,
4429
5206
  atomic: false,
4430
5207
  });
4431
5208
  }
4432
5209
 
4433
- function attestLinuxNodePluginTreeSnapshot(
5210
+ function attestLinuxPluginTreeSnapshot(
4434
5211
  runtime,
4435
5212
  mounts,
4436
5213
  entryDestination,
5214
+ targetRuntime,
4437
5215
  expected,
4438
5216
  ) {
5217
+ const native = targetRuntime?.startsWith("native-");
5218
+ const errorPrefix = native
5219
+ ? "native_plugin_tree_snapshot"
5220
+ : "node_plugin_tree_snapshot";
4439
5221
  for (const mount of mounts || []) {
4440
5222
  if (!linuxPathWithin("/opt/chainless/plugin", mount?.destination)) continue;
4441
5223
  const snapshot = mount.contentSnapshot;
4442
5224
  const contract =
4443
5225
  mount.destination === entryDestination
4444
- ? linuxEntrySnapshotContract("node")
4445
- : linuxNodePluginTreeFileSnapshotContract(
5226
+ ? linuxEntrySnapshotContract(targetRuntime)
5227
+ : linuxPluginTreeFileSnapshotContract(
4446
5228
  Number.parseInt(snapshot?.sourceFileMode, 8),
5229
+ targetRuntime,
4447
5230
  );
4448
5231
  attestLinuxRegularFileSnapshot(runtime, mount, snapshot, contract);
4449
5232
  }
4450
- const actual = buildLinuxNodePluginTreeSnapshotAttestation(
5233
+ const actual = buildLinuxPluginTreeSnapshotAttestation(
4451
5234
  mounts,
4452
5235
  entryDestination,
5236
+ targetRuntime,
4453
5237
  );
4454
5238
  for (const field of [
4455
5239
  "scope",
@@ -4462,7 +5246,7 @@ function attestLinuxNodePluginTreeSnapshot(
4462
5246
  "atomic",
4463
5247
  ]) {
4464
5248
  if (actual[field] !== expected?.[field]) {
4465
- throw new Error("node_plugin_tree_snapshot_identity_changed");
5249
+ throw new Error(`${errorPrefix}_identity_changed`);
4466
5250
  }
4467
5251
  }
4468
5252
  }
@@ -6014,8 +6798,22 @@ function probeLinuxBubblewrapPolicy(
6014
6798
  ino: supervisorBinding.fileId.ino,
6015
6799
  bytes: supervisorBinding.bytes,
6016
6800
  });
6801
+ const childProbeSource = [
6802
+ 'const fs = require("node:fs");',
6803
+ "const sentinel = process.argv[1];",
6804
+ 'const raw = fs.readFileSync("/proc/self/stat", "utf8");',
6805
+ 'const close = raw.lastIndexOf(")");',
6806
+ 'const pid = Number(raw.slice(0, raw.indexOf(" ")));',
6807
+ "const fields = raw.slice(close + 2).trim().split(/\\s+/);",
6808
+ "const processGroupPid = Number(fields[2]);",
6809
+ "const sessionPid = Number(fields[3]);",
6810
+ "fs.writeSync(1, JSON.stringify({ sentinel, pid, processGroupPid, sessionPid }));",
6811
+ "if (processGroupPid !== pid || sessionPid !== pid) process.exit(2);",
6812
+ ].join("\n");
6017
6813
  const probeSource = [
6018
6814
  'const fs = require("node:fs");',
6815
+ 'const crypto = require("node:crypto");',
6816
+ 'const { spawnSync } = require("node:child_process");',
6019
6817
  `const expected = ${supervisorIdentity};`,
6020
6818
  'for (const name of fs.readdirSync("/proc/self/fd")) {',
6021
6819
  " let current;",
@@ -6032,10 +6830,62 @@ function probeLinuxBubblewrapPolicy(
6032
6830
  "} catch (error) {",
6033
6831
  ' if (error?.code !== "ENOENT") throw error;',
6034
6832
  "}",
6833
+ "// /tmp is a fresh private bwrap tmpfs; read the same O_EXCL inode by fd.",
6834
+ 'const childReportPath = "/tmp/.chainless-runtime-child-probe.json";',
6835
+ `const expectedChildSentinel = ${JSON.stringify(
6836
+ `${LINUX_BWRAP_CHILD_RUNTIME_PROBE_SENTINEL}:`,
6837
+ )} + crypto.randomBytes(16).toString("hex");`,
6838
+ "let reportFd;",
6839
+ "let runtimeDetachedChildSpawnVerified = false;",
6840
+ "try {",
6841
+ ' reportFd = fs.openSync(childReportPath, "wx+", 0o600);',
6842
+ ` const child = spawnSync("/opt/chainless/runtime/node", ["-e", ${JSON.stringify(
6843
+ childProbeSource,
6844
+ )}, expectedChildSentinel], {`,
6845
+ ' cwd: "/",',
6846
+ " detached: true,",
6847
+ " env: process.env,",
6848
+ " shell: false,",
6849
+ ' stdio: ["ignore", reportFd, "ignore"],',
6850
+ " timeout: 5_000,",
6851
+ " });",
6852
+ " const reportStat = fs.fstatSync(reportFd);",
6853
+ " const reportBytes = Number(reportStat.size);",
6854
+ " if (!child.error && child.status === 0 && child.signal === null &&",
6855
+ " Number.isSafeInteger(child.pid) && child.pid > 0 &&",
6856
+ " reportStat.isFile() && Number.isSafeInteger(reportBytes) &&",
6857
+ " reportBytes > 0 && reportBytes <= 1_024) {",
6858
+ " const reportBuffer = Buffer.alloc(reportBytes);",
6859
+ " const reportRead = fs.readSync(reportFd, reportBuffer, 0, reportBytes, 0);",
6860
+ " if (reportRead === reportBytes) {",
6861
+ ' const reportText = reportBuffer.toString("utf8");',
6862
+ " const report = JSON.parse(reportText);",
6863
+ " const canonicalReport = JSON.stringify({ sentinel: report.sentinel,",
6864
+ " pid: report.pid, processGroupPid: report.processGroupPid,",
6865
+ " sessionPid: report.sessionPid });",
6866
+ " runtimeDetachedChildSpawnVerified = reportText === canonicalReport &&",
6867
+ " report.sentinel === expectedChildSentinel &&",
6868
+ " Number.isSafeInteger(report.pid) && report.pid > 0 &&",
6869
+ " report.pid === child.pid && report.processGroupPid === report.pid &&",
6870
+ " report.sessionPid === report.pid;",
6871
+ " }",
6872
+ " }",
6873
+ "} catch {} finally {",
6874
+ " try { if (Number.isInteger(reportFd)) fs.closeSync(reportFd); } catch {",
6875
+ " runtimeDetachedChildSpawnVerified = false;",
6876
+ " }",
6877
+ " try { fs.unlinkSync(childReportPath); } catch {",
6878
+ " runtimeDetachedChildSpawnVerified = false;",
6879
+ " }",
6880
+ "}",
6881
+ `if (!runtimeDetachedChildSpawnVerified) process.exit(${LINUX_BWRAP_CHILD_RUNTIME_PROBE_FAILURE_STATUS});`,
6035
6882
  `process.stdout.write(${JSON.stringify(LINUX_BWRAP_NODE_PROBE_SENTINEL)});`,
6036
6883
  ].join("\n");
6037
6884
  let result;
6038
6885
  try {
6886
+ // These pipes connect the host Broker to bwrap and are created before
6887
+ // bwrap installs the target seccomp filter. The nested runtime probe above
6888
+ // inherits a regular-file descriptor and never requests UV_CREATE_PIPE.
6039
6889
  result = runtime.spawnSync(
6040
6890
  supervisorLaunch.command,
6041
6891
  [...policyArgs, "--", "/opt/chainless/runtime/node", "-e", probeSource],
@@ -6069,7 +6919,11 @@ function probeLinuxBubblewrapPolicy(
6069
6919
  return linuxBubblewrapProbe(
6070
6920
  true,
6071
6921
  false,
6072
- result?.error ? "probe_spawn_failed" : "probe_failed",
6922
+ result?.error
6923
+ ? "probe_spawn_failed"
6924
+ : result?.status === LINUX_BWRAP_CHILD_RUNTIME_PROBE_FAILURE_STATUS
6925
+ ? "detached_child_runtime_probe_failed"
6926
+ : "probe_failed",
6073
6927
  targetRuntime,
6074
6928
  contentSnapshot,
6075
6929
  supervisorBinding,
@@ -6092,6 +6946,9 @@ function probeLinuxBubblewrapPolicy(
6092
6946
  targetRuntime,
6093
6947
  contentSnapshot,
6094
6948
  supervisorBinding,
6949
+ null,
6950
+ null,
6951
+ true,
6095
6952
  );
6096
6953
  }
6097
6954
 
@@ -6360,30 +7217,33 @@ export function applyLinuxSandbox(
6360
7217
  const rawPluginMounts = pluginMountIndexes.map(
6361
7218
  (index) => pinnedMounts[index],
6362
7219
  );
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);
7220
+ const pluginSnapshotErrorPrefix = validation.entryRuntime.startsWith(
7221
+ "native-",
7222
+ )
7223
+ ? "native_plugin_tree_snapshot"
7224
+ : "node_plugin_tree_snapshot";
7225
+ if (
7226
+ pluginMountIndexes.length < 1 ||
7227
+ pluginMountIndexes.length > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES
7228
+ ) {
7229
+ throw new Error(`${pluginSnapshotErrorPrefix}_file_count_invalid`);
7230
+ }
7231
+ const pluginBytes = pluginMountIndexes.reduce((total, index) => {
7232
+ const bytes = pinnedMounts[index]?.bytes;
6381
7233
  if (
6382
- !Number.isSafeInteger(pluginBytes) ||
6383
- pluginBytes > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES
7234
+ !Number.isSafeInteger(bytes) ||
7235
+ bytes < 0 ||
7236
+ bytes > LINUX_ATTESTED_FILE_MAX_BYTES
6384
7237
  ) {
6385
- throw new Error("node_plugin_tree_snapshot_bytes_exceeded");
7238
+ throw new Error(`${pluginSnapshotErrorPrefix}_member_size_invalid`);
6386
7239
  }
7240
+ return total + bytes;
7241
+ }, 0);
7242
+ if (
7243
+ !Number.isSafeInteger(pluginBytes) ||
7244
+ pluginBytes > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES
7245
+ ) {
7246
+ throw new Error(`${pluginSnapshotErrorPrefix}_bytes_exceeded`);
6387
7247
  }
6388
7248
  const rawEntryMount = entryMount;
6389
7249
  const snapshot = createLinuxEntrySnapshot(
@@ -6397,37 +7257,36 @@ export function applyLinuxSandbox(
6397
7257
  finalMounts[entryIndex] = snapshot.finalMount;
6398
7258
  probeMounts[entryIndex] = snapshot.probeMount;
6399
7259
  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,
7260
+ for (const index of pluginMountIndexes) {
7261
+ if (index === entryIndex) continue;
7262
+ const rawMount = pinnedMounts[index];
7263
+ const source = runtime.fs.fstatSync(rawMount.fd);
7264
+ const fileSnapshot = createLinuxRegularFileSnapshot(
7265
+ runtime,
7266
+ rawMount,
7267
+ {
7268
+ fileId: rawMount.fileId,
7269
+ bytes: rawMount.bytes,
7270
+ mtimeMs: rawMount.mtimeMs,
7271
+ },
7272
+ linuxPluginTreeFileSnapshotContract(
7273
+ Number(source.mode),
7274
+ validation.entryRuntime,
7275
+ ),
6422
7276
  );
7277
+ pluginTreeFileSnapshots.push(fileSnapshot);
7278
+ finalMounts[index] = fileSnapshot.finalMount;
7279
+ probeMounts[index] = fileSnapshot.probeMount;
6423
7280
  }
7281
+ pluginTreeSnapshot = buildLinuxPluginTreeSnapshotAttestation(
7282
+ finalMounts,
7283
+ sandboxEntry,
7284
+ validation.entryRuntime,
7285
+ );
6424
7286
  pinnedMounts = finalMounts;
6425
7287
  probePinnedMounts = probeMounts;
6426
7288
  entryMount = entrySnapshot.finalMount;
6427
- closeLinuxPinnedMounts(
6428
- runtime,
6429
- validation.entryRuntime === "node" ? rawPluginMounts : [rawEntryMount],
6430
- );
7289
+ closeLinuxPinnedMounts(runtime, rawPluginMounts);
6431
7290
  } catch (error) {
6432
7291
  closeLinuxPinnedMounts(runtime, pinnedMounts);
6433
7292
  closeLinuxPinnedMounts(runtime, [
@@ -6607,7 +7466,7 @@ export function applyLinuxSandbox(
6607
7466
  .createHash("sha256")
6608
7467
  .update(
6609
7468
  JSON.stringify({
6610
- version: validation.entryRuntime === "node" ? 4 : 3,
7469
+ version: 4,
6611
7470
  backend: LINUX_BWRAP_BACKEND,
6612
7471
  supervisor: supervisorPin.attestation,
6613
7472
  contract: {
@@ -6651,9 +7510,7 @@ export function applyLinuxSandbox(
6651
7510
  ...(nativeDynamicClosure
6652
7511
  ? { initialDynamicLoadClosure: nativeDynamicClosure }
6653
7512
  : {}),
6654
- ...(validation.entryRuntime === "node"
6655
- ? { pluginTreeContentSnapshot: pluginTreeSnapshot }
6656
- : {}),
7513
+ pluginTreeContentSnapshot: pluginTreeSnapshot,
6657
7514
  seccomp: {
6658
7515
  arch: seccompFilter.arch,
6659
7516
  policy: seccompFilter.policy,
@@ -6726,21 +7583,13 @@ export function applyLinuxSandbox(
6726
7583
  });
6727
7584
  }
6728
7585
  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
- }
7586
+ attestLinuxPluginTreeSnapshot(
7587
+ runtime,
7588
+ pinnedMounts,
7589
+ sandboxEntry,
7590
+ validation.entryRuntime,
7591
+ pluginTreeSnapshot,
7592
+ );
6744
7593
  } catch (error) {
6745
7594
  closeStrongLinuxResources(supervisorPin, finalLaunch);
6746
7595
  return createSandboxPlan({
@@ -6819,6 +7668,7 @@ export function applyLinuxSandbox(
6819
7668
  supervisorBinding,
6820
7669
  pluginTreeSnapshot,
6821
7670
  nativeDynamicClosure,
7671
+ policyProbe.runtimeDetachedChildSpawnVerified === true,
6822
7672
  );
6823
7673
  const runtimeProbe = Object.freeze(
6824
7674
  capsuleContract
@@ -6961,12 +7811,17 @@ export function applySandbox(
6961
7811
  explicitRequest,
6962
7812
  );
6963
7813
  const profileName = sandboxRequest.profile;
7814
+ // Windows does not consume the POSIX allow-write lists. Avoid invoking an
7815
+ // embedding temp-directory hook while eagerly constructing three unused
7816
+ // profiles; applyWindowsSandbox resolves and pins that hook exactly once.
7817
+ const profileTempDirectory =
7818
+ runtime.platform === "win32" ? null : runtime.tmpdir();
6964
7819
  const profiles = {
6965
7820
  default: {
6966
7821
  allowNetwork: false,
6967
7822
  allowExec: true,
6968
7823
  allowRead: [runtime.homedir(), "/tmp"],
6969
- allowWrite: [runtime.tmpdir()],
7824
+ allowWrite: profileTempDirectory ? [profileTempDirectory] : [],
6970
7825
  limits: {
6971
7826
  // RLIMIT_NPROC is intentionally omitted. Linux applies it per real
6972
7827
  // user ID rather than to this child tree, so it can starve unrelated
@@ -6979,7 +7834,7 @@ export function applySandbox(
6979
7834
  allowNetwork: false,
6980
7835
  allowExec: false,
6981
7836
  allowRead: [],
6982
- allowWrite: [runtime.tmpdir()],
7837
+ allowWrite: profileTempDirectory ? [profileTempDirectory] : [],
6983
7838
  limits: {
6984
7839
  cpu: 10,
6985
7840
  as: 256 * 1024 * 1024, // 256MB address space
@@ -6990,7 +7845,7 @@ export function applySandbox(
6990
7845
  allowNetwork: true,
6991
7846
  allowExec: true,
6992
7847
  allowRead: [runtime.homedir()],
6993
- allowWrite: [runtime.tmpdir()],
7848
+ allowWrite: profileTempDirectory ? [profileTempDirectory] : [],
6994
7849
  limits: {
6995
7850
  cpu: 60,
6996
7851
  nofile: 512,