chainlesschain 0.163.1 → 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 (191) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-Cn7quRgL.js → AIOps-DIY6KxV-.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-YqMX3ueH.js → ActionButton-BXH0muPt.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-Bxmy1b7Y.js → Analytics-D39w8vmg.js} +2 -2
  5. package/src/assets/web-panel/assets/{AppLayout-DBOw69jE.js → AppLayout-CUU1zvLs.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-mq1nyzN1.js → Artifacts-D8azHO3T.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-mMiKtZ0D.js → Audit-D0tAwXTg.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-WBUjpYVh.js → BackgroundAgents-C29mhJll.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-c25OoJgs.js → Backup-D6Aa1Gro.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BbfWhZ4-.js → BaseInput-B1uoHbPQ.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-CVVzsTwU.js → Chat-Bs4DLOR5.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CVCjZJ04.js → ChatBubbleRenderer-DUYIBSle.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-C9o2HK-1.js → Checkbox-CNcBPEa4.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-B7X5LfKc.js → Codegen-CIY89763.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-jpPj2Pur.js → Col-CsPYFmgo.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-Dti6bHxN.js → Community-BBocEge4.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-S4ZsLt3W.js → Compact-TQ8Ux0Lf.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-Bge0sQ3O.js → Compliance-DfBsZTft.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-C4XyzVI7.js → Cowork-Cg-I6FVH.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-CPR0v2xy.js → Cron-DTQzfRTp.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DAX8lDaj.js → Crosschain-DGxEKGpR.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-fBHPa0QH.js → DID-DLZ1RDSr.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-vvaUTafU.js → Dashboard-XneICKNI.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DyaQM63v.js → Dropdown-BRU00J1-.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-CAF0wiU2.js → EmailListRenderer-BzCbJuAy.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-ia-WzDRs.js → FamilyGuardDashboard-D7BRyNBG.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-Cz3gHoT5.js → Federation-D9DeFIhW.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-CJQDJr4Q.js → FormItemContext-6rAtA9Ko.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-B05BZlQp.js → GenericCardRenderer-8Hqy80pU.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DT0YcQnI.js → Git-Cmvkj1rt.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-Bqbl6m5Q.js → Governance-BRiQUjjw.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-BdebLR0N.js → Inference-CKN_EzVf.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-M36b8YYV.js → KnowledgeGraph-BCDHwpwQ.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-5LN_XL43.js → Logs-DK9ovhaB.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-BCb1Dm8l.js → MarkdownRenderer-D53pHiHy.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-BkIin-QY.js → Marketplace-BILPGakp.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-Bkppx8Uj.js → McpTools-DTVCOr9-.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-Qk9wg_Ib.js → Memory-Hzc1iow4.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BsTDU3Aj.js → MobileBridge-GXfgnGYV.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-CrZW5I28.js → MobileProjects-B-3TJWsS.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-BMkmgtsA.js → Mtc-DwSJMWtK.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-6m4CC0Yn.js → MtcAudit-D2uxf-DA.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-BZ8yB7QV.js → Multisig-DG6kbKK0.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CxitAMIO.js → NLProgramming-f5p49EDU.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-Bv0msnHu.js → Notes-FUgHyzSD.js} +4 -4
  46. package/src/assets/web-panel/assets/{NotificationSettings-DeVMqM3P.js → NotificationSettings-DCHGee7e.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-CXBSWa3R.js → OrderTableRenderer-Dm6LlIoa.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-b_G5Vs1Q.js → Organization-BawA7qAg.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-DW7riOmA.js → Overflow-D6a5wZU1.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-CtpQNYjL.js → P2P-K8WavwnA.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-Ba63yT4l.js → PdhVaultBrowser-4JAJYaqx.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-DnzyeB67.js → Permissions-BDWvi8PD.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-DbPZ7HWw.js → PersonalDataHub-DF_K4E9i.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-gs_HOThJ.js → Pipeline-BYeZlesI.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-CZlQ1EjE.js → Privacy-BIHlgnIc.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-DQp__Ccb.js → ProjectInit-Cvf-6B5P.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-C7h2sOWZ.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-CxC7JuYv.js +1 -0
  60. package/src/assets/web-panel/assets/{QrScannerModal-B_8M1-FC.js → QrScannerModal-VLf3gAfL.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-Bpycr8WD.js → QuickAsk-B5vGjRiF.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-Z4TrcNzs.js → Recommend--UW4iYs-.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-BD6TsJlS.js → RemoteSession-DMSPcfKR.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-IKxDXHEu.js → Reputation-kQXlEHmj.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-DbmR20tJ.js → Row-CxGu3pit.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-CLjA5PPf.js → RssFeed-Bi5lo8MZ.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-Bm3owbIU.js → Search-Cq_H5RTh.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-CY4RyOuq.js → Security-YFbmkaQS.js} +2 -2
  69. package/src/assets/web-panel/assets/{Services-CYO9qrd_.js → Services-DLOGdaLm.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-ClF51Go6.js → Skeleton-CteIxb3D.js} +1 -1
  71. package/src/assets/web-panel/assets/Skills-CccopaIZ.js +1 -0
  72. package/src/assets/web-panel/assets/{Sla-zqpDUxzP.js → Sla-AzfEQKX1.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-DPSi8Qco.js → SpeechSettings-ekGo5l77.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-CqABopsQ.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-Dzl6f0M2.js → Templates-CX1Tgeka.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-XeZkw40a.js → Tenant-Zc59jF4i.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-CYw2YzPm.js → Terminal-CXOD7y83.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-Bc2pko4i.js → TimelineRenderer-B1aBHPBo.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CfCenOHx.js → Tokens-zALlOq_U.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DTz-_YqV.js → Trigger-CQfJMXaq.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-CB3tt3Z6.js → Trust-BU0RcM-w.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-y1ykmMvU.js → UkeySign-IFZ3HsbH.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-UlZ_lX0e.js → VideoEditing-BzslgW0v.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-DMBaHTha.js → Wallet-BZAcQNS6.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Cksm-hBI.js → WebAuthn-CBK1DMi0.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-BhOa40sX.js → WorkflowEditor-BlMTPzO-.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-DNbeSTDO.js → chat-B51-8QVn.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-9zz8gBmW.js → colors-504RTByU.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-D7aoVv-5.js → compact-item-C-GP6IQM.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-DYg7B8nd.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-BNbhkcoI.js → hasIn-DN7V-_FW.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DeD1wWA8.js → index--n-ZciV3.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Da0SysnX.js → index-7jIqHbzw.js} +1 -1
  96. package/src/assets/web-panel/assets/{index--TvoQ6UC.js → index-B2-x9bS8.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-m2pXS3W2.js → index-BFIFJ5Dr.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CzDDeZBo.js → index-B_vGU-Js.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-gNKj00Fx.js → index-Bkxhaeta.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C8LC0nvw.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-B8irlIUK.js → index-C0k_Nqg9.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-B9zUawcE.js → index-C3TSohzc.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BPojutcZ.js → index-C4DV_5uA.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-ZPDbEh-L.js → index-C8UltbXK.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-jS6NASar.js → index-C9_48OzU.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-67GdrUx2.js → index-CKTfZyfU.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CYu-F-gp.js → index-CP2A226L.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Cq3l0L7C.js → index-CUhQpiYv.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DtQzs0hH.js → index-CX78k-qf.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Bdxe0nrD.js → index-CYopNjcJ.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-uc8Rz96X.js → index-Cao5DZy7.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DnTM8FWK.js → index-CfiEM64n.js} +3 -3
  114. package/src/assets/web-panel/assets/{index-DHskwmwI.js → index-ConjaJro.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CA72qB1n.js → index-CqcLo5WV.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-Dn-JFqEi.js → index-CySTJKeq.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DUWmZ059.js → index-D1phyca8.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-_-Vt1Wqk.js → index-D9LyiN-L.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DIljkSt9.js → index-DA2f1UV3.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B9TzVW7i.js → index-DAOpQvl_.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CRKukX1W.js → index-DE-hmI2l.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DIMVe3HJ.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-FlBuITVG.js → index-DaA__-q1.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Cqy-hqD9.js → index-FcV8J_Qz.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CpEIwp4A.js → index-P2mfY27Z.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-nnmo3ipi.js → index-et5JIPHO.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-SmEIFaRm.js → index-f4Bo4KXm.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DMrqEsOS.js → index-kAqQXfP6.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CV06YdPO.js → index-kt_5rfcZ.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CN6-OEgS.js → index-so8eIIxF.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BRd1zkLB.js → index-yYTQZypF.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-D0mTMUGz.js → initDefaultProps-CPYEIdpV.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-DK16p4-o.js → motion-Cjim9pxs.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-DalC20HC.js → move-DuU-cgz3.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-B5RusszF.js → mtc-parser-DJHNAkta.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-C9Mhitht.js → omit-DSQBgdBj.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-51xQLgwM.js → pickAttrs-CTmT_j5m.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-CJzIqp8u.js → placementArrow-Lfm6v-r4.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-HQvC19u4.js → responsiveObserve-CgrcPcDi.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-C6hhabOp.js → slide-CuE3110i.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-Q1r48-oc.js → statusUtils-DiK5nbpf.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-UakVWrHs.js → styleChecker-DPEJdQaZ.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-Dmv94cxx.js → useFlexGapSupport-CnnodGmp.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DrxOnt_V.js → useFs-DWF84Yav.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-BVf2M-Hg.js → usePersonalDataHub-a5b5Dw2f.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-BzzW9qKH.js → vnode-DAluzXOq.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-C1ZvMgAZ.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 +245 -30
  156. package/src/data/changelog.json +28 -2
  157. package/src/harness/mcp-client.js +256 -43
  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 +52 -6
  166. package/src/lib/cowork-task-runner.js +1 -0
  167. package/src/lib/cowork-task-templates.js +1 -0
  168. package/src/lib/mcp-headers-helper.js +76 -8
  169. package/src/lib/mcp-sandbox-policy.js +155 -0
  170. package/src/lib/mcp-stdio-executable-identity.js +75 -11
  171. package/src/lib/mcp-stdio-execution-authority.js +168 -0
  172. package/src/lib/mcp-stdio-workspace-authority.js +252 -0
  173. package/src/lib/plugin-runtime/sandbox-policy.js +42 -2
  174. package/src/lib/process-execution-broker/index.js +495 -126
  175. package/src/lib/process-execution-broker/platform-sandbox.js +1424 -189
  176. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  177. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  178. package/src/lib/process-execution-broker/windows-sandbox.cs +5 -4
  179. package/src/lib/process-tree-termination.js +17 -1
  180. package/src/lib/skill-mcp.js +74 -3
  181. package/src/lib/web-fetch.js +4 -4
  182. package/src/runtime/agent-runtime.js +61 -8
  183. package/src/runtime/mcp-config.js +238 -28
  184. package/src/workers/background-agent-worker.js +492 -71
  185. package/src/assets/web-panel/assets/Projects-DdCpNRNt.js +0 -1
  186. package/src/assets/web-panel/assets/Providers-CyvtUkUf.js +0 -1
  187. package/src/assets/web-panel/assets/Skills-CCCEmSf5.js +0 -1
  188. package/src/assets/web-panel/assets/Tasks-4dbAJng4.js +0 -1
  189. package/src/assets/web-panel/assets/devWarning-uadaGF_y.js +0 -1
  190. package/src/assets/web-panel/assets/index-C7zhie9M.js +0 -1
  191. package/src/assets/web-panel/assets/index-Cmbg0Wbr.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.
@@ -169,11 +176,18 @@ const LINUX_EXECUTABLE_SNAPSHOT_SOURCE_MODE = 0o500;
169
176
  const LINUX_MCP_CAPSULE_BACKEND = "linux-fd-code-snapshot";
170
177
  const LINUX_MCP_RUNTIME_SNAPSHOT_SCOPE = "mcp-node-runtime-executable";
171
178
  const LINUX_MCP_CAPSULE_SNAPSHOT_MECHANISM =
172
- "verified-o_tmpfile-copy-inherited-fd-exec-v1";
179
+ "verified-o_tmpfile-copy-inherited-fd-module-compile-v1";
180
+ const MACOS_MCP_CAPSULE_BACKEND = "macos-fd-code-snapshot";
181
+ export const MCP_STDIO_FD_ENTRY_BOOTSTRAP =
182
+ 'const fs=require("node:fs");const Module=require("node:module");const filename="/chainlesschain/mcp-capsule.cjs";const source=fs.readFileSync(4,"utf8");process.argv.splice(1,0,filename);const target=new Module(filename,module);target.filename=filename;target.paths=[];target._compile(source,filename);';
183
+ export const MCP_STDIO_FD_ENTRY_BOOTSTRAP_SHA256 = crypto
184
+ .createHash("sha256")
185
+ .update(MCP_STDIO_FD_ENTRY_BOOTSTRAP)
186
+ .digest("hex");
173
187
  const LINUX_NODE_ENTRY_SNAPSHOT_SCOPE = "plugin-entry-source";
174
188
  const LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE = "plugin-entry-executable";
175
- const LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE = "all-pinned-plugin-regular-files";
176
- 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";
177
191
  const LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE = "0400";
178
192
  const LINUX_NATIVE_ENTRY_SNAPSHOT_TARGET_MODE = "0500";
179
193
  const LINUX_ELF64_HEADER_BYTES = 64;
@@ -283,6 +297,14 @@ const windowsAdapterCacheEntries = new Set();
283
297
  const windowsTemporaryCleanupBacklog = new Set();
284
298
  let windowsTemporaryCleanupRetryTimer = null;
285
299
  const windowsAppContainerCleanupBacklog = new Set();
300
+ const issuedWindowsMcpCodeSnapshotPlans = new WeakMap();
301
+ // Only the unified production adapter can register a Windows MCP launch
302
+ // capability. Direct callers of the exported platform-specific helper (and
303
+ // callers that inject a test/runtime facade into applySandbox) may exercise
304
+ // plan construction, but cannot mint a Broker-admissible capability.
305
+ const WINDOWS_MCP_CODE_SNAPSHOT_ISSUER = Symbol(
306
+ "windows-mcp-code-snapshot-issuer",
307
+ );
286
308
  // Each AppContainer retry starts a synchronous, digest-attested PowerShell
287
309
  // helper. Bound automatic retries so a permanently unsupported Windows host
288
310
  // cannot repeatedly block a long-lived CLI; explicit cleanup/reset and process
@@ -296,6 +318,325 @@ function resolveRuntime(overrides = {}) {
296
318
  return { ...DEFAULT_RUNTIME, ...overrides };
297
319
  }
298
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
+
299
640
  function normalizeSandboxRequest(profileOrRequest, explicitRequest) {
300
641
  const legacyRequest =
301
642
  profileOrRequest &&
@@ -453,6 +794,8 @@ export function generateMacSeatbeltProfile(opts = {}, runtimeOverrides = {}) {
453
794
  allowNetwork = false,
454
795
  allowExec = true,
455
796
  } = opts;
797
+ const seatbeltLiteral = (value) =>
798
+ String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
456
799
 
457
800
  const lines = [
458
801
  "(version 1)",
@@ -485,12 +828,12 @@ export function generateMacSeatbeltProfile(opts = {}, runtimeOverrides = {}) {
485
828
 
486
829
  // Allow read/write to specific paths
487
830
  for (const p of allowRead) {
488
- const abs = runtime.resolvePath(p);
831
+ const abs = seatbeltLiteral(runtime.resolvePath(p));
489
832
  lines.push(`(allow file-read* (subpath "${abs}"))`);
490
833
  }
491
834
 
492
835
  for (const p of allowWrite) {
493
- const abs = runtime.resolvePath(p);
836
+ const abs = seatbeltLiteral(runtime.resolvePath(p));
494
837
  lines.push(`(allow file-read* file-write* (subpath "${abs}"))`);
495
838
  }
496
839
 
@@ -511,6 +854,144 @@ export function generateMacSeatbeltProfile(opts = {}, runtimeOverrides = {}) {
511
854
  return lines.join("\n");
512
855
  }
513
856
 
857
+ function validateMacMcpCapsuleContract(
858
+ command,
859
+ args,
860
+ spawnOpts,
861
+ contract,
862
+ runtime,
863
+ sync,
864
+ ) {
865
+ if (
866
+ contract?.kind !== MCP_STDIO_CAPSULE_SANDBOX_CONTRACT_KIND ||
867
+ contract.contractVersion !== 1 ||
868
+ (sync !== true && sync !== false) ||
869
+ spawnOpts?.shell !== false ||
870
+ !linuxStdioIsNarrow(spawnOpts?.stdio) ||
871
+ spawnOpts?.serialization !== undefined ||
872
+ spawnOpts?.argv0 !== undefined ||
873
+ spawnOpts?.uid !== undefined ||
874
+ spawnOpts?.gid !== undefined ||
875
+ !Array.isArray(args) ||
876
+ args.length < 1 ||
877
+ args.some((value) => typeof value !== "string" || value.includes("\0")) ||
878
+ command !== contract.runtimePath ||
879
+ command !== contract.runtimeIdentity?.realPath ||
880
+ args[0] !== contract.entryIdentity?.realPath ||
881
+ ![".js", ".cjs", ".mjs"].includes(
882
+ path.posix.extname(contract.entryIdentity?.realPath || "").toLowerCase(),
883
+ )
884
+ ) {
885
+ return { ok: false, reason: "launch_identity_mismatch" };
886
+ }
887
+ for (const value of [
888
+ contract.pluginRoot,
889
+ contract.workingDirectory,
890
+ contract.rootIdentity?.realPath,
891
+ contract.entryIdentity?.realPath,
892
+ contract.runtimeIdentity?.realPath,
893
+ ]) {
894
+ if (
895
+ typeof value !== "string" ||
896
+ !path.posix.isAbsolute(value) ||
897
+ value.includes("\0")
898
+ ) {
899
+ return { ok: false, reason: "noncanonical_contract_path" };
900
+ }
901
+ }
902
+ try {
903
+ if (
904
+ linuxRealpath(runtime, contract.pluginRoot) !== contract.pluginRoot ||
905
+ linuxRealpath(runtime, contract.workingDirectory) !==
906
+ contract.workingDirectory ||
907
+ linuxRealpath(runtime, spawnOpts.cwd) !== contract.pluginRoot ||
908
+ contract.workingDirectory !== contract.pluginRoot ||
909
+ contract.rootIdentity?.realPath !== contract.pluginRoot ||
910
+ !linuxPathWithin(contract.pluginRoot, contract.entryIdentity.realPath)
911
+ ) {
912
+ return { ok: false, reason: "noncanonical_contract_path" };
913
+ }
914
+ } catch {
915
+ return { ok: false, reason: "contract_path_unavailable" };
916
+ }
917
+ if (
918
+ !linuxDirectoryIdentityMatches(runtime, contract.rootIdentity) ||
919
+ !linuxIdentityMatches(runtime, contract.runtimeIdentity, {
920
+ executable: true,
921
+ }) ||
922
+ !linuxIdentityMatches(runtime, contract.entryIdentity)
923
+ ) {
924
+ return { ok: false, reason: "execution_identity_changed" };
925
+ }
926
+ return { ok: true };
927
+ }
928
+
929
+ function applyMacMcpCapsuleCodeSnapshot(
930
+ command,
931
+ args,
932
+ spawnOpts,
933
+ sandboxOpts,
934
+ runtime,
935
+ base,
936
+ ) {
937
+ const contract = sandboxOpts.executionContract;
938
+ const validation = validateMacMcpCapsuleContract(
939
+ command,
940
+ args,
941
+ spawnOpts,
942
+ contract,
943
+ runtime,
944
+ sandboxOpts.sync,
945
+ );
946
+ const unavailable = (reason, runtimeProbe = null) =>
947
+ createSandboxPlan({
948
+ ...base,
949
+ backend: null,
950
+ candidateBackend: MACOS_MCP_CAPSULE_BACKEND,
951
+ policyAttested: false,
952
+ runtimeProbe: runtimeProbe
953
+ ? {
954
+ kind: "darwin-mcp-capsule-code-snapshot-v1",
955
+ attempted: runtimeProbe.attempted !== false,
956
+ probeRuntime: "node",
957
+ targetRuntime: "node",
958
+ ...runtimeProbe,
959
+ }
960
+ : null,
961
+ reason,
962
+ guarantees: [],
963
+ });
964
+ if (!validation.ok) {
965
+ return unavailable("macos_mcp_capsule_execution_contract_invalid", {
966
+ attempted: false,
967
+ runnable: false,
968
+ reason: validation.reason,
969
+ contentSnapshot: false,
970
+ handleAtomic: false,
971
+ entrySnapshotAtomic: false,
972
+ runtimeLaunchAtomic: false,
973
+ sharedLibraryClosure: false,
974
+ });
975
+ }
976
+
977
+ // Darwin exposes only pathname-based exec/spawn APIs to an unprivileged
978
+ // process. An inherited descriptor can bind the entry source, but public
979
+ // APIs cannot bind the verified Node runtime descriptor to the image that
980
+ // executes. Private temporary paths and Seatbelt still leave a same-UID
981
+ // pathname-replacement window, so they cannot satisfy CODE_SNAPSHOT.
982
+ return unavailable("macos_atomic_runtime_exec_unavailable", {
983
+ attempted: true,
984
+ runnable: false,
985
+ reason: "public_api_has_no_descriptor_bound_exec",
986
+ contentSnapshot: false,
987
+ handleAtomic: false,
988
+ entrySnapshotAtomic: false,
989
+ runtimeLaunchAtomic: false,
990
+ runtimeLaunchMechanism: "darwin-public-api-pathname-exec-only-v1",
991
+ sharedLibraryClosure: false,
992
+ });
993
+ }
994
+
514
995
  /**
515
996
  * Apply macOS sandbox via SBWritableProfile or sandbox-exec wrapper.
516
997
  *
@@ -552,6 +1033,20 @@ export function applyMacSandbox(
552
1033
  });
553
1034
  }
554
1035
 
1036
+ if (
1037
+ Array.isArray(sandboxOpts.requiredBoundaries) &&
1038
+ sandboxOpts.requiredBoundaries.includes(SANDBOX_BOUNDARIES.CODE_SNAPSHOT)
1039
+ ) {
1040
+ return applyMacMcpCapsuleCodeSnapshot(
1041
+ command,
1042
+ args,
1043
+ spawnOpts,
1044
+ sandboxOpts,
1045
+ runtime,
1046
+ base,
1047
+ );
1048
+ }
1049
+
555
1050
  // A deny-by-default Seatbelt profile cannot safely infer the filesystem,
556
1051
  // IPC, and inherited-descriptor access required by an arbitrary command.
557
1052
  // Keep the boundary truthful for the broker's implicit default profile:
@@ -619,14 +1114,42 @@ function cleanupWindowsTemporaryPath(
619
1114
  targetPath,
620
1115
  attempts = 100,
621
1116
  delayMs = 10,
1117
+ expectedIdentity = null,
622
1118
  ) {
623
1119
  if (!targetPath) return true;
624
1120
  for (let attempt = 0; attempt < attempts; attempt += 1) {
625
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
+ }
626
1138
  runtime.fs.unlinkSync(targetPath);
1139
+ assertWindowsAdapterTempRoot(runtime);
627
1140
  return true;
628
1141
  } catch (error) {
629
- 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
+ }
630
1153
  }
631
1154
  if (attempt + 1 < attempts) runtime.sleepSync(delayMs);
632
1155
  }
@@ -637,13 +1160,32 @@ function cleanupOrTrackWindowsTemporaryPath(
637
1160
  runtime,
638
1161
  targetPath,
639
1162
  attempts = 100,
1163
+ entry = null,
1164
+ expectedIdentity = null,
640
1165
  ) {
641
- 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();
642
1176
  for (const residual of [...windowsTemporaryCleanupBacklog]) {
643
- if (residual.runtime === runtime && residual.targetPath === targetPath) {
1177
+ if (
1178
+ residual.kind === "file" &&
1179
+ residual.runtime === runtime &&
1180
+ residual.targetPath === targetPath
1181
+ ) {
644
1182
  windowsTemporaryCleanupBacklog.delete(residual);
1183
+ if (residual.entry) residualEntries.add(residual.entry);
645
1184
  }
646
1185
  }
1186
+ for (const residualEntry of residualEntries) {
1187
+ finalizeWindowsTemporaryCleanupEntry(residualEntry);
1188
+ }
647
1189
  if (
648
1190
  windowsTemporaryCleanupBacklog.size === 0 &&
649
1191
  windowsTemporaryCleanupRetryTimer
@@ -653,13 +1195,25 @@ function cleanupOrTrackWindowsTemporaryPath(
653
1195
  }
654
1196
  return true;
655
1197
  }
656
- if (
657
- ![...windowsTemporaryCleanupBacklog].some(
658
- (residual) =>
659
- residual.runtime === runtime && residual.targetPath === targetPath,
660
- )
661
- ) {
662
- 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
+ });
663
1217
  }
664
1218
  registerWindowsAdapterExitCleanup();
665
1219
  scheduleWindowsTemporaryCleanupRetry();
@@ -669,14 +1223,25 @@ function cleanupOrTrackWindowsTemporaryPath(
669
1223
  function retryWindowsTemporaryCleanupBacklog(attempts = 100) {
670
1224
  let cleaned = true;
671
1225
  for (const residual of [...windowsTemporaryCleanupBacklog]) {
672
- if (
673
- cleanupWindowsTemporaryPath(
674
- residual.runtime,
675
- residual.targetPath,
676
- attempts,
677
- )
678
- ) {
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) {
679
1243
  windowsTemporaryCleanupBacklog.delete(residual);
1244
+ if (residual.entry) finalizeWindowsTemporaryCleanupEntry(residual.entry);
680
1245
  } else {
681
1246
  cleaned = false;
682
1247
  }
@@ -691,6 +1256,18 @@ function retryWindowsTemporaryCleanupBacklog(attempts = 100) {
691
1256
  return cleaned;
692
1257
  }
693
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
+
694
1271
  function scheduleWindowsTemporaryCleanupRetry() {
695
1272
  if (
696
1273
  windowsTemporaryCleanupRetryTimer ||
@@ -713,17 +1290,109 @@ function cleanupWindowsTemporaryDirectory(
713
1290
  targetPath,
714
1291
  attempts = 100,
715
1292
  delayMs = 10,
1293
+ expectedIdentity = null,
716
1294
  ) {
717
1295
  if (!targetPath) return true;
718
1296
  for (let attempt = 0; attempt < attempts; attempt += 1) {
719
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
+ }
720
1314
  runtime.fs.rmdirSync(targetPath);
1315
+ assertWindowsAdapterTempRoot(runtime);
721
1316
  return true;
722
1317
  } catch (error) {
723
- 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
+ }
1329
+ }
1330
+ if (attempt + 1 < attempts) runtime.sleepSync(delayMs);
1331
+ }
1332
+ return false;
1333
+ }
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;
724
1371
  }
725
- if (attempt + 1 < attempts) runtime.sleepSync(delayMs);
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
+ });
726
1393
  }
1394
+ registerWindowsAdapterExitCleanup();
1395
+ scheduleWindowsTemporaryCleanupRetry();
727
1396
  return false;
728
1397
  }
729
1398
 
@@ -731,6 +1400,218 @@ function sha256(value) {
731
1400
  return crypto.createHash("sha256").update(value).digest("hex");
732
1401
  }
733
1402
 
1403
+ function sameStringArray(left, right) {
1404
+ return (
1405
+ Array.isArray(left) &&
1406
+ Array.isArray(right) &&
1407
+ left.length === right.length &&
1408
+ left.every((value, index) => value === right[index])
1409
+ );
1410
+ }
1411
+
1412
+ function captureWindowsStdio(stdio) {
1413
+ if (Array.isArray(stdio)) return Object.freeze([...stdio]);
1414
+ return stdio ?? null;
1415
+ }
1416
+
1417
+ function sameWindowsStdio(left, right) {
1418
+ const normalizedRight = right ?? null;
1419
+ if (Array.isArray(left) || Array.isArray(normalizedRight)) {
1420
+ return (
1421
+ Array.isArray(left) &&
1422
+ Array.isArray(normalizedRight) &&
1423
+ left.length === normalizedRight.length &&
1424
+ left.every((entry, index) => Object.is(entry, normalizedRight[index]))
1425
+ );
1426
+ }
1427
+ return Object.is(left, normalizedRight);
1428
+ }
1429
+
1430
+ function windowsStdioDigestContract(stdio) {
1431
+ const normalizeEntry = (entry) => {
1432
+ if (entry === null || entry === undefined) return ["null"];
1433
+ if (typeof entry === "string") return ["string", entry];
1434
+ if (Number.isInteger(entry)) return ["fd", entry];
1435
+ return [typeof entry];
1436
+ };
1437
+ return Array.isArray(stdio)
1438
+ ? ["array", ...stdio.map(normalizeEntry)]
1439
+ : ["scalar", ...normalizeEntry(stdio)];
1440
+ }
1441
+
1442
+ function windowsEnvironmentDigest(env) {
1443
+ const entries = Object.entries(env || {})
1444
+ .map(([key, value]) => [key, String(value)])
1445
+ .sort(([leftKey, leftValue], [rightKey, rightValue]) => {
1446
+ if (leftKey !== rightKey) return leftKey < rightKey ? -1 : 1;
1447
+ if (leftValue === rightValue) return 0;
1448
+ return leftValue < rightValue ? -1 : 1;
1449
+ });
1450
+ return sha256(JSON.stringify(entries));
1451
+ }
1452
+
1453
+ function windowsMcpBindingIdentity(identity) {
1454
+ if (!identity) return null;
1455
+ return {
1456
+ contractVersion: identity.contractVersion ?? null,
1457
+ realPath: identity.realPath ?? null,
1458
+ sha256: identity.sha256 ?? null,
1459
+ bytes: identity.bytes ?? null,
1460
+ fileId: identity.fileId
1461
+ ? {
1462
+ dev: identity.fileId.dev ?? null,
1463
+ ino: identity.fileId.ino ?? null,
1464
+ }
1465
+ : null,
1466
+ mtimeMs: identity.mtimeMs ?? null,
1467
+ attestation: identity.attestation ?? null,
1468
+ };
1469
+ }
1470
+
1471
+ function windowsMcpBindingContract(contract) {
1472
+ return {
1473
+ contractVersion: contract?.contractVersion ?? null,
1474
+ kind: contract?.kind ?? null,
1475
+ pluginRoot: contract?.pluginRoot ?? null,
1476
+ workingDirectory: contract?.workingDirectory ?? null,
1477
+ runtimePath: contract?.runtimePath ?? null,
1478
+ rootIdentity: contract?.rootIdentity
1479
+ ? {
1480
+ realPath: contract.rootIdentity.realPath ?? null,
1481
+ fileId: contract.rootIdentity.fileId
1482
+ ? {
1483
+ dev: contract.rootIdentity.fileId.dev ?? null,
1484
+ ino: contract.rootIdentity.fileId.ino ?? null,
1485
+ }
1486
+ : null,
1487
+ attestation: contract.rootIdentity.attestation ?? null,
1488
+ }
1489
+ : null,
1490
+ entryIdentity: windowsMcpBindingIdentity(contract?.entryIdentity),
1491
+ runtimeIdentity: windowsMcpBindingIdentity(contract?.runtimeIdentity),
1492
+ };
1493
+ }
1494
+
1495
+ function createWindowsMcpCodeSnapshotPlanBinding({
1496
+ executionContract,
1497
+ command,
1498
+ args,
1499
+ spawnOpts,
1500
+ profile,
1501
+ requiredBoundaries,
1502
+ sync,
1503
+ backend,
1504
+ policyAttested,
1505
+ policyDigest,
1506
+ adapterSource,
1507
+ helperInvocation,
1508
+ helperOptions,
1509
+ postSpawn,
1510
+ postSpawnWindows,
1511
+ }) {
1512
+ const helperEnvDigest = windowsEnvironmentDigest(helperOptions.env);
1513
+ const helperStdio = captureWindowsStdio(helperOptions.stdio);
1514
+ const originalArgs = Object.freeze([...(args || [])]);
1515
+ const helperArgs = Object.freeze([...(helperInvocation.args || [])]);
1516
+ const normalizedPostSpawn = Object.freeze({
1517
+ required: postSpawn?.required === true,
1518
+ mode: postSpawn?.mode || "none",
1519
+ });
1520
+ const digestPayload = {
1521
+ version: 1,
1522
+ kind: "windows-mcp-code-snapshot-plan-binding-v1",
1523
+ executionContract: windowsMcpBindingContract(executionContract),
1524
+ originalLaunch: {
1525
+ command,
1526
+ args: originalArgs,
1527
+ cwd: spawnOpts?.cwd ?? null,
1528
+ shell: spawnOpts?.shell ?? null,
1529
+ detached: spawnOpts?.detached ?? null,
1530
+ profile,
1531
+ requiredBoundaries: [...requiredBoundaries],
1532
+ sync,
1533
+ },
1534
+ policy: { backend, policyAttested, policyDigest },
1535
+ adapter: {
1536
+ loaderMode: adapterSource.loaderMode,
1537
+ sourceDigest: adapterSource.sourceDigest,
1538
+ sourceContractDigest: adapterSource.sourceContractDigest,
1539
+ },
1540
+ helperInvocation: {
1541
+ command: helperInvocation.command,
1542
+ args: helperArgs,
1543
+ cwd: helperOptions.cwd ?? null,
1544
+ shell: helperOptions.shell ?? null,
1545
+ detached: helperOptions.detached ?? null,
1546
+ envDigest: helperEnvDigest,
1547
+ stdio: windowsStdioDigestContract(helperStdio),
1548
+ },
1549
+ postSpawn: normalizedPostSpawn,
1550
+ };
1551
+ return Object.freeze({
1552
+ executionContract,
1553
+ originalCommand: command,
1554
+ originalArgs,
1555
+ originalCwd: spawnOpts?.cwd ?? null,
1556
+ originalShell: spawnOpts?.shell ?? null,
1557
+ originalDetached: spawnOpts?.detached ?? null,
1558
+ profile,
1559
+ requiredBoundaries: Object.freeze([...requiredBoundaries]),
1560
+ sync,
1561
+ backend,
1562
+ policyAttested,
1563
+ policyDigest,
1564
+ helperCommand: helperInvocation.command,
1565
+ helperArgs,
1566
+ helperCwd: helperOptions.cwd ?? null,
1567
+ helperShell: helperOptions.shell ?? null,
1568
+ helperDetached: helperOptions.detached ?? null,
1569
+ helperEnvDigest,
1570
+ helperStdio,
1571
+ postSpawn: normalizedPostSpawn,
1572
+ postSpawnWindows: postSpawnWindows || null,
1573
+ planBindingDigest: sha256(JSON.stringify(digestPayload)),
1574
+ });
1575
+ }
1576
+
1577
+ /**
1578
+ * Consume the one-launch capability attached to a real built-in Windows MCP
1579
+ * code-snapshot plan. The issuer stays module-private so an injected adapter
1580
+ * cannot make a structurally similar helper payload authoritative.
1581
+ */
1582
+ export function consumeWindowsMcpCodeSnapshotPlanBinding(plan, expected = {}) {
1583
+ const issued = issuedWindowsMcpCodeSnapshotPlans.get(plan);
1584
+ if (!issued) return false;
1585
+ issuedWindowsMcpCodeSnapshotPlans.delete(plan);
1586
+ return (
1587
+ issued.executionContract === expected.executionContract &&
1588
+ issued.originalCommand === expected.command &&
1589
+ sameStringArray(issued.originalArgs, expected.args) &&
1590
+ issued.originalCwd === (expected.cwd ?? null) &&
1591
+ issued.originalShell === (expected.shell ?? null) &&
1592
+ issued.originalDetached === (expected.detached ?? null) &&
1593
+ issued.profile === expected.profile &&
1594
+ sameStringArray(issued.requiredBoundaries, expected.requiredBoundaries) &&
1595
+ issued.sync === (expected.sync === true) &&
1596
+ plan.backend === issued.backend &&
1597
+ plan.policyAttested === issued.policyAttested &&
1598
+ plan.policyDigest === issued.policyDigest &&
1599
+ plan.command === issued.helperCommand &&
1600
+ sameStringArray(plan.args, issued.helperArgs) &&
1601
+ plan.options?.cwd === issued.helperCwd &&
1602
+ plan.options?.shell === issued.helperShell &&
1603
+ plan.options?.detached === issued.helperDetached &&
1604
+ windowsEnvironmentDigest(plan.options?.env) === issued.helperEnvDigest &&
1605
+ sameWindowsStdio(issued.helperStdio, plan.options?.stdio) &&
1606
+ plan.postSpawn?.required === issued.postSpawn.required &&
1607
+ plan.postSpawn?.mode === issued.postSpawn.mode &&
1608
+ (plan.postSpawnWindows || null) === issued.postSpawnWindows &&
1609
+ plan.runtimeProbe?.planBindingMechanism ===
1610
+ "windows-mcp-code-snapshot-plan-binding-v1" &&
1611
+ plan.runtimeProbe?.planBindingDigest === issued.planBindingDigest
1612
+ );
1613
+ }
1614
+
734
1615
  function windowsFileIdentity(stat) {
735
1616
  const timestamp = (nanosecondsKey, millisecondsKey) => {
736
1617
  if (stat[nanosecondsKey] !== undefined) {
@@ -762,15 +1643,11 @@ function sameWindowsFileIdentity(left, right) {
762
1643
  }
763
1644
 
764
1645
  function inspectWindowsAdapterAssembly(runtime, assemblyPath) {
765
- if (typeof runtime.fs.lstatSync === "function") {
766
- const linkStat = runtime.fs.lstatSync(assemblyPath, { bigint: true });
767
- if (
768
- linkStat.isSymbolicLink?.() ||
769
- (typeof linkStat.isFile === "function" && !linkStat.isFile())
770
- ) {
771
- throw new Error("Windows in-memory adapter is not a regular file");
772
- }
773
- }
1646
+ const boundBefore = inspectWindowsAdapterBoundPath(
1647
+ runtime,
1648
+ assemblyPath,
1649
+ "file",
1650
+ );
774
1651
  const before = runtime.fs.statSync(assemblyPath, { bigint: true });
775
1652
  if (typeof before.isFile === "function" && !before.isFile()) {
776
1653
  throw new Error("Windows in-memory adapter is not a regular file");
@@ -783,6 +1660,15 @@ function inspectWindowsAdapterAssembly(runtime, assemblyPath) {
783
1660
  if (!sameWindowsFileIdentity(beforeIdentity, afterIdentity)) {
784
1661
  throw new Error("Windows in-memory adapter changed during attestation");
785
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
+ }
786
1672
  return { assemblyDigest, fileIdentity: afterIdentity };
787
1673
  }
788
1674
 
@@ -792,6 +1678,11 @@ function verifyWindowsAdapterEntry(entry, runtime, source) {
792
1678
  entry.cleaned ||
793
1679
  entry.runtimeFs !== runtime.fs ||
794
1680
  entry.tempDirectory !== source.tempDirectory ||
1681
+ entry.idleTtlMs !== source.idleTtlMs ||
1682
+ !sameWindowsAdapterTempRootBinding(
1683
+ entry.tempRootBinding,
1684
+ source.tempRootBinding,
1685
+ ) ||
795
1686
  entry.sourceDigest !== source.sourceDigest ||
796
1687
  entry.sourceContractDigest !== source.sourceContractDigest ||
797
1688
  entry.loaderMode !== source.loaderMode
@@ -799,6 +1690,15 @@ function verifyWindowsAdapterEntry(entry, runtime, source) {
799
1690
  return false;
800
1691
  }
801
1692
  try {
1693
+ assertWindowsAdapterTempRoot(runtime);
1694
+ if (entry.adapterDirectory) {
1695
+ assertWindowsAdapterBoundPathIdentity(
1696
+ runtime,
1697
+ entry.adapterDirectory,
1698
+ "directory",
1699
+ entry.adapterDirectoryIdentity,
1700
+ );
1701
+ }
802
1702
  const attestation = inspectWindowsAdapterAssembly(
803
1703
  runtime,
804
1704
  entry.assemblyPath,
@@ -822,14 +1722,18 @@ function cleanupWindowsAdapterEntry(entry, attempts = 100) {
822
1722
  entry.runtime,
823
1723
  entry.assemblyPath,
824
1724
  attempts,
1725
+ entry,
1726
+ entry.fileIdentity,
825
1727
  );
826
- const directoryDeleted =
827
- deleted &&
828
- cleanupWindowsTemporaryDirectory(
829
- entry.runtime,
830
- entry.adapterDirectory,
831
- attempts,
832
- );
1728
+ const directoryDeleted = entry.adapterDirectory
1729
+ ? cleanupOrTrackWindowsTemporaryDirectory(
1730
+ entry.runtime,
1731
+ entry.adapterDirectory,
1732
+ attempts,
1733
+ entry,
1734
+ entry.adapterDirectoryIdentity,
1735
+ )
1736
+ : true;
833
1737
  if (deleted && directoryDeleted) {
834
1738
  entry.cleaned = true;
835
1739
  windowsAdapterCacheEntries.delete(entry);
@@ -872,15 +1776,35 @@ function registerWindowsAdapterExitCleanup() {
872
1776
  * this hook at a known quiescent point to prove cleanup.
873
1777
  */
874
1778
  export function resetWindowsSandboxAdapterCache() {
875
- let cleaned = retryWindowsAppContainerCleanupBacklog();
1779
+ const appContainerCleaned = retryWindowsAppContainerCleanupBacklog();
876
1780
  windowsAdapterCache = null;
877
1781
  for (const entry of [...windowsAdapterCacheEntries]) {
878
1782
  entry.retired = true;
879
1783
  entry.refCount = 0;
880
- if (!cleanupWindowsAdapterEntry(entry)) cleaned = false;
1784
+ cleanupWindowsAdapterEntry(entry);
881
1785
  }
882
- if (!retryWindowsTemporaryCleanupBacklog()) cleaned = false;
883
- 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;
884
1808
  }
885
1809
 
886
1810
  function scheduleWindowsAdapterIdleCleanup(entry) {
@@ -892,11 +1816,11 @@ function scheduleWindowsAdapterIdleCleanup(entry) {
892
1816
  ) {
893
1817
  return;
894
1818
  }
895
- const configuredTtl = Number(entry.runtime.windowsAdapterIdleTtlMs);
896
- const idleTtlMs =
897
- Number.isFinite(configuredTtl) && configuredTtl >= 0
898
- ? configuredTtl
899
- : WINDOWS_ADAPTER_IDLE_TTL_MS;
1819
+ const idleTtlMs = entry.idleTtlMs;
1820
+ if (idleTtlMs === 0) {
1821
+ retireWindowsAdapterEntry(entry);
1822
+ return;
1823
+ }
900
1824
  entry.idleTimer = setTimeout(() => {
901
1825
  entry.idleTimer = null;
902
1826
  if (
@@ -912,6 +1836,7 @@ function scheduleWindowsAdapterIdleCleanup(entry) {
912
1836
  }
913
1837
 
914
1838
  function loadWindowsAdapterSource(runtime, loaderMode) {
1839
+ const tempRootBinding = assertWindowsAdapterTempRoot(runtime);
915
1840
  const bundled = runtime.windowsAdapterContent === undefined;
916
1841
  const content = bundled
917
1842
  ? loaderMode === "managed-executable"
@@ -926,7 +1851,10 @@ function loadWindowsAdapterSource(runtime, loaderMode) {
926
1851
  : WINDOWS_SANDBOX_HELPER_ASSEMBLY_DIGEST
927
1852
  : sha256(content),
928
1853
  sourceContractDigest: bundled ? WINDOWS_SANDBOX_HELPER_SOURCE_DIGEST : null,
929
- tempDirectory: runtime.tmpdir(),
1854
+ tempDirectory: tempRootBinding.rootPath,
1855
+ tempRootBinding,
1856
+ tempRootAttestation: tempRootBinding.attestation,
1857
+ idleTtlMs: runtime.windowsAdapterIdleTtlMs,
930
1858
  loaderMode,
931
1859
  };
932
1860
  }
@@ -948,54 +1876,121 @@ function materializeWindowsAdapter(runtime, source) {
948
1876
  const assemblyPath = runtime.joinPath(
949
1877
  adapterDirectory || source.tempDirectory,
950
1878
  source.loaderMode === "managed-executable"
951
- ? "windows-sandbox-helper.exe"
1879
+ ? WINDOWS_ADAPTER_HELPER_NAME
952
1880
  : `${adapterBaseName}.dll`,
953
1881
  );
954
- if (
955
- (adapterDirectory && runtime.fs.existsSync(adapterDirectory)) ||
956
- runtime.fs.existsSync(assemblyPath)
957
- ) {
958
- return { reason: "windows_native_adapter_random_path_collision" };
959
- }
1882
+ let adapterDirectoryIdentity = null;
1883
+ let assemblyFileIdentity = null;
960
1884
  try {
1885
+ assertWindowsAdapterTempRoot(runtime);
961
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
+ }
962
1894
  runtime.fs.mkdirSync(adapterDirectory, {
963
1895
  mode: 0o700,
964
1896
  recursive: false,
965
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;
966
1914
  }
967
1915
  runtime.fs.writeFileSync(assemblyPath, source.content, {
968
1916
  mode: adapterDirectory ? 0o500 : 0o600,
969
1917
  flag: "wx",
970
1918
  });
1919
+ assemblyFileIdentity = inspectWindowsAdapterAssembly(
1920
+ runtime,
1921
+ assemblyPath,
1922
+ ).fileIdentity;
971
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
+ }
972
1951
  const assemblyDeleted = cleanupOrTrackWindowsTemporaryPath(
973
1952
  runtime,
974
1953
  assemblyPath,
1954
+ 100,
1955
+ null,
1956
+ assemblyFileIdentity,
975
1957
  );
976
- const directoryDeleted =
977
- assemblyDeleted &&
978
- 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
+ }
979
1968
  if (!assemblyDeleted || !directoryDeleted) {
980
1969
  return { reason: "windows_native_adapter_compile_cleanup_unverified" };
981
1970
  }
982
- if (error?.code === "EEXIST") {
983
- return { reason: "windows_native_adapter_random_path_collision" };
984
- }
985
1971
  return { reason: "windows_native_adapter_materialize_failed" };
986
1972
  }
987
1973
  return {
988
1974
  adapterDirectory,
1975
+ adapterDirectoryIdentity,
989
1976
  assemblyPath,
1977
+ assemblyFileIdentity,
990
1978
  cleanupAssembly: () => {
991
1979
  const assemblyDeleted = cleanupOrTrackWindowsTemporaryPath(
992
1980
  runtime,
993
1981
  assemblyPath,
1982
+ 100,
1983
+ null,
1984
+ assemblyFileIdentity,
994
1985
  );
995
- return (
996
- assemblyDeleted &&
997
- cleanupWindowsTemporaryDirectory(runtime, adapterDirectory)
1986
+ const directoryDeleted = cleanupOrTrackWindowsTemporaryDirectory(
1987
+ runtime,
1988
+ adapterDirectory,
1989
+ 100,
1990
+ null,
1991
+ adapterDirectoryIdentity,
998
1992
  );
1993
+ return assemblyDeleted && directoryDeleted;
999
1994
  },
1000
1995
  };
1001
1996
  }
@@ -1007,9 +2002,7 @@ function materializeWindowsAdapterInvocation(runtime, helperArgs) {
1007
2002
  .randomBytes(24)
1008
2003
  .toString("hex")}.json`,
1009
2004
  );
1010
- if (runtime.fs.existsSync(payloadPath)) {
1011
- return { reason: "windows_adapter_invocation_random_path_collision" };
1012
- }
2005
+ let fileIdentity = null;
1013
2006
  let content;
1014
2007
  try {
1015
2008
  content = Buffer.from(
@@ -1029,16 +2022,57 @@ function materializeWindowsAdapterInvocation(runtime, helperArgs) {
1029
2022
  return { reason: "windows_adapter_invocation_too_large" };
1030
2023
  }
1031
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
+ }
1032
2032
  runtime.fs.writeFileSync(payloadPath, content, {
1033
2033
  mode: 0o600,
1034
2034
  flag: "wx",
1035
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;
1036
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
+ }
1037
2065
  if (error?.code === "EEXIST") {
1038
2066
  return { reason: "windows_adapter_invocation_random_path_collision" };
1039
2067
  }
1040
2068
  return {
1041
- reason: cleanupOrTrackWindowsTemporaryPath(runtime, payloadPath)
2069
+ reason: cleanupOrTrackWindowsTemporaryPath(
2070
+ runtime,
2071
+ payloadPath,
2072
+ 100,
2073
+ null,
2074
+ fileIdentity,
2075
+ )
1042
2076
  ? "windows_adapter_invocation_materialize_failed"
1043
2077
  : "windows_adapter_invocation_cleanup_unverified",
1044
2078
  };
@@ -1046,7 +2080,24 @@ function materializeWindowsAdapterInvocation(runtime, helperArgs) {
1046
2080
  return {
1047
2081
  payloadPath,
1048
2082
  payloadDigest: sha256(content),
1049
- 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
+ ),
1050
2101
  };
1051
2102
  }
1052
2103
 
@@ -1102,6 +2153,7 @@ function buildWindowsAdapterInvocation(entry, invocation) {
1102
2153
  invocation.payloadPath,
1103
2154
  invocation.payloadDigest,
1104
2155
  ],
2156
+ verify: invocation.verify,
1105
2157
  cleanup: invocation.cleanup,
1106
2158
  };
1107
2159
  }
@@ -1116,6 +2168,7 @@ function buildWindowsAdapterInvocation(entry, invocation) {
1116
2168
  "-EncodedCommand",
1117
2169
  windowsPowerShellBootstrap(entry, invocation),
1118
2170
  ],
2171
+ verify: invocation.verify,
1119
2172
  cleanup: invocation.cleanup,
1120
2173
  };
1121
2174
  }
@@ -1202,11 +2255,14 @@ function createFreshWindowsAdapter(runtime, source, hostEnvironment) {
1202
2255
  runtime,
1203
2256
  runtimeFs: runtime.fs,
1204
2257
  tempDirectory: source.tempDirectory,
2258
+ tempRootBinding: source.tempRootBinding,
2259
+ idleTtlMs: source.idleTtlMs,
1205
2260
  sourceDigest: source.sourceDigest,
1206
2261
  sourceContractDigest: source.sourceContractDigest,
1207
2262
  loaderMode: source.loaderMode,
1208
2263
  powershellPath: powershellHosts[0] || null,
1209
2264
  adapterDirectory: materialized.adapterDirectory,
2265
+ adapterDirectoryIdentity: materialized.adapterDirectoryIdentity,
1210
2266
  assemblyPath: materialized.assemblyPath,
1211
2267
  assemblyDigest: attestation.assemblyDigest,
1212
2268
  fileIdentity: attestation.fileIdentity,
@@ -1362,7 +2418,9 @@ function createWindowsAdapterController(runtime, source, hostEnvironment) {
1362
2418
  error.adapterReason = payload.reason;
1363
2419
  throw error;
1364
2420
  }
1365
- return buildWindowsAdapterInvocation(entry, payload);
2421
+ const invocation = buildWindowsAdapterInvocation(entry, payload);
2422
+ invocation.verify();
2423
+ return invocation;
1366
2424
  };
1367
2425
 
1368
2426
  return {
@@ -1373,6 +2431,7 @@ function createWindowsAdapterController(runtime, source, hostEnvironment) {
1373
2431
  let result;
1374
2432
  let failure;
1375
2433
  try {
2434
+ invocation.verify();
1376
2435
  result = runtime.spawnSync(invocation.command, invocation.args, {
1377
2436
  ...options,
1378
2437
  cwd: runtime.joinPath(runtime.windowsDir(), "System32"),
@@ -1400,20 +2459,40 @@ function createWindowsAdapterController(runtime, source, hostEnvironment) {
1400
2459
  };
1401
2460
  }
1402
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
+
1403
2477
  function waitForWindowsTargetIdentity(
1404
2478
  proc,
1405
2479
  identityPath,
1406
2480
  runtime,
1407
2481
  expectedAppContainer = null,
1408
2482
  timeoutMs = 30_000,
2483
+ onIdentityFileAttested = null,
1409
2484
  ) {
1410
2485
  const deadline = runtime.now() + timeoutMs;
1411
2486
  let lastError;
1412
2487
  while (runtime.now() < deadline) {
1413
2488
  try {
1414
- const identity = JSON.parse(
1415
- runtime.fs.readFileSync(identityPath, "utf8"),
2489
+ const identityFile = readWindowsAdapterBoundFile(
2490
+ runtime,
2491
+ identityPath,
2492
+ "utf8",
1416
2493
  );
2494
+ onIdentityFileAttested?.(identityFile.fileIdentity);
2495
+ const identity = JSON.parse(identityFile.content);
1417
2496
  if (identity?.error) {
1418
2497
  throw new Error(
1419
2498
  `Windows sandbox helper rejected the target: ${identity.error}`,
@@ -2077,6 +3156,7 @@ export function applyWindowsSandbox(
2077
3156
  spawnOpts,
2078
3157
  sandboxOpts = {},
2079
3158
  runtimeOverrides = {},
3159
+ planBindingAuthority = null,
2080
3160
  ) {
2081
3161
  let runtime = resolveRuntime(runtimeOverrides);
2082
3162
  const requiredBoundaries = Array.isArray(sandboxOpts.requiredBoundaries)
@@ -2097,6 +3177,10 @@ export function applyWindowsSandbox(
2097
3177
  SANDBOX_BOUNDARIES.RESOURCE_LIMITS,
2098
3178
  SANDBOX_BOUNDARIES.PRIVILEGE_REDUCTION,
2099
3179
  ];
3180
+ const appContainerSupportedBoundaries = [
3181
+ ...appContainerGuarantees,
3182
+ SANDBOX_BOUNDARIES.CODE_SNAPSHOT,
3183
+ ];
2100
3184
  const base = {
2101
3185
  platform: runtime.platform,
2102
3186
  profile: sandboxOpts.profileName || "default",
@@ -2143,7 +3227,7 @@ export function applyWindowsSandbox(
2143
3227
  if (
2144
3228
  requiresAppContainer &&
2145
3229
  requiredBoundaries.some(
2146
- (boundary) => !appContainerGuarantees.includes(boundary),
3230
+ (boundary) => !appContainerSupportedBoundaries.includes(boundary),
2147
3231
  )
2148
3232
  ) {
2149
3233
  return unavailablePlan("windows_appcontainer_boundary_unsupported");
@@ -2224,11 +3308,33 @@ export function applyWindowsSandbox(
2224
3308
  ) {
2225
3309
  return unavailablePlan("windows_git_nested_process_compatibility");
2226
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
+ };
2227
3326
  const loaderMode =
2228
3327
  profile !== "strict" && !requiresAppContainer && !entrySnapshot.locks
2229
3328
  ? "managed-executable"
2230
3329
  : "powershell-byte-assembly";
2231
- 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
+ }
2232
3338
  if (!adapterSource.content) {
2233
3339
  return unavailablePlan(adapterSource.reason);
2234
3340
  }
@@ -2273,6 +3379,22 @@ export function applyWindowsSandbox(
2273
3379
  .toString("hex")}.json`,
2274
3380
  )
2275
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
+ }
2276
3398
  const launchSpec = {
2277
3399
  cpuSeconds: Number(limits.cpu || 0),
2278
3400
  processMemoryBytes: Number(
@@ -2313,7 +3435,13 @@ export function applyWindowsSandbox(
2313
3435
  if (identityPath) launchSpec.identityPath = identityPath;
2314
3436
  const cleanupIdentity = () => {
2315
3437
  if (!identityPath) return true;
2316
- return cleanupOrTrackWindowsTemporaryPath(runtime, identityPath);
3438
+ return cleanupOrTrackWindowsTemporaryPath(
3439
+ runtime,
3440
+ identityPath,
3441
+ 100,
3442
+ null,
3443
+ identityFileIdentity,
3444
+ );
2317
3445
  };
2318
3446
  const appContainerProfileName = requiresAppContainer
2319
3447
  ? `ChainlessChain.CliSandbox.${runtime.randomBytes(12).toString("hex")}`
@@ -2503,10 +3631,14 @@ export function applyWindowsSandbox(
2503
3631
  detached: false,
2504
3632
  shell: false,
2505
3633
  cwd: helperWorkingDirectory,
2506
- env: hostEnvironment,
3634
+ env: Object.freeze({ ...hostEnvironment }),
3635
+ stdio: Array.isArray(invocation.options.stdio)
3636
+ ? Object.freeze([...invocation.options.stdio])
3637
+ : invocation.options.stdio,
2507
3638
  };
2508
3639
  let appContainerCleanupVerified = false;
2509
3640
  let appContainerCleanupRecord = null;
3641
+ let issuedWindowsMcpPlan = null;
2510
3642
  const cleanupAppContainer = () => {
2511
3643
  if (!appContainer || appContainerCleanupVerified) return true;
2512
3644
  if (appContainerCleanupRecord) {
@@ -2544,6 +3676,10 @@ export function applyWindowsSandbox(
2544
3676
  return false;
2545
3677
  };
2546
3678
  const cleanup = () => {
3679
+ if (issuedWindowsMcpPlan) {
3680
+ issuedWindowsMcpCodeSnapshotPlans.delete(issuedWindowsMcpPlan);
3681
+ issuedWindowsMcpPlan = null;
3682
+ }
2547
3683
  const failures = [];
2548
3684
  const attempt = (label, action) => {
2549
3685
  try {
@@ -2579,6 +3715,10 @@ export function applyWindowsSandbox(
2579
3715
  identityPath,
2580
3716
  runtime,
2581
3717
  appContainer,
3718
+ 30_000,
3719
+ (fileIdentity) => {
3720
+ identityFileIdentity = fileIdentity;
3721
+ },
2582
3722
  );
2583
3723
  if (!cleanupIdentity()) {
2584
3724
  throw new Error(
@@ -2604,6 +3744,28 @@ export function applyWindowsSandbox(
2604
3744
  },
2605
3745
  }
2606
3746
  : {};
3747
+ const windowsMcpPlanBinding =
3748
+ entrySnapshot.locks &&
3749
+ sandboxOpts.executionContract?.kind ===
3750
+ MCP_STDIO_CAPSULE_SANDBOX_CONTRACT_KIND
3751
+ ? createWindowsMcpCodeSnapshotPlanBinding({
3752
+ executionContract: sandboxOpts.executionContract,
3753
+ command,
3754
+ args,
3755
+ spawnOpts,
3756
+ profile,
3757
+ requiredBoundaries,
3758
+ sync: sandboxOpts.sync === true,
3759
+ backend,
3760
+ policyAttested: requiresAppContainer ? true : null,
3761
+ policyDigest: appContainerPolicyDigest,
3762
+ adapterSource,
3763
+ helperInvocation,
3764
+ helperOptions: options,
3765
+ postSpawn: identityContract.postSpawn,
3766
+ postSpawnWindows: identityContract.postSpawnWindows,
3767
+ })
3768
+ : null;
2607
3769
  const entrySnapshotRuntimeProbe = entrySnapshot.locks
2608
3770
  ? {
2609
3771
  ...(appContainerRuntimeProbe || nodeSnapshotRuntimeProbe),
@@ -2636,16 +3798,19 @@ export function applyWindowsSandbox(
2636
3798
  runtimeLaunchMechanism:
2637
3799
  "filter-oplock-locked-createprocess-suspended-image-v1",
2638
3800
  sharedLibraryClosure: false,
3801
+ planBindingMechanism: "windows-mcp-code-snapshot-plan-binding-v1",
3802
+ planBindingDigest: windowsMcpPlanBinding.planBindingDigest,
2639
3803
  }
2640
3804
  : {}),
2641
3805
  }
2642
3806
  : appContainerRuntimeProbe;
2643
- return createSandboxPlan({
3807
+ const plan = createSandboxPlan({
2644
3808
  ...base,
2645
3809
  applied: true,
2646
3810
  enforcement: backend,
2647
3811
  policyAttested: requiresAppContainer ? true : null,
2648
3812
  policyDigest: appContainerPolicyDigest,
3813
+ adapterTempRootAttestation: adapterSource.tempRootAttestation,
2649
3814
  runtimeProbe: entrySnapshotRuntimeProbe,
2650
3815
  guarantees: [
2651
3816
  ...(requiresAppContainer
@@ -2667,6 +3832,14 @@ export function applyWindowsSandbox(
2667
3832
  ...identityContract,
2668
3833
  cleanup,
2669
3834
  });
3835
+ if (
3836
+ windowsMcpPlanBinding &&
3837
+ planBindingAuthority === WINDOWS_MCP_CODE_SNAPSHOT_ISSUER
3838
+ ) {
3839
+ issuedWindowsMcpPlan = plan;
3840
+ issuedWindowsMcpCodeSnapshotPlans.set(plan, windowsMcpPlanBinding);
3841
+ }
3842
+ return plan;
2670
3843
  }
2671
3844
 
2672
3845
  /**
@@ -2717,13 +3890,12 @@ function linuxBubblewrapProbe(
2717
3890
  const supervisorDescriptorBound =
2718
3891
  supervisorBinding?.mechanism === LINUX_BWRAP_SUPERVISOR_BINDING_MECHANISM;
2719
3892
  const pluginTreeContentSnapshot =
2720
- !native &&
2721
3893
  attempted &&
2722
3894
  snapshot &&
2723
3895
  runnable &&
2724
3896
  reason === null &&
2725
3897
  supervisorDescriptorBound &&
2726
- pluginTreeSnapshot?.scope === LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE &&
3898
+ pluginTreeSnapshot?.scope === LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE &&
2727
3899
  pluginTreeSnapshot?.mechanism === LINUX_ENTRY_SNAPSHOT_MECHANISM &&
2728
3900
  Number.isSafeInteger(pluginTreeSnapshot?.files) &&
2729
3901
  pluginTreeSnapshot.files > 0 &&
@@ -2734,7 +3906,7 @@ function linuxBubblewrapProbe(
2734
3906
  typeof pluginTreeSnapshot?.digest === "string" &&
2735
3907
  /^[a-f0-9]{64}$/.test(pluginTreeSnapshot.digest) &&
2736
3908
  pluginTreeSnapshot?.consistency ===
2737
- LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY &&
3909
+ LINUX_PLUGIN_TREE_SNAPSHOT_CONSISTENCY &&
2738
3910
  pluginTreeSnapshot?.contractBound === false &&
2739
3911
  pluginTreeSnapshot?.atomic === false;
2740
3912
  const initialDynamicLoadClosureDescriptorBound =
@@ -3349,16 +4521,20 @@ function linuxEntrySnapshotContract(targetRuntime) {
3349
4521
  throw new Error("entry_snapshot_runtime_unsupported");
3350
4522
  }
3351
4523
 
3352
- 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";
3353
4529
  if (!Number.isSafeInteger(sourceMode) || sourceMode < 0) {
3354
- throw new Error("node_plugin_tree_snapshot_source_mode_invalid");
4530
+ throw new Error(`${errorPrefix}_source_mode_invalid`);
3355
4531
  }
3356
4532
  return {
3357
- scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
4533
+ scope: LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE,
3358
4534
  targetMode: (sourceMode & 0o111) !== 0 ? "0500" : "0400",
3359
4535
  sourceMode: LINUX_ENTRY_SNAPSHOT_SOURCE_MODE,
3360
4536
  minimumBytes: 0,
3361
- errorPrefix: "node_plugin_tree_snapshot",
4537
+ errorPrefix,
3362
4538
  };
3363
4539
  }
3364
4540
 
@@ -3439,9 +4615,9 @@ function createLinuxRegularFileSnapshot(
3439
4615
  throw snapshotError("source_changed");
3440
4616
  }
3441
4617
  if (
3442
- snapshotContract.scope === LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE &&
4618
+ snapshotContract.scope === LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE &&
3443
4619
  snapshotContract.targetMode !==
3444
- linuxNodePluginTreeFileSnapshotContract(sourceFileMode).targetMode
4620
+ ((sourceFileMode & 0o111) !== 0 ? "0500" : "0400")
3445
4621
  ) {
3446
4622
  throw snapshotError("source_mode_changed");
3447
4623
  }
@@ -3829,7 +5005,6 @@ function applyLinuxMcpCapsuleCodeSnapshot(
3829
5005
  ]);
3830
5006
  const descriptors = [runtimeSnapshot.finalMount, entrySnapshot.finalMount];
3831
5007
  const runtimeFd = 3;
3832
- const entryFd = 4;
3833
5008
  const snapshotIdentity = Object.freeze({
3834
5009
  runtimeSnapshotSha256: runtimeSnapshot.attestation.sha256,
3835
5010
  runtimeSnapshotBytes: runtimeSnapshot.attestation.bytes,
@@ -3846,6 +5021,8 @@ function applyLinuxMcpCapsuleCodeSnapshot(
3846
5021
  ].sort(),
3847
5022
  identity: snapshotIdentity,
3848
5023
  cwd: invocation.options.cwd,
5024
+ entrySnapshotBootstrapSha256: MCP_STDIO_FD_ENTRY_BOOTSTRAP_SHA256,
5025
+ runtimeLaunchMechanism: "inherited-executable-fd-v1",
3849
5026
  passthroughArgsDigest: sha256(JSON.stringify(invocation.args.slice(1))),
3850
5027
  }),
3851
5028
  );
@@ -3874,13 +5051,22 @@ function applyLinuxMcpCapsuleCodeSnapshot(
3874
5051
  contentSnapshotScope: "mcp-capsule-entry-and-node-runtime",
3875
5052
  contentSnapshotMechanism: LINUX_MCP_CAPSULE_SNAPSHOT_MECHANISM,
3876
5053
  handleAtomic: true,
5054
+ entrySnapshotAtomic: true,
5055
+ runtimeLaunchAtomic: true,
5056
+ runtimeLaunchMechanism: "inherited-executable-fd-v1",
5057
+ entrySnapshotBootstrapSha256: MCP_STDIO_FD_ENTRY_BOOTSTRAP_SHA256,
3877
5058
  sharedLibraryClosure: false,
3878
5059
  ...snapshotIdentity,
3879
5060
  },
3880
5061
  reason: null,
3881
5062
  guarantees: [SANDBOX_BOUNDARIES.CODE_SNAPSHOT],
3882
5063
  command: `/proc/self/fd/${runtimeFd}`,
3883
- args: [`/proc/self/fd/${entryFd}`, ...invocation.args.slice(1)],
5064
+ args: [
5065
+ "-e",
5066
+ MCP_STDIO_FD_ENTRY_BOOTSTRAP,
5067
+ "--",
5068
+ ...invocation.args.slice(1),
5069
+ ],
3884
5070
  options: {
3885
5071
  ...invocation.options,
3886
5072
  shell: false,
@@ -3892,7 +5078,21 @@ function applyLinuxMcpCapsuleCodeSnapshot(
3892
5078
  });
3893
5079
  }
3894
5080
 
3895
- 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";
3896
5096
  const members = [];
3897
5097
  const destinations = new Set();
3898
5098
  for (const mount of mounts || []) {
@@ -3901,18 +5101,23 @@ function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
3901
5101
  path.posix.normalize(mount.destination) !== mount.destination ||
3902
5102
  destinations.has(mount.destination)
3903
5103
  ) {
3904
- throw new Error("node_plugin_tree_snapshot_destination_invalid");
5104
+ throw new Error(`${errorPrefix}_destination_invalid`);
3905
5105
  }
3906
5106
  destinations.add(mount.destination);
3907
5107
  const snapshot = mount.contentSnapshot;
3908
5108
  const entry = mount.destination === entryDestination;
3909
5109
  const expectedScope = entry
3910
- ? LINUX_NODE_ENTRY_SNAPSHOT_SCOPE
3911
- : 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;
3912
5114
  const sourceMode = Number.parseInt(snapshot?.sourceFileMode, 8);
3913
5115
  const expectedTargetMode = entry
3914
- ? LINUX_NODE_ENTRY_SNAPSHOT_TARGET_MODE
3915
- : 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;
3916
5121
  if (
3917
5122
  snapshot?.scope !== expectedScope ||
3918
5123
  snapshot?.mechanism !== LINUX_ENTRY_SNAPSHOT_MECHANISM ||
@@ -3935,7 +5140,7 @@ function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
3935
5140
  mount.mountMode !== "ro-bind-data" ||
3936
5141
  mount.mountPermissions !== expectedTargetMode
3937
5142
  ) {
3938
- throw new Error("node_plugin_tree_snapshot_member_invalid");
5143
+ throw new Error(`${errorPrefix}_member_invalid`);
3939
5144
  }
3940
5145
  members.push({
3941
5146
  destination: mount.destination,
@@ -3962,58 +5167,66 @@ function buildLinuxNodePluginTreeSnapshotAttestation(mounts, entryDestination) {
3962
5167
  members.length > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES ||
3963
5168
  !members.some((member) => member.destination === entryDestination)
3964
5169
  ) {
3965
- throw new Error("node_plugin_tree_snapshot_file_count_invalid");
5170
+ throw new Error(`${errorPrefix}_file_count_invalid`);
3966
5171
  }
3967
5172
  const bytes = members.reduce((total, member) => total + member.bytes, 0);
3968
5173
  if (
3969
5174
  !Number.isSafeInteger(bytes) ||
3970
5175
  bytes > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES
3971
5176
  ) {
3972
- throw new Error("node_plugin_tree_snapshot_bytes_exceeded");
5177
+ throw new Error(`${errorPrefix}_bytes_exceeded`);
3973
5178
  }
3974
5179
  const digest = crypto
3975
5180
  .createHash("sha256")
3976
5181
  .update(
3977
5182
  JSON.stringify({
3978
5183
  version: 1,
3979
- scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
3980
- consistency: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
5184
+ scope: LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE,
5185
+ ...(native ? { targetRuntime } : {}),
5186
+ consistency: LINUX_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
3981
5187
  members,
3982
5188
  }),
3983
5189
  )
3984
5190
  .digest("hex");
3985
5191
  return Object.freeze({
3986
- scope: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_SCOPE,
5192
+ scope: LINUX_PLUGIN_TREE_SNAPSHOT_SCOPE,
3987
5193
  mechanism: LINUX_ENTRY_SNAPSHOT_MECHANISM,
3988
5194
  files: members.length,
3989
5195
  bytes,
3990
5196
  digest,
3991
- consistency: LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
5197
+ consistency: LINUX_PLUGIN_TREE_SNAPSHOT_CONSISTENCY,
3992
5198
  contractBound: false,
3993
5199
  atomic: false,
3994
5200
  });
3995
5201
  }
3996
5202
 
3997
- function attestLinuxNodePluginTreeSnapshot(
5203
+ function attestLinuxPluginTreeSnapshot(
3998
5204
  runtime,
3999
5205
  mounts,
4000
5206
  entryDestination,
5207
+ targetRuntime,
4001
5208
  expected,
4002
5209
  ) {
5210
+ const native = targetRuntime?.startsWith("native-");
5211
+ const errorPrefix = native
5212
+ ? "native_plugin_tree_snapshot"
5213
+ : "node_plugin_tree_snapshot";
4003
5214
  for (const mount of mounts || []) {
4004
5215
  if (!linuxPathWithin("/opt/chainless/plugin", mount?.destination)) continue;
4005
5216
  const snapshot = mount.contentSnapshot;
4006
5217
  const contract =
4007
5218
  mount.destination === entryDestination
4008
- ? linuxEntrySnapshotContract("node")
4009
- : linuxNodePluginTreeFileSnapshotContract(
5219
+ ? linuxEntrySnapshotContract(targetRuntime)
5220
+ : linuxPluginTreeFileSnapshotContract(
4010
5221
  Number.parseInt(snapshot?.sourceFileMode, 8),
5222
+ targetRuntime,
4011
5223
  );
4012
5224
  attestLinuxRegularFileSnapshot(runtime, mount, snapshot, contract);
4013
5225
  }
4014
- const actual = buildLinuxNodePluginTreeSnapshotAttestation(
5226
+ const actual = buildLinuxPluginTreeSnapshotAttestation(
4015
5227
  mounts,
4016
5228
  entryDestination,
5229
+ targetRuntime,
4017
5230
  );
4018
5231
  for (const field of [
4019
5232
  "scope",
@@ -4026,7 +5239,7 @@ function attestLinuxNodePluginTreeSnapshot(
4026
5239
  "atomic",
4027
5240
  ]) {
4028
5241
  if (actual[field] !== expected?.[field]) {
4029
- throw new Error("node_plugin_tree_snapshot_identity_changed");
5242
+ throw new Error(`${errorPrefix}_identity_changed`);
4030
5243
  }
4031
5244
  }
4032
5245
  }
@@ -5707,7 +6920,10 @@ export function applyLinuxSandbox(
5707
6920
  boundary === SANDBOX_BOUNDARIES.PROCESS_TREE,
5708
6921
  );
5709
6922
 
5710
- if (requiredBoundaries.includes(SANDBOX_BOUNDARIES.CODE_SNAPSHOT)) {
6923
+ if (
6924
+ requiredBoundaries.includes(SANDBOX_BOUNDARIES.CODE_SNAPSHOT) &&
6925
+ !requiresStrongLinuxBoundary
6926
+ ) {
5711
6927
  return applyLinuxMcpCapsuleCodeSnapshot(
5712
6928
  command,
5713
6929
  args,
@@ -5921,30 +7137,33 @@ export function applyLinuxSandbox(
5921
7137
  const rawPluginMounts = pluginMountIndexes.map(
5922
7138
  (index) => pinnedMounts[index],
5923
7139
  );
5924
- if (validation.entryRuntime === "node") {
5925
- if (
5926
- pluginMountIndexes.length < 1 ||
5927
- pluginMountIndexes.length > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_FILES
5928
- ) {
5929
- throw new Error("node_plugin_tree_snapshot_file_count_invalid");
5930
- }
5931
- const pluginBytes = pluginMountIndexes.reduce((total, index) => {
5932
- const bytes = pinnedMounts[index]?.bytes;
5933
- if (
5934
- !Number.isSafeInteger(bytes) ||
5935
- bytes < 0 ||
5936
- bytes > LINUX_ATTESTED_FILE_MAX_BYTES
5937
- ) {
5938
- throw new Error("node_plugin_tree_snapshot_member_size_invalid");
5939
- }
5940
- return total + bytes;
5941
- }, 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;
5942
7153
  if (
5943
- !Number.isSafeInteger(pluginBytes) ||
5944
- pluginBytes > LINUX_PLUGIN_TREE_SNAPSHOT_MAX_BYTES
7154
+ !Number.isSafeInteger(bytes) ||
7155
+ bytes < 0 ||
7156
+ bytes > LINUX_ATTESTED_FILE_MAX_BYTES
5945
7157
  ) {
5946
- throw new Error("node_plugin_tree_snapshot_bytes_exceeded");
7158
+ throw new Error(`${pluginSnapshotErrorPrefix}_member_size_invalid`);
5947
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`);
5948
7167
  }
5949
7168
  const rawEntryMount = entryMount;
5950
7169
  const snapshot = createLinuxEntrySnapshot(
@@ -5958,37 +7177,36 @@ export function applyLinuxSandbox(
5958
7177
  finalMounts[entryIndex] = snapshot.finalMount;
5959
7178
  probeMounts[entryIndex] = snapshot.probeMount;
5960
7179
  entrySnapshot = snapshot;
5961
- if (validation.entryRuntime === "node") {
5962
- for (const index of pluginMountIndexes) {
5963
- if (index === entryIndex) continue;
5964
- const rawMount = pinnedMounts[index];
5965
- const source = runtime.fs.fstatSync(rawMount.fd);
5966
- const fileSnapshot = createLinuxRegularFileSnapshot(
5967
- runtime,
5968
- rawMount,
5969
- {
5970
- fileId: rawMount.fileId,
5971
- bytes: rawMount.bytes,
5972
- mtimeMs: rawMount.mtimeMs,
5973
- },
5974
- linuxNodePluginTreeFileSnapshotContract(Number(source.mode)),
5975
- );
5976
- pluginTreeFileSnapshots.push(fileSnapshot);
5977
- finalMounts[index] = fileSnapshot.finalMount;
5978
- probeMounts[index] = fileSnapshot.probeMount;
5979
- }
5980
- pluginTreeSnapshot = buildLinuxNodePluginTreeSnapshotAttestation(
5981
- finalMounts,
5982
- 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
+ ),
5983
7196
  );
7197
+ pluginTreeFileSnapshots.push(fileSnapshot);
7198
+ finalMounts[index] = fileSnapshot.finalMount;
7199
+ probeMounts[index] = fileSnapshot.probeMount;
5984
7200
  }
7201
+ pluginTreeSnapshot = buildLinuxPluginTreeSnapshotAttestation(
7202
+ finalMounts,
7203
+ sandboxEntry,
7204
+ validation.entryRuntime,
7205
+ );
5985
7206
  pinnedMounts = finalMounts;
5986
7207
  probePinnedMounts = probeMounts;
5987
7208
  entryMount = entrySnapshot.finalMount;
5988
- closeLinuxPinnedMounts(
5989
- runtime,
5990
- validation.entryRuntime === "node" ? rawPluginMounts : [rawEntryMount],
5991
- );
7209
+ closeLinuxPinnedMounts(runtime, rawPluginMounts);
5992
7210
  } catch (error) {
5993
7211
  closeLinuxPinnedMounts(runtime, pinnedMounts);
5994
7212
  closeLinuxPinnedMounts(runtime, [
@@ -6168,7 +7386,7 @@ export function applyLinuxSandbox(
6168
7386
  .createHash("sha256")
6169
7387
  .update(
6170
7388
  JSON.stringify({
6171
- version: validation.entryRuntime === "node" ? 4 : 3,
7389
+ version: 4,
6172
7390
  backend: LINUX_BWRAP_BACKEND,
6173
7391
  supervisor: supervisorPin.attestation,
6174
7392
  contract: {
@@ -6212,9 +7430,7 @@ export function applyLinuxSandbox(
6212
7430
  ...(nativeDynamicClosure
6213
7431
  ? { initialDynamicLoadClosure: nativeDynamicClosure }
6214
7432
  : {}),
6215
- ...(validation.entryRuntime === "node"
6216
- ? { pluginTreeContentSnapshot: pluginTreeSnapshot }
6217
- : {}),
7433
+ pluginTreeContentSnapshot: pluginTreeSnapshot,
6218
7434
  seccomp: {
6219
7435
  arch: seccompFilter.arch,
6220
7436
  policy: seccompFilter.policy,
@@ -6287,21 +7503,13 @@ export function applyLinuxSandbox(
6287
7503
  });
6288
7504
  }
6289
7505
  try {
6290
- if (validation.entryRuntime === "node") {
6291
- attestLinuxNodePluginTreeSnapshot(
6292
- runtime,
6293
- pinnedMounts,
6294
- sandboxEntry,
6295
- pluginTreeSnapshot,
6296
- );
6297
- } else {
6298
- attestLinuxEntrySnapshot(
6299
- runtime,
6300
- entryMount,
6301
- entrySnapshot.attestation,
6302
- validation.entryRuntime,
6303
- );
6304
- }
7506
+ attestLinuxPluginTreeSnapshot(
7507
+ runtime,
7508
+ pinnedMounts,
7509
+ sandboxEntry,
7510
+ validation.entryRuntime,
7511
+ pluginTreeSnapshot,
7512
+ );
6305
7513
  } catch (error) {
6306
7514
  closeStrongLinuxResources(supervisorPin, finalLaunch);
6307
7515
  return createSandboxPlan({
@@ -6369,17 +7577,36 @@ export function applyLinuxSandbox(
6369
7577
  const supervisorBinding = supervisorPin.attestation;
6370
7578
  closeLinuxPinnedMounts(runtime, [supervisorPin]);
6371
7579
  supervisorPin = null;
7580
+ const capsuleContract =
7581
+ validation.contract.kind === MCP_STDIO_CAPSULE_SANDBOX_CONTRACT_KIND;
7582
+ const baseRuntimeProbe = linuxBubblewrapProbe(
7583
+ true,
7584
+ true,
7585
+ null,
7586
+ validation.entryRuntime,
7587
+ entrySnapshot?.attestation,
7588
+ supervisorBinding,
7589
+ pluginTreeSnapshot,
7590
+ nativeDynamicClosure,
7591
+ );
6372
7592
  const runtimeProbe = Object.freeze(
6373
- linuxBubblewrapProbe(
6374
- true,
6375
- true,
6376
- null,
6377
- validation.entryRuntime,
6378
- entrySnapshot?.attestation,
6379
- supervisorBinding,
6380
- pluginTreeSnapshot,
6381
- nativeDynamicClosure,
6382
- ),
7593
+ capsuleContract
7594
+ ? {
7595
+ ...baseRuntimeProbe,
7596
+ mcpCapsuleCodeSnapshot: true,
7597
+ entrySnapshotAtomic: true,
7598
+ runtimeLaunchAtomic: true,
7599
+ runtimeLaunchMechanism:
7600
+ "bwrap-descriptor-mount-node-runtime-exec-v1",
7601
+ sharedLibraryClosure: false,
7602
+ runtimeSnapshotSha256: validation.contract.runtimeIdentity.sha256,
7603
+ runtimeSnapshotBytes: validation.contract.runtimeIdentity.bytes,
7604
+ entrySnapshotSha256: entrySnapshot.attestation.sha256,
7605
+ entrySnapshotBytes: entrySnapshot.attestation.bytes,
7606
+ runtimeLaunchPath: "/opt/chainless/runtime/node",
7607
+ entrySnapshotPath: sandboxEntry,
7608
+ }
7609
+ : baseRuntimeProbe,
6383
7610
  );
6384
7611
  const options = {
6385
7612
  ...spawnOpts,
@@ -6417,6 +7644,7 @@ export function applyLinuxSandbox(
6417
7644
  SANDBOX_BOUNDARIES.FILESYSTEM,
6418
7645
  SANDBOX_BOUNDARIES.NETWORK,
6419
7646
  SANDBOX_BOUNDARIES.PROCESS_TREE,
7647
+ ...(capsuleContract ? [SANDBOX_BOUNDARIES.CODE_SNAPSHOT] : []),
6420
7648
  ],
6421
7649
  command: finalLaunch.command,
6422
7650
  args: [...policyArgs, "--", ...targetArgs],
@@ -6492,21 +7720,27 @@ export function applySandbox(
6492
7720
  args,
6493
7721
  spawnOpts,
6494
7722
  profileOrRequest = "default",
6495
- runtimeOverrides = {},
7723
+ runtimeOverrides = undefined,
6496
7724
  explicitRequest = null,
6497
7725
  ) {
7726
+ const runtimeInjected = runtimeOverrides !== undefined;
6498
7727
  const runtime = resolveRuntime(runtimeOverrides);
6499
7728
  const sandboxRequest = normalizeSandboxRequest(
6500
7729
  profileOrRequest,
6501
7730
  explicitRequest,
6502
7731
  );
6503
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();
6504
7738
  const profiles = {
6505
7739
  default: {
6506
7740
  allowNetwork: false,
6507
7741
  allowExec: true,
6508
7742
  allowRead: [runtime.homedir(), "/tmp"],
6509
- allowWrite: [runtime.tmpdir()],
7743
+ allowWrite: profileTempDirectory ? [profileTempDirectory] : [],
6510
7744
  limits: {
6511
7745
  // RLIMIT_NPROC is intentionally omitted. Linux applies it per real
6512
7746
  // user ID rather than to this child tree, so it can starve unrelated
@@ -6519,7 +7753,7 @@ export function applySandbox(
6519
7753
  allowNetwork: false,
6520
7754
  allowExec: false,
6521
7755
  allowRead: [],
6522
- allowWrite: [runtime.tmpdir()],
7756
+ allowWrite: profileTempDirectory ? [profileTempDirectory] : [],
6523
7757
  limits: {
6524
7758
  cpu: 10,
6525
7759
  as: 256 * 1024 * 1024, // 256MB address space
@@ -6530,7 +7764,7 @@ export function applySandbox(
6530
7764
  allowNetwork: true,
6531
7765
  allowExec: true,
6532
7766
  allowRead: [runtime.homedir()],
6533
- allowWrite: [runtime.tmpdir()],
7767
+ allowWrite: profileTempDirectory ? [profileTempDirectory] : [],
6534
7768
  limits: {
6535
7769
  cpu: 60,
6536
7770
  nofile: 512,
@@ -6558,6 +7792,7 @@ export function applySandbox(
6558
7792
  spawnOpts,
6559
7793
  profile,
6560
7794
  runtimeOverrides,
7795
+ runtimeInjected ? null : WINDOWS_MCP_CODE_SNAPSHOT_ISSUER,
6561
7796
  );
6562
7797
  }
6563
7798
  if (runtime.platform === "linux") {