chainlesschain 0.163.7 → 0.164.0

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 (252) hide show
  1. package/package.json +5 -1
  2. package/scripts/macos-mcp-launcher-build.mjs +317 -0
  3. package/scripts/macos-mcp-launcher-postinstall.sh +54 -0
  4. package/scripts/macos-mcp-launcher-preinstall.sh +61 -0
  5. package/src/assets/web-panel/assets/{AIOps-axqQfwv8.js → AIOps-D2MEn7fI.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-DV-Ro0By.js → ActionButton-lCW_4Gf0.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-Bt72WEVu.js → Analytics-Bs7i-nsS.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-BtepG55E.js → AppLayout-CK7B6S_j.js} +5 -5
  9. package/src/assets/web-panel/assets/{Artifacts-B7KaJ7S_.js → Artifacts-C8ILjpyo.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-FNFaUg7A.js → Audit-Bb9cNaWc.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-DFb6J2wQ.js → BackgroundAgents-sC9ooXIf.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-CC7YBhyj.js → Backup-D-DrsplT.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-RkgTNVtj.js → BaseInput-uCNZpwSK.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-hHEmGZy-.js → Chat-CsDK1uE9.js} +6 -6
  15. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CAP9YhHv.js → ChatBubbleRenderer-OqSDt50w.js} +1 -1
  16. package/src/assets/web-panel/assets/{Checkbox-z5wXOQP1.js → Checkbox-JhTXM1-K.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-DVaDJnxy.js → Codegen-BbzZLnyD.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-CJmaRDR5.js → Col-D4VW4m4j.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-DF29_2bI.js → Community-BXrYawxV.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-DtkQkBzn.js → Compact-DCihGy1u.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-xoHDxp7I.js → Compliance-DlyZ-lk8.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-BUm-R7ht.js → Cowork-DvGZrhbO.js} +2 -2
  23. package/src/assets/web-panel/assets/{Cron-Cgl14ZkB.js → Cron-DUncZi8h.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-BNcbmoqh.js → Crosschain-C_hLuUcm.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-Bwja0Zo9.js → DID-b6YSUqxr.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-Bk5XjEdJ.js → Dashboard-uvM15Cfb.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-Ci36Oitq.js → Dropdown-D1-Nd4qY.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer--U04ZN4q.js → EmailListRenderer-8ApvL6Zc.js} +1 -1
  29. package/src/assets/web-panel/assets/{FamilyGuardDashboard-B2gNPR2s.js → FamilyGuardDashboard-LYJt8811.js} +1 -1
  30. package/src/assets/web-panel/assets/{Federation-C4KghYn7.js → Federation-BbI3HGpv.js} +1 -1
  31. package/src/assets/web-panel/assets/{FormItemContext-QL7x_Q_0.js → FormItemContext-DSoEKuFu.js} +1 -1
  32. package/src/assets/web-panel/assets/{GenericCardRenderer-F3Ryltwm.js → GenericCardRenderer-B0S9OWd5.js} +1 -1
  33. package/src/assets/web-panel/assets/{Git-2HassK5k.js → Git-DIs9Zhf3.js} +2 -2
  34. package/src/assets/web-panel/assets/{Governance-DoBIp7oe.js → Governance-jzZXrxcl.js} +1 -1
  35. package/src/assets/web-panel/assets/{Inference--W01QhZ-.js → Inference-JUPjf2ZI.js} +1 -1
  36. package/src/assets/web-panel/assets/{KnowledgeGraph-DmvwqGpT.js → KnowledgeGraph-DxnfJvlP.js} +1 -1
  37. package/src/assets/web-panel/assets/{Logs-BBJ4yAel.js → Logs-iXiz8BEE.js} +2 -2
  38. package/src/assets/web-panel/assets/{MarkdownRenderer-BLCZMzhJ.js → MarkdownRenderer-GmB6DOr6.js} +1 -1
  39. package/src/assets/web-panel/assets/{Marketplace-D5lNngSI.js → Marketplace-Y3Xch312.js} +1 -1
  40. package/src/assets/web-panel/assets/{McpTools-CkEVOMKH.js → McpTools-D7kGGbO7.js} +4 -4
  41. package/src/assets/web-panel/assets/{Memory-B1Ow1xVE.js → Memory-Do_hVxzh.js} +2 -2
  42. package/src/assets/web-panel/assets/{MobileBridge-SgYbUfb6.js → MobileBridge-CtrR0Tas.js} +1 -1
  43. package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +1 -0
  44. package/src/assets/web-panel/assets/{Mtc-DcTXSSma.js → Mtc-qo5QCTGd.js} +2 -2
  45. package/src/assets/web-panel/assets/{MtcAudit-Dt3WG4oj.js → MtcAudit-cq5mnjYj.js} +2 -2
  46. package/src/assets/web-panel/assets/{Multisig-B9B38UZx.js → Multisig-Bq0MAQHw.js} +3 -3
  47. package/src/assets/web-panel/assets/{NLProgramming-B0S4GGvG.js → NLProgramming-DdWE6Vi2.js} +1 -1
  48. package/src/assets/web-panel/assets/{Notes-DZ5U58Ng.js → Notes-BxMBFcIp.js} +3 -3
  49. package/src/assets/web-panel/assets/{NotificationSettings-PjL5ZW34.js → NotificationSettings-RTrAcbUe.js} +1 -1
  50. package/src/assets/web-panel/assets/{OrderTableRenderer-DrAX2ESt.js → OrderTableRenderer-DJbpGqzT.js} +1 -1
  51. package/src/assets/web-panel/assets/{Organization-Cv1GYs-c.js → Organization-SQzIPdqC.js} +4 -4
  52. package/src/assets/web-panel/assets/{Overflow-Bbkp_PL3.js → Overflow-02yECh22.js} +1 -1
  53. package/src/assets/web-panel/assets/{P2P-BD8QUgoZ.js → P2P-BNg4pNCM.js} +2 -2
  54. package/src/assets/web-panel/assets/{PdhVaultBrowser-BO-2PeaB.js → PdhVaultBrowser-BxPJZsD3.js} +3 -3
  55. package/src/assets/web-panel/assets/{Permissions-D_OPVa02.js → Permissions-CLnDnPAz.js} +4 -4
  56. package/src/assets/web-panel/assets/{PersonalDataHub-mh-pEmaB.js → PersonalDataHub-C5m0AjGf.js} +4 -4
  57. package/src/assets/web-panel/assets/{Pipeline-6JC2EDdb.js → Pipeline-VwOf5Dhv.js} +1 -1
  58. package/src/assets/web-panel/assets/{Privacy-CUshzUAG.js → Privacy-CC0W2hZd.js} +1 -1
  59. package/src/assets/web-panel/assets/{ProjectInit-Cs2Aalri.js → ProjectInit-Dz893Bob.js} +2 -2
  60. package/src/assets/web-panel/assets/{ProjectSettings-1ak49tcj.js → ProjectSettings-CxQkvl0k.js} +2 -2
  61. package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +1 -0
  62. package/src/assets/web-panel/assets/{Providers-CXXxvafH.js → Providers-DeB9IkxK.js} +1 -1
  63. package/src/assets/web-panel/assets/{QrScannerModal-PTcDsF58.js → QrScannerModal-OCeoaswn.js} +1 -1
  64. package/src/assets/web-panel/assets/{QuickAsk-CDwucrJw.js → QuickAsk-DenotaAj.js} +1 -1
  65. package/src/assets/web-panel/assets/{Recommend-CJlLVRXR.js → Recommend-Cies_jjI.js} +1 -1
  66. package/src/assets/web-panel/assets/{RemoteSession-DlN-ETsO.js → RemoteSession-Dxzl5n2T.js} +3 -3
  67. package/src/assets/web-panel/assets/{Reputation-DI23LR6m.js → Reputation-CW0mvxgZ.js} +1 -1
  68. package/src/assets/web-panel/assets/{Row-Cl7UQMrM.js → Row-SEd32qZU.js} +1 -1
  69. package/src/assets/web-panel/assets/{RssFeed-CexHNfh8.js → RssFeed-BSwnJ3Lm.js} +3 -3
  70. package/src/assets/web-panel/assets/{Search-BneBWvRN.js → Search-xe2m8bkC.js} +1 -1
  71. package/src/assets/web-panel/assets/{Security-DX7Phgrp.js → Security-Mq2F9Nro.js} +4 -4
  72. package/src/assets/web-panel/assets/{Services-BWXO8Mte.js → Services-BKGw2V3P.js} +2 -2
  73. package/src/assets/web-panel/assets/{Skeleton-DCyOjKPJ.js → Skeleton-DW95-Rxf.js} +1 -1
  74. package/src/assets/web-panel/assets/{Skills-CXqERsTB.js → Skills-CHgChCv_.js} +1 -1
  75. package/src/assets/web-panel/assets/{Sla-bhbrbz8E.js → Sla-tKh6k3R7.js} +1 -1
  76. package/src/assets/web-panel/assets/{SpeechSettings-3IPU0HBZ.js → SpeechSettings-D6UL3dA5.js} +1 -1
  77. package/src/assets/web-panel/assets/{SyncSettings-DBNTQLbM.js → SyncSettings-D0Fw8fVL.js} +2 -2
  78. package/src/assets/web-panel/assets/{Tasks-quP0bkN-.js → Tasks-20x1GB3D.js} +1 -1
  79. package/src/assets/web-panel/assets/{Templates-BouWmgho.js → Templates-DJ-lts-a.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tenant-qv0D9s7z.js → Tenant-BFdGpbs7.js} +1 -1
  81. package/src/assets/web-panel/assets/{Terminal-BxvbE8BB.js → Terminal-Xpx61vHL.js} +2 -2
  82. package/src/assets/web-panel/assets/{TimelineRenderer-D-IeLnJ2.js → TimelineRenderer-C6UIvyZQ.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tokens-CFMKKyVG.js → Tokens-Dellq08f.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trigger-L8as3UuJ.js → Trigger-VKw8d0J-.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trust-MxmA4VZG.js → Trust-DNftdKT5.js} +1 -1
  86. package/src/assets/web-panel/assets/{UkeySign-C0VvJYRi.js → UkeySign-DX9VAKJI.js} +1 -1
  87. package/src/assets/web-panel/assets/{VideoEditing-Bf5QZj8N.js → VideoEditing-B2h3jO8q.js} +1 -1
  88. package/src/assets/web-panel/assets/{Wallet-BIJ9riRz.js → Wallet-CjGkof7f.js} +4 -4
  89. package/src/assets/web-panel/assets/{WebAuthn-B8p9Mhkw.js → WebAuthn-2Zfcu55P.js} +4 -4
  90. package/src/assets/web-panel/assets/{WorkflowEditor-BVsuaPen.js → WorkflowEditor-B9eZ8UxZ.js} +1 -1
  91. package/src/assets/web-panel/assets/{chat-BfkjoKpV.js → chat-CQNfD-YO.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-DeAlsWK8.js → colors-D4usyFT2.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-Cs97bz5j.js → compact-item-s4TpjvjX.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-BqbG60LH.js → createContext-BRCSvYxj.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +1 -0
  96. package/src/assets/web-panel/assets/{hasIn-i_RY0b1X.js → hasIn-AdkUbkV-.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BOVkf1K3.js → index--00U6vXZ.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B-3tEpTc.js → index-B2V-eKfK.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-D2B4VPyP.js → index-B4JJCfQ2.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BkBi5TmC.js → index-BIoG5yYf.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-79V_zv1s.js → index-BMcimU-2.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-U4f5izHz.js → index-BPhkFZ1I.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DiSkx4UB.js → index-BQ3DXBtD.js} +3 -3
  104. package/src/assets/web-panel/assets/{index-dIHr4B_u.js → index-Bad4VNvz.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CIG8fQ2_.js → index-BgiXR_-_.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DqBgBpgp.js → index-BtobShro.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-ZxO_-E8a.js → index-Bw2MEnUk.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DUUrv141.js → index-C1mJtE6q.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Bm7lLR4c.js → index-C484EJiS.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-COdGA-8n.js → index-C5KAQFPQ.js} +1 -1
  111. package/src/assets/web-panel/assets/index-C61cnl3K.js +1 -0
  112. package/src/assets/web-panel/assets/{index-CED2PL_y.js → index-C7Y8Aluc.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-YXgLng0K.js → index-CCsM_Ybx.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-0-agYlWL.js → index-CJCFe2Qf.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-B5UkyA3c.js → index-CJVEOa4i.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-B1CbOH1i.js → index-CJdQrZth.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-s1opAL-M.js → index-CKC1t3Ic.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-yObj1O4n.js → index-CQhAVU5t.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CgsqdnRV.js → index-CYdoQsa1.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Dk7-v4hP.js → index-Cduu2Hhr.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-B-JVTrpr.js → index-CkwiSLhU.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CeWoNHau.js → index-Cvyt-zhm.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CE2VtTlp.js → index-D2GjI2oe.js} +1 -1
  124. package/src/assets/web-panel/assets/index-D4XFHxBr.js +1 -0
  125. package/src/assets/web-panel/assets/{index-DmbfhJUP.js → index-DARsAyUp.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BESLFt4q.js → index-DVKfQ0Ux.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-PT_5sBMA.js → index-DZT36ZYq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-gyFpvL7l.js → index-D_XBHopw.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BYUVuwKt.js → index-DdCB70SS.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BcUXAB2s.js → index-DzRjHxEv.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Dk2qouAz.js → index-MMN9V6JF.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D9evtvg2.js → index-RfMr79MW.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-BuLQobzW.js → index-_IRVsV9X.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-Cf3WCLmy.js → index-nXyk8GZ9.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CG3PRyAL.js → index-s_buFMEG.js} +1 -1
  136. package/src/assets/web-panel/assets/{initDefaultProps-C9h9yKL3.js → initDefaultProps-Blmpk7HL.js} +1 -1
  137. package/src/assets/web-panel/assets/{motion-BvmLsPdu.js → motion-DPBJMv1n.js} +1 -1
  138. package/src/assets/web-panel/assets/{move-CoL4ekU1.js → move-ButdzCM7.js} +1 -1
  139. package/src/assets/web-panel/assets/{mtc-parser-C4pMy6xV.js → mtc-parser-cyRC_QCe.js} +1 -1
  140. package/src/assets/web-panel/assets/{omit-DPuJfNxx.js → omit-1epSV7hz.js} +1 -1
  141. package/src/assets/web-panel/assets/{pickAttrs-D0KEeaK2.js → pickAttrs-DuPY8xJs.js} +1 -1
  142. package/src/assets/web-panel/assets/{placementArrow-BpkyINxt.js → placementArrow-6XZqarhe.js} +1 -1
  143. package/src/assets/web-panel/assets/{responsiveObserve-DQI_wOq8.js → responsiveObserve-B40pXBWM.js} +1 -1
  144. package/src/assets/web-panel/assets/{slide-DYfdDiRg.js → slide-jbZ9Wu4n.js} +1 -1
  145. package/src/assets/web-panel/assets/{statusUtils-B5dtvb0D.js → statusUtils-BKQQcVy4.js} +1 -1
  146. package/src/assets/web-panel/assets/{styleChecker-CF310mSj.js → styleChecker-NiKfWjUT.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFlexGapSupport-D1Qp4BLp.js → useFlexGapSupport-bQUCvPeb.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFs-C7_iyvJK.js → useFs-CUGji7ph.js} +1 -1
  149. package/src/assets/web-panel/assets/{usePersonalDataHub-DZUt_fIm.js → usePersonalDataHub-4veyN2N2.js} +1 -1
  150. package/src/assets/web-panel/assets/{vnode-D3dsi0lZ.js → vnode-1-nLug-Z.js} +1 -1
  151. package/src/assets/web-panel/assets/{zoom-WdbOlvdQ.js → zoom-dhCeRDMo.js} +1 -1
  152. package/src/assets/web-panel/index.html +1 -1
  153. package/src/command-help-index.json +6 -6
  154. package/src/commands/automation.js +103 -1
  155. package/src/commands/cowork.js +174 -19
  156. package/src/commands/permissions.js +240 -16
  157. package/src/commands/plugin.js +1293 -56
  158. package/src/commands/session-location.js +276 -0
  159. package/src/commands/session.js +2 -0
  160. package/src/commands/team-merge-review.js +937 -0
  161. package/src/commands/team.js +2 -0
  162. package/src/data/changelog.json +27 -1
  163. package/src/gateways/ws/action-protocol.js +35 -10
  164. package/src/gateways/ws/remote-session-protocol.js +24 -1
  165. package/src/gateways/ws/ws-agent-handler.js +12 -0
  166. package/src/gateways/ws/ws-server.js +48 -12
  167. package/src/harness/jsonl-session-store.js +341 -24
  168. package/src/harness/mcp-client.js +8 -1
  169. package/src/harness/remote-session-audit.js +1 -0
  170. package/src/harness/remote-session-crypto.js +210 -2
  171. package/src/harness/remote-session-registry.js +159 -6
  172. package/src/harness/session-list-index.js +233 -14
  173. package/src/lib/agent-authority.js +6 -0
  174. package/src/lib/agent-schedule-store.js +139 -28
  175. package/src/lib/agent-team/team-merge-review-store.js +765 -0
  176. package/src/lib/agent-team/team-merge-review-transaction.js +1223 -0
  177. package/src/lib/agent-team/team-merge-review.js +1847 -0
  178. package/src/lib/automation-center-incidents.js +314 -0
  179. package/src/lib/automation-center-runtime.js +448 -0
  180. package/src/lib/automation-center.js +182 -38
  181. package/src/lib/automation-engine.js +41 -0
  182. package/src/lib/automation-execution-authority.js +241 -12
  183. package/src/lib/automation-execution-incident.js +737 -0
  184. package/src/lib/background-agent-keeper-client.js +248 -0
  185. package/src/lib/background-agent-keeper-protocol.js +147 -0
  186. package/src/lib/background-agent-supervisor.js +520 -40
  187. package/src/lib/background-interaction-resolver.js +12 -0
  188. package/src/lib/background-turn-bootstrap-protocol.js +126 -0
  189. package/src/lib/cowork-cron.js +85 -14
  190. package/src/lib/cowork-workflow.js +336 -16
  191. package/src/lib/dynamic-workflow-facade.js +648 -0
  192. package/src/lib/execution-location-contract.js +551 -0
  193. package/src/lib/execution-location-runtime.js +130 -0
  194. package/src/lib/ide-context.js +94 -6
  195. package/src/lib/mcp-stdio-package-materialization.js +166 -14
  196. package/src/lib/packer/pkg-config-generator.js +2 -0
  197. package/src/lib/permission-authority.js +77 -0
  198. package/src/lib/permission-side-effect-center.js +495 -0
  199. package/src/lib/plugin-runtime/install.js +514 -13
  200. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +900 -0
  201. package/src/lib/plugin-runtime/marketplace-catalog.js +1181 -0
  202. package/src/lib/plugin-runtime/marketplace-impact.js +408 -0
  203. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1567 -0
  204. package/src/lib/plugin-security.js +60 -3
  205. package/src/lib/process-execution-broker/index.js +1095 -57
  206. package/src/lib/process-execution-broker/linux-bwrap-descriptor-launch.js +375 -0
  207. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +90 -16
  208. package/src/lib/process-execution-broker/linux-generic-bwrap.js +159 -18
  209. package/src/lib/process-execution-broker/macos-mcp-launcher-contract.js +278 -0
  210. package/src/lib/process-execution-broker/macos-mcp-launcher-protocol.json +23 -0
  211. package/src/lib/process-execution-broker/macos-mcp-launcher.c +1497 -0
  212. package/src/lib/process-execution-broker/mcp-fd-entry-bootstrap.js +37 -0
  213. package/src/lib/process-execution-broker/platform-sandbox.js +2330 -279
  214. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  215. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  216. package/src/lib/process-execution-broker/windows-sandbox.cs +50 -4
  217. package/src/lib/public-ide-capability-manifest.js +3 -0
  218. package/src/lib/remote-approval-bridge.js +123 -17
  219. package/src/lib/remote-membership-authority.js +632 -0
  220. package/src/lib/remote-membership-coordinator.js +2409 -0
  221. package/src/lib/remote-membership-host-store.js +1750 -0
  222. package/src/lib/scheduler-kernel/agenda-adapter.js +17 -7
  223. package/src/lib/scheduler-kernel/automation-adapter.js +105 -11
  224. package/src/lib/scheduler-kernel/automation-event-adapter.js +87 -11
  225. package/src/lib/scheduler-kernel/contract.js +91 -0
  226. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +12 -1
  227. package/src/lib/scheduler-kernel/loop-adapter.js +7 -1
  228. package/src/lib/scheduler-kernel/routine-adapter.js +7 -1
  229. package/src/lib/scheduler-kernel/runtime-control-capabilities.js +31 -0
  230. package/src/lib/scheduler-kernel/runtime.js +176 -3
  231. package/src/lib/scheduler-kernel/store.js +1253 -45
  232. package/src/lib/scoped-permission-store.js +369 -0
  233. package/src/lib/session-anti-rollback-anchor.js +262 -5
  234. package/src/lib/sub-agent-context.js +4 -0
  235. package/src/lib/workflow-definition-contract.js +149 -0
  236. package/src/repl/agent-repl.js +41 -7
  237. package/src/repl/chat-repl.js +2 -0
  238. package/src/repl/clipboard-image.js +735 -16
  239. package/src/repl/ide-status.js +1 -1
  240. package/src/repl/prompt-interactions.js +59 -6
  241. package/src/runtime/agent-core.js +93 -3
  242. package/src/runtime/headless-runner.js +30 -25
  243. package/src/runtime/headless-stream.js +32 -25
  244. package/src/runtime/mcp-config.js +81 -10
  245. package/src/workers/background-agent-keeper.js +498 -0
  246. package/src/workers/background-agent-worker.js +358 -13
  247. package/src/workers/background-turn-bootstrap.js +143 -0
  248. package/src/assets/web-panel/assets/MobileProjects-BWdOvQAj.js +0 -1
  249. package/src/assets/web-panel/assets/Projects-BV4oApsm.js +0 -1
  250. package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +0 -1
  251. package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +0 -1
  252. package/src/assets/web-panel/assets/index-ChVT0p9u.js +0 -1
@@ -0,0 +1,1497 @@
1
+ /*
2
+ * ChainlessChain macOS MCP runtime launcher.
3
+ *
4
+ * This helper is intentionally useful only after a formal release installs a
5
+ * Developer-ID-signed Mach-O at the fixed root:wheel path with mode 04555 and
6
+ * installs the matching root-owned contract. It does not turn a pathname
7
+ * recheck into an atomic claim. Instead, fd 3 supplies the already-approved
8
+ * Node runtime bytes. A root watchdog copies and hashes those bytes into a
9
+ * root-only namespace that the real caller may traverse and execute but may
10
+ * not mutate. The actual Node image must run the compiled READY bootstrap;
11
+ * fd 4 is copied into a root-owned read-only file, reopened, identity checked,
12
+ * unlinked, and directory-fsynced before the target exists. Only that anonymous
13
+ * snapshot is inherited by the target. After the watchdog unlinks the runtime,
14
+ * removes its directory, fsyncs the fixed root, and observes both helper and
15
+ * Broker lifelines, it releases the immutable entry snapshot through the gate.
16
+ *
17
+ * Generated build constants come solely from
18
+ * macos-mcp-launcher-protocol.json and mcp-fd-entry-bootstrap.js.
19
+ */
20
+
21
+ #define _DARWIN_C_SOURCE 1
22
+
23
+ #include "macos-mcp-launcher-generated.h"
24
+
25
+ #include <CommonCrypto/CommonDigest.h>
26
+ #include <dirent.h>
27
+ #include <errno.h>
28
+ #include <fcntl.h>
29
+ #include <grp.h>
30
+ #include <limits.h>
31
+ #include <poll.h>
32
+ #include <signal.h>
33
+ #include <stdint.h>
34
+ #include <stdio.h>
35
+ #include <stdlib.h>
36
+ #include <string.h>
37
+ #include <strings.h>
38
+ #include <sys/file.h>
39
+ #include <sys/resource.h>
40
+ #include <sys/stat.h>
41
+ #include <sys/types.h>
42
+ #include <sys/wait.h>
43
+ #include <unistd.h>
44
+
45
+ extern char **environ;
46
+
47
+ #define CC_EXIT_USAGE 64
48
+ #define CC_EXIT_UNAVAILABLE 69
49
+ #define CC_EXIT_SECURITY 77
50
+ #define CC_EXIT_TEMPFAIL 75
51
+ #define CC_EXIT_INTERNAL 70
52
+ #define CC_NONCE_HEX_BYTES 64
53
+ #define CC_SHA256_HEX_BYTES 64
54
+ #define CC_COPY_CHUNK_BYTES (1024 * 1024)
55
+ #define CC_PROFILE_CAPACITY (PATH_MAX * 5 + 8192)
56
+
57
+ struct launch_request {
58
+ char nonce[CC_NONCE_HEX_BYTES + 1];
59
+ char runtime_sha256[CC_SHA256_HEX_BYTES + 1];
60
+ off_t runtime_bytes;
61
+ char entry_sha256[CC_SHA256_HEX_BYTES + 1];
62
+ off_t entry_bytes;
63
+ uid_t caller_uid;
64
+ gid_t caller_gid;
65
+ char policy_sha256[CC_SHA256_HEX_BYTES + 1];
66
+ int passthrough_argc;
67
+ char **passthrough_argv;
68
+ };
69
+
70
+ struct source_evidence {
71
+ struct stat runtime_stat;
72
+ struct stat entry_stat;
73
+ struct stat entry_snapshot_stat;
74
+ struct stat capsule_stat;
75
+ };
76
+
77
+ static volatile sig_atomic_t control_write_fd = -1;
78
+ static volatile sig_atomic_t pending_signal = 0;
79
+
80
+ static int scan_descriptor_directory(int minimum_fd, int close_entries) {
81
+ DIR *directory = NULL;
82
+ struct dirent *entry;
83
+ int directory_fd;
84
+ int unexpected = 0;
85
+ int result = -1;
86
+
87
+ if (minimum_fd < 0 || (directory = opendir("/dev/fd")) == NULL) {
88
+ return -1;
89
+ }
90
+ directory_fd = dirfd(directory);
91
+ if (directory_fd < 0) {
92
+ goto done;
93
+ }
94
+ for (;;) {
95
+ char *end = NULL;
96
+ long candidate;
97
+ errno = 0;
98
+ entry = readdir(directory);
99
+ if (entry == NULL) {
100
+ if (errno == 0) result = unexpected ? -1 : 0;
101
+ break;
102
+ }
103
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
104
+ continue;
105
+ }
106
+ errno = 0;
107
+ candidate = strtol(entry->d_name, &end, 10);
108
+ if (errno == ERANGE || end == entry->d_name || *end != '\0' ||
109
+ candidate < 0 || candidate > INT_MAX) {
110
+ goto done;
111
+ }
112
+ if (candidate < minimum_fd || candidate == directory_fd) {
113
+ continue;
114
+ }
115
+ if (!close_entries) {
116
+ unexpected = 1;
117
+ }
118
+ if (close((int)candidate) != 0 && errno != EBADF) {
119
+ goto done;
120
+ }
121
+ }
122
+
123
+ done:
124
+ if (closedir(directory) != 0) result = -1;
125
+ return result;
126
+ }
127
+
128
+ static int close_descriptors_from(int minimum_fd) {
129
+ /* Darwin exposes descriptor enumeration through /dev/fd but does not expose
130
+ * closefrom in the public macOS 13 SDK. A second pass closes and rejects any
131
+ * descriptor the first pass skipped, so no late descriptor is leaked even
132
+ * on the fail-closed path. RLIMIT_NOFILE is not an authority: a caller can
133
+ * lower it after opening a higher-numbered fd. */
134
+ if (scan_descriptor_directory(minimum_fd, 1) != 0) {
135
+ return -1;
136
+ }
137
+ return scan_descriptor_directory(minimum_fd, 0);
138
+ }
139
+
140
+ static void relay_signal(int signal_number) {
141
+ unsigned char value = (unsigned char)signal_number;
142
+ int fd = (int)control_write_fd;
143
+ int saved_errno = errno;
144
+ ssize_t written = -1;
145
+ pending_signal = (sig_atomic_t)signal_number;
146
+ if (fd >= 0) {
147
+ /* The write end is O_NONBLOCK. A single-byte write is async-signal-safe;
148
+ * EAGAIN means an earlier abort signal is already queued for the watchdog. */
149
+ written = write(fd, &value, sizeof(value));
150
+ }
151
+ if (written == 1) pending_signal = 0;
152
+ errno = saved_errno;
153
+ }
154
+
155
+ static void flush_pending_signal(void) {
156
+ sigset_t handled;
157
+ sigset_t previous;
158
+ sig_atomic_t signal_number;
159
+ unsigned char value;
160
+ ssize_t written;
161
+ int saved_errno = errno;
162
+ sigemptyset(&handled);
163
+ sigaddset(&handled, SIGINT);
164
+ sigaddset(&handled, SIGTERM);
165
+ sigaddset(&handled, SIGHUP);
166
+ sigaddset(&handled, SIGQUIT);
167
+ if (sigprocmask(SIG_BLOCK, &handled, &previous) != 0) return;
168
+ signal_number = pending_signal;
169
+ if (signal_number > 0 && control_write_fd >= 0) {
170
+ value = (unsigned char)signal_number;
171
+ written = write((int)control_write_fd, &value, sizeof(value));
172
+ if (written == 1 || (written < 0 && errno == EAGAIN)) {
173
+ pending_signal = 0;
174
+ }
175
+ }
176
+ (void)sigprocmask(SIG_SETMASK, &previous, NULL);
177
+ errno = saved_errno;
178
+ }
179
+
180
+ static int is_lower_hex(const char *value, size_t length) {
181
+ size_t index;
182
+ if (value == NULL || strlen(value) != length) {
183
+ return 0;
184
+ }
185
+ for (index = 0; index < length; index += 1) {
186
+ char character = value[index];
187
+ if (!((character >= '0' && character <= '9') ||
188
+ (character >= 'a' && character <= 'f'))) {
189
+ return 0;
190
+ }
191
+ }
192
+ return 1;
193
+ }
194
+
195
+ static int parse_off_t(const char *value, off_t maximum, off_t *result) {
196
+ char *end = NULL;
197
+ unsigned long long parsed;
198
+ if (value == NULL || value[0] == '\0' || value[0] == '-') {
199
+ return -1;
200
+ }
201
+ errno = 0;
202
+ parsed = strtoull(value, &end, 10);
203
+ if (errno != 0 || end == value || *end != '\0' || parsed > (uint64_t)maximum) {
204
+ return -1;
205
+ }
206
+ *result = (off_t)parsed;
207
+ return 0;
208
+ }
209
+
210
+ static int parse_uid(const char *value, uid_t *result) {
211
+ off_t parsed;
212
+ if (parse_off_t(value, (off_t)UINT32_MAX, &parsed) != 0) {
213
+ return -1;
214
+ }
215
+ *result = (uid_t)parsed;
216
+ return 0;
217
+ }
218
+
219
+ static int parse_gid(const char *value, gid_t *result) {
220
+ off_t parsed;
221
+ if (parse_off_t(value, (off_t)UINT32_MAX, &parsed) != 0) {
222
+ return -1;
223
+ }
224
+ *result = (gid_t)parsed;
225
+ return 0;
226
+ }
227
+
228
+ static void sha256_hex(const unsigned char digest[CC_SHA256_DIGEST_LENGTH],
229
+ char output[CC_SHA256_HEX_BYTES + 1]) {
230
+ static const char alphabet[] = "0123456789abcdef";
231
+ size_t index;
232
+ for (index = 0; index < CC_SHA256_DIGEST_LENGTH; index += 1) {
233
+ output[index * 2] = alphabet[(digest[index] >> 4) & 0xf];
234
+ output[index * 2 + 1] = alphabet[digest[index] & 0xf];
235
+ }
236
+ output[CC_SHA256_HEX_BYTES] = '\0';
237
+ }
238
+
239
+ static int same_file_stat(const struct stat *left, const struct stat *right) {
240
+ return left->st_dev == right->st_dev && left->st_ino == right->st_ino &&
241
+ left->st_mode == right->st_mode && left->st_nlink == right->st_nlink &&
242
+ left->st_uid == right->st_uid && left->st_gid == right->st_gid &&
243
+ left->st_size == right->st_size &&
244
+ left->st_mtimespec.tv_sec == right->st_mtimespec.tv_sec &&
245
+ left->st_mtimespec.tv_nsec == right->st_mtimespec.tv_nsec &&
246
+ left->st_ctimespec.tv_sec == right->st_ctimespec.tv_sec &&
247
+ left->st_ctimespec.tv_nsec == right->st_ctimespec.tv_nsec;
248
+ }
249
+
250
+ static int same_snapshot_identity(const struct stat *left,
251
+ const struct stat *right) {
252
+ return left->st_dev == right->st_dev && left->st_ino == right->st_ino &&
253
+ left->st_mode == right->st_mode && left->st_uid == right->st_uid &&
254
+ left->st_gid == right->st_gid && left->st_size == right->st_size &&
255
+ left->st_mtimespec.tv_sec == right->st_mtimespec.tv_sec &&
256
+ left->st_mtimespec.tv_nsec == right->st_mtimespec.tv_nsec;
257
+ }
258
+
259
+ static int hash_open_file(int fd, off_t expected_bytes,
260
+ char output[CC_SHA256_HEX_BYTES + 1],
261
+ struct stat *stable_stat) {
262
+ unsigned char *buffer = NULL;
263
+ unsigned char digest[CC_SHA256_DIGEST_LENGTH];
264
+ CC_SHA256_CTX context;
265
+ struct stat before;
266
+ struct stat after;
267
+ off_t offset = 0;
268
+ int result = -1;
269
+
270
+ if (fstat(fd, &before) != 0 || !S_ISREG(before.st_mode) ||
271
+ before.st_size != expected_bytes || expected_bytes < 0) {
272
+ return -1;
273
+ }
274
+ buffer = (unsigned char *)malloc(CC_COPY_CHUNK_BYTES);
275
+ if (buffer == NULL || CC_SHA256_Init(&context) != 1) {
276
+ goto done;
277
+ }
278
+ while (offset < expected_bytes) {
279
+ size_t requested = (size_t)((expected_bytes - offset) > CC_COPY_CHUNK_BYTES
280
+ ? CC_COPY_CHUNK_BYTES
281
+ : (expected_bytes - offset));
282
+ ssize_t count = pread(fd, buffer, requested, offset);
283
+ if (count < 0 && errno == EINTR) {
284
+ continue;
285
+ }
286
+ if (count <= 0 || CC_SHA256_Update(&context, buffer, (CC_LONG)count) != 1) {
287
+ goto done;
288
+ }
289
+ offset += count;
290
+ }
291
+ if (CC_SHA256_Final(digest, &context) != 1 || fstat(fd, &after) != 0 ||
292
+ !same_file_stat(&before, &after)) {
293
+ goto done;
294
+ }
295
+ sha256_hex(digest, output);
296
+ if (stable_stat != NULL) {
297
+ *stable_stat = after;
298
+ }
299
+ result = 0;
300
+
301
+ done:
302
+ free(buffer);
303
+ return result;
304
+ }
305
+
306
+ static int hash_bytes(const char *bytes, size_t length,
307
+ char output[CC_SHA256_HEX_BYTES + 1]) {
308
+ unsigned char digest[CC_SHA256_DIGEST_LENGTH];
309
+ if (CC_SHA256(bytes, (CC_LONG)length, digest) == NULL) {
310
+ return -1;
311
+ }
312
+ sha256_hex(digest, output);
313
+ return 0;
314
+ }
315
+
316
+ static int canonical_fixed_path(const char *path, char resolved[PATH_MAX]) {
317
+ char *result = realpath(path, resolved);
318
+ return result != NULL && strcmp(path, resolved) == 0 ? 0 : -1;
319
+ }
320
+
321
+ static int root_owned_directory(const char *path, mode_t exact_mode) {
322
+ struct stat value;
323
+ char resolved[PATH_MAX];
324
+ if (lstat(path, &value) != 0 || !S_ISDIR(value.st_mode) ||
325
+ S_ISLNK(value.st_mode) || value.st_uid != 0 || value.st_gid != 0 ||
326
+ (value.st_mode & 07777) != exact_mode ||
327
+ canonical_fixed_path(path, resolved) != 0) {
328
+ return -1;
329
+ }
330
+ return 0;
331
+ }
332
+
333
+ static int validate_fixed_installation(void) {
334
+ struct stat helper;
335
+ struct stat sandbox;
336
+ char resolved[PATH_MAX];
337
+ if (root_owned_directory("/Library", 0755) != 0 ||
338
+ root_owned_directory("/Library/PrivilegedHelperTools", 0755) != 0 ||
339
+ lstat(CC_HELPER_INSTALL_PATH, &helper) != 0 ||
340
+ !S_ISREG(helper.st_mode) || S_ISLNK(helper.st_mode) ||
341
+ helper.st_uid != 0 || helper.st_gid != 0 ||
342
+ (helper.st_mode & 07777) != 04555 || helper.st_nlink != 1 ||
343
+ canonical_fixed_path(CC_HELPER_INSTALL_PATH, resolved) != 0 ||
344
+ lstat(CC_SANDBOX_EXECUTABLE, &sandbox) != 0 ||
345
+ !S_ISREG(sandbox.st_mode) || S_ISLNK(sandbox.st_mode) ||
346
+ sandbox.st_uid != 0 || sandbox.st_gid != 0 ||
347
+ (sandbox.st_mode & 0022) != 0 ||
348
+ canonical_fixed_path(CC_SANDBOX_EXECUTABLE, resolved) != 0) {
349
+ return -1;
350
+ }
351
+ return 0;
352
+ }
353
+
354
+ static int install_snapshot_lock(void) {
355
+ struct stat root_before;
356
+ struct stat root_after;
357
+ struct stat lock;
358
+ int root_fd = -1;
359
+ int lock_fd = -1;
360
+ int created = 0;
361
+ int created_identity = 0;
362
+ int result = CC_EXIT_SECURITY;
363
+
364
+ /* Only the package installer, already running as real root, may create the
365
+ * fixed lock. A setuid invocation by a non-root caller has no install-side
366
+ * effect. Existing trusted locks are opened and verified, never replaced. */
367
+ if (getuid() != 0 || geteuid() != 0 || getgid() != 0 || getegid() != 0) {
368
+ return CC_EXIT_SECURITY;
369
+ }
370
+ if (close_descriptors_from(3) != 0) {
371
+ return CC_EXIT_SECURITY;
372
+ }
373
+ if (root_owned_directory("/Library", 0755) != 0 ||
374
+ root_owned_directory("/Library/Application Support", 0755) != 0 ||
375
+ root_owned_directory("/Library/Application Support/ChainlessChain", 0755) != 0 ||
376
+ root_owned_directory("/Library/Application Support/ChainlessChain/McpLauncher", 0755) != 0 ||
377
+ root_owned_directory(CC_SNAPSHOT_ROOT, 0711) != 0) {
378
+ return CC_EXIT_SECURITY;
379
+ }
380
+ root_fd = open(CC_SNAPSHOT_ROOT,
381
+ O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
382
+ if (root_fd < 0 || fstat(root_fd, &root_before) != 0 ||
383
+ !S_ISDIR(root_before.st_mode) || root_before.st_uid != 0 ||
384
+ root_before.st_gid != 0 || (root_before.st_mode & 07777) != 0711) {
385
+ goto done;
386
+ }
387
+ lock_fd = openat(root_fd, CC_SNAPSHOT_LOCK_NAME,
388
+ O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
389
+ if (lock_fd >= 0) {
390
+ created = 1;
391
+ if (fstat(lock_fd, &lock) != 0) {
392
+ goto done;
393
+ }
394
+ created_identity = 1;
395
+ if (fchown(lock_fd, 0, 0) != 0 || fchmod(lock_fd, 0600) != 0 ||
396
+ fsync(lock_fd) != 0 || fsync(root_fd) != 0) {
397
+ goto done;
398
+ }
399
+ } else {
400
+ if (errno != EEXIST) {
401
+ goto done;
402
+ }
403
+ lock_fd = openat(root_fd, CC_SNAPSHOT_LOCK_NAME,
404
+ O_RDWR | O_NOFOLLOW | O_CLOEXEC);
405
+ if (lock_fd < 0) {
406
+ goto done;
407
+ }
408
+ }
409
+ if (fstat(lock_fd, &lock) != 0 || !S_ISREG(lock.st_mode) ||
410
+ lock.st_uid != 0 || lock.st_gid != 0 ||
411
+ (lock.st_mode & 07777) != 0600 || lock.st_nlink != 1 ||
412
+ fstat(root_fd, &root_after) != 0 ||
413
+ root_before.st_dev != root_after.st_dev ||
414
+ root_before.st_ino != root_after.st_ino) {
415
+ goto done;
416
+ }
417
+ result = 0;
418
+
419
+ done:
420
+ if (result != 0 && created && created_identity && lock_fd >= 0) {
421
+ struct stat path_lock;
422
+ if (fstatat(root_fd, CC_SNAPSHOT_LOCK_NAME, &path_lock,
423
+ AT_SYMLINK_NOFOLLOW) == 0 &&
424
+ path_lock.st_dev == lock.st_dev && path_lock.st_ino == lock.st_ino) {
425
+ (void)unlinkat(root_fd, CC_SNAPSHOT_LOCK_NAME, 0);
426
+ (void)fsync(root_fd);
427
+ }
428
+ }
429
+ if (lock_fd >= 0) close(lock_fd);
430
+ if (root_fd >= 0) close(root_fd);
431
+ return result;
432
+ }
433
+
434
+ static int validate_snapshot_root(int *root_fd, int *lock_fd) {
435
+ struct stat root;
436
+ struct stat lock;
437
+ int opened_root = -1;
438
+ int opened_lock = -1;
439
+ char resolved[PATH_MAX];
440
+
441
+ if (root_owned_directory("/Library/Application Support", 0755) != 0 ||
442
+ root_owned_directory("/Library/Application Support/ChainlessChain", 0755) != 0 ||
443
+ root_owned_directory("/Library/Application Support/ChainlessChain/McpLauncher", 0755) != 0 ||
444
+ root_owned_directory(CC_SNAPSHOT_ROOT, 0711) != 0 ||
445
+ canonical_fixed_path(CC_SNAPSHOT_ROOT, resolved) != 0) {
446
+ return -1;
447
+ }
448
+ opened_root = open(CC_SNAPSHOT_ROOT, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
449
+ if (opened_root < 0 || fstat(opened_root, &root) != 0 ||
450
+ !S_ISDIR(root.st_mode) || root.st_uid != 0 || root.st_gid != 0 ||
451
+ (root.st_mode & 07777) != 0711) {
452
+ goto failure;
453
+ }
454
+ opened_lock = openat(opened_root, CC_SNAPSHOT_LOCK_NAME,
455
+ O_RDWR | O_NOFOLLOW | O_CLOEXEC);
456
+ if (opened_lock < 0 || fstat(opened_lock, &lock) != 0 ||
457
+ !S_ISREG(lock.st_mode) || lock.st_uid != 0 || lock.st_gid != 0 ||
458
+ (lock.st_mode & 07777) != 0600 || lock.st_nlink != 1 ||
459
+ flock(opened_lock, LOCK_EX | LOCK_NB) != 0) {
460
+ goto failure;
461
+ }
462
+ *root_fd = opened_root;
463
+ *lock_fd = opened_lock;
464
+ return 0;
465
+
466
+ failure:
467
+ if (opened_lock >= 0) {
468
+ close(opened_lock);
469
+ }
470
+ if (opened_root >= 0) {
471
+ close(opened_root);
472
+ }
473
+ return -1;
474
+ }
475
+
476
+ static int secure_snapshot_entry(int directory_fd, const char *name,
477
+ mode_t final_mode, mode_t partial_mode,
478
+ int strict, int *present) {
479
+ struct stat value;
480
+ mode_t mode;
481
+ if (fstatat(directory_fd, name, &value, AT_SYMLINK_NOFOLLOW) != 0) {
482
+ if (errno == ENOENT) {
483
+ *present = 0;
484
+ return 0;
485
+ }
486
+ return -1;
487
+ }
488
+ *present = 1;
489
+ mode = value.st_mode & 07777;
490
+ if (!S_ISREG(value.st_mode) || S_ISLNK(value.st_mode) || value.st_uid != 0 ||
491
+ value.st_gid != 0 || value.st_nlink != 1 ||
492
+ (strict ? mode != final_mode
493
+ : (mode != final_mode && mode != partial_mode))) {
494
+ return -1;
495
+ }
496
+ return 0;
497
+ }
498
+
499
+ static int snapshot_directory_members_are_fixed(int directory_fd) {
500
+ int scan_fd = dup(directory_fd);
501
+ DIR *directory = NULL;
502
+ struct dirent *entry;
503
+ int result = -1;
504
+ if (scan_fd < 0 || (directory = fdopendir(scan_fd)) == NULL) {
505
+ if (scan_fd >= 0) close(scan_fd);
506
+ return -1;
507
+ }
508
+ while ((entry = readdir(directory)) != NULL) {
509
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 ||
510
+ strcmp(entry->d_name, "node") == 0 ||
511
+ strcmp(entry->d_name, "entry") == 0) {
512
+ continue;
513
+ }
514
+ goto done;
515
+ }
516
+ result = 0;
517
+
518
+ done:
519
+ closedir(directory);
520
+ return result;
521
+ }
522
+
523
+ static int cleanup_snapshot_directory(int root_fd, const char *nonce,
524
+ int require_runtime) {
525
+ int directory_fd = -1;
526
+ struct stat directory;
527
+ int runtime_present = 0;
528
+ int entry_present = 0;
529
+ mode_t directory_mode;
530
+ int result = -1;
531
+
532
+ directory_fd = openat(root_fd, nonce,
533
+ O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
534
+ if (directory_fd < 0 || fstat(directory_fd, &directory) != 0 ||
535
+ !S_ISDIR(directory.st_mode) || directory.st_uid != 0 ||
536
+ directory.st_gid != 0) {
537
+ goto done;
538
+ }
539
+ directory_mode = directory.st_mode & 07777;
540
+ if ((require_runtime ? directory_mode != 0711
541
+ : (directory_mode != 0700 && directory_mode != 0711)) ||
542
+ snapshot_directory_members_are_fixed(directory_fd) != 0 ||
543
+ secure_snapshot_entry(directory_fd, "node", 0555, 0500,
544
+ require_runtime, &runtime_present) != 0 ||
545
+ secure_snapshot_entry(directory_fd, "entry", 0400, 0600,
546
+ require_runtime, &entry_present) != 0 ||
547
+ (require_runtime && (!runtime_present || entry_present))) {
548
+ goto done;
549
+ }
550
+ if (entry_present && unlinkat(directory_fd, "entry", 0) != 0) {
551
+ goto done;
552
+ }
553
+ if (runtime_present && unlinkat(directory_fd, "node", 0) != 0) {
554
+ goto done;
555
+ }
556
+ if (fsync(directory_fd) != 0) {
557
+ goto done;
558
+ }
559
+ if (close(directory_fd) != 0) {
560
+ directory_fd = -1;
561
+ goto done;
562
+ }
563
+ directory_fd = -1;
564
+ if (unlinkat(root_fd, nonce, AT_REMOVEDIR) != 0 || fsync(root_fd) != 0) {
565
+ goto done;
566
+ }
567
+ result = 0;
568
+
569
+ done:
570
+ if (directory_fd >= 0) {
571
+ close(directory_fd);
572
+ }
573
+ return result;
574
+ }
575
+
576
+ static int cleanup_stale_snapshots(int root_fd) {
577
+ int scan_fd = dup(root_fd);
578
+ DIR *directory = NULL;
579
+ struct dirent *entry;
580
+ char names[CC_MAX_STALE_SNAPSHOTS][CC_NONCE_HEX_BYTES + 1];
581
+ int seen = 0;
582
+ int index;
583
+ int result = -1;
584
+
585
+ if (scan_fd < 0 || (directory = fdopendir(scan_fd)) == NULL) {
586
+ if (scan_fd >= 0) {
587
+ close(scan_fd);
588
+ }
589
+ return -1;
590
+ }
591
+ while ((entry = readdir(directory)) != NULL) {
592
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 ||
593
+ strcmp(entry->d_name, CC_SNAPSHOT_LOCK_NAME) == 0) {
594
+ continue;
595
+ }
596
+ if (seen >= CC_MAX_STALE_SNAPSHOTS ||
597
+ !is_lower_hex(entry->d_name, CC_NONCE_HEX_BYTES)) {
598
+ goto done;
599
+ }
600
+ memcpy(names[seen], entry->d_name, CC_NONCE_HEX_BYTES + 1);
601
+ seen += 1;
602
+ }
603
+ if (closedir(directory) != 0) {
604
+ directory = NULL;
605
+ return -1;
606
+ }
607
+ directory = NULL;
608
+ for (index = 0; index < seen; index += 1) {
609
+ if (cleanup_snapshot_directory(root_fd, names[index], 0) != 0) return -1;
610
+ }
611
+ result = fsync(root_fd) == 0 ? 0 : -1;
612
+
613
+ done:
614
+ if (directory != NULL) closedir(directory);
615
+ return result;
616
+ }
617
+
618
+ static int validate_caller_lifeline(void) {
619
+ struct stat value;
620
+ struct pollfd descriptor;
621
+ int flags = fcntl(CC_CALLER_LIFELINE_FD, F_GETFL);
622
+ descriptor.fd = CC_CALLER_LIFELINE_FD;
623
+ descriptor.events = POLLIN | POLLHUP | POLLERR;
624
+ descriptor.revents = 0;
625
+ if (flags < 0 || (flags & O_ACCMODE) == O_WRONLY ||
626
+ fstat(CC_CALLER_LIFELINE_FD, &value) != 0 ||
627
+ (!S_ISFIFO(value.st_mode) && !S_ISSOCK(value.st_mode)) ||
628
+ poll(&descriptor, 1, 0) < 0 || descriptor.revents != 0) {
629
+ return -1;
630
+ }
631
+ return 0;
632
+ }
633
+
634
+ static int validate_descriptor_sources(const struct launch_request *request,
635
+ struct source_evidence *evidence) {
636
+ char digest[CC_SHA256_HEX_BYTES + 1];
637
+ struct stat capsule_after;
638
+
639
+ if (hash_open_file(CC_RUNTIME_FD, request->runtime_bytes, digest,
640
+ &evidence->runtime_stat) != 0 ||
641
+ strcmp(digest, request->runtime_sha256) != 0 ||
642
+ hash_open_file(CC_ENTRY_FD, request->entry_bytes, digest,
643
+ &evidence->entry_stat) != 0 ||
644
+ strcmp(digest, request->entry_sha256) != 0 ||
645
+ fstat(CC_CAPSULE_ROOT_FD, &evidence->capsule_stat) != 0 ||
646
+ !S_ISDIR(evidence->capsule_stat.st_mode) ||
647
+ fstat(CC_CAPSULE_ROOT_FD, &capsule_after) != 0 ||
648
+ !same_file_stat(&evidence->capsule_stat, &capsule_after) ||
649
+ validate_caller_lifeline() != 0) {
650
+ return -1;
651
+ }
652
+ return 0;
653
+ }
654
+
655
+ static int parse_launch_request(int argc, char **argv,
656
+ struct launch_request *request) {
657
+ uid_t caller_uid = getuid();
658
+ gid_t caller_gid = getgid();
659
+ if (argc < 11 || strcmp(argv[1], "--launch-v1") != 0 ||
660
+ !is_lower_hex(argv[2], CC_NONCE_HEX_BYTES) ||
661
+ strcmp(argv[3], CC_PROTOCOL_SHA256) != 0 ||
662
+ !is_lower_hex(argv[4], CC_SHA256_HEX_BYTES) ||
663
+ parse_off_t(argv[5], (off_t)CC_MAXIMUM_RUNTIME_BYTES,
664
+ &request->runtime_bytes) != 0 ||
665
+ request->runtime_bytes <= 0 ||
666
+ !is_lower_hex(argv[6], CC_SHA256_HEX_BYTES) ||
667
+ parse_off_t(argv[7], (off_t)CC_MAXIMUM_ENTRY_BYTES,
668
+ &request->entry_bytes) != 0 ||
669
+ parse_uid(argv[8], &request->caller_uid) != 0 ||
670
+ parse_gid(argv[9], &request->caller_gid) != 0 ||
671
+ request->caller_uid != caller_uid || request->caller_gid != caller_gid ||
672
+ !is_lower_hex(argv[10], CC_SHA256_HEX_BYTES) ||
673
+ argc - 11 > CC_MAXIMUM_PASSTHROUGH_ARGS) {
674
+ return -1;
675
+ }
676
+ memcpy(request->nonce, argv[2], CC_NONCE_HEX_BYTES + 1);
677
+ memcpy(request->runtime_sha256, argv[4], CC_SHA256_HEX_BYTES + 1);
678
+ memcpy(request->entry_sha256, argv[6], CC_SHA256_HEX_BYTES + 1);
679
+ memcpy(request->policy_sha256, argv[10], CC_SHA256_HEX_BYTES + 1);
680
+ request->passthrough_argc = argc - 11;
681
+ request->passthrough_argv = &argv[11];
682
+ return 0;
683
+ }
684
+
685
+ static int copy_root_file_snapshot(
686
+ int source_fd, off_t expected_bytes, const char *expected_sha256,
687
+ const struct stat *expected_source_stat, int directory_fd,
688
+ const char *snapshot_name, mode_t partial_mode, mode_t final_mode,
689
+ int unlink_after_reopen, int *reader_result,
690
+ struct stat *snapshot_result) {
691
+ unsigned char *buffer = NULL;
692
+ int writer_fd = -1;
693
+ int reader_fd = -1;
694
+ int reader_flags;
695
+ struct stat source_before;
696
+ struct stat source_after;
697
+ struct stat writer_stat;
698
+ struct stat reader_stat;
699
+ struct stat unlinked_stat;
700
+ char digest[CC_SHA256_HEX_BYTES + 1];
701
+ off_t offset = 0;
702
+ int result = -1;
703
+
704
+ if (fstat(source_fd, &source_before) != 0 ||
705
+ !same_file_stat(&source_before, expected_source_stat)) {
706
+ goto done;
707
+ }
708
+ writer_fd = openat(directory_fd, snapshot_name,
709
+ O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC,
710
+ partial_mode);
711
+ if (writer_fd < 0 || fchown(writer_fd, 0, 0) != 0 ||
712
+ fchmod(writer_fd, partial_mode) != 0) {
713
+ goto done;
714
+ }
715
+ buffer = (unsigned char *)malloc(CC_COPY_CHUNK_BYTES);
716
+ if (buffer == NULL) {
717
+ goto done;
718
+ }
719
+ while (offset < expected_bytes) {
720
+ size_t requested = (size_t)((expected_bytes - offset) > CC_COPY_CHUNK_BYTES
721
+ ? CC_COPY_CHUNK_BYTES
722
+ : (expected_bytes - offset));
723
+ ssize_t count = pread(source_fd, buffer, requested, offset);
724
+ size_t written = 0;
725
+ if (count < 0 && errno == EINTR) {
726
+ continue;
727
+ }
728
+ if (count <= 0) {
729
+ goto done;
730
+ }
731
+ while (written < (size_t)count) {
732
+ ssize_t current = write(writer_fd, buffer + written,
733
+ (size_t)count - written);
734
+ if (current < 0 && errno == EINTR) {
735
+ continue;
736
+ }
737
+ if (current <= 0) {
738
+ goto done;
739
+ }
740
+ written += (size_t)current;
741
+ }
742
+ offset += count;
743
+ }
744
+ if (fstat(source_fd, &source_after) != 0 ||
745
+ !same_file_stat(&source_before, &source_after) ||
746
+ fsync(writer_fd) != 0 || fchmod(writer_fd, final_mode) != 0 ||
747
+ fsync(writer_fd) != 0 || fstat(writer_fd, &writer_stat) != 0 ||
748
+ !S_ISREG(writer_stat.st_mode) || writer_stat.st_uid != 0 ||
749
+ writer_stat.st_gid != 0 ||
750
+ (writer_stat.st_mode & 07777) != final_mode ||
751
+ writer_stat.st_nlink != 1 || writer_stat.st_size != expected_bytes) {
752
+ goto done;
753
+ }
754
+ if (close(writer_fd) != 0) {
755
+ writer_fd = -1;
756
+ goto done;
757
+ }
758
+ writer_fd = -1;
759
+ reader_fd = openat(directory_fd, snapshot_name,
760
+ O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
761
+ reader_flags = reader_fd >= 0 ? fcntl(reader_fd, F_GETFL) : -1;
762
+ if (reader_fd < 0 || reader_flags < 0 ||
763
+ (reader_flags & O_ACCMODE) != O_RDONLY ||
764
+ hash_open_file(reader_fd, expected_bytes, digest, &reader_stat) != 0 ||
765
+ strcmp(digest, expected_sha256) != 0 ||
766
+ !same_file_stat(&writer_stat, &reader_stat) ||
767
+ reader_stat.st_uid != 0 || reader_stat.st_gid != 0 ||
768
+ (reader_stat.st_mode & 07777) != final_mode ||
769
+ reader_stat.st_nlink != 1 || fsync(directory_fd) != 0) {
770
+ goto done;
771
+ }
772
+ if (unlink_after_reopen) {
773
+ if (unlinkat(directory_fd, snapshot_name, 0) != 0 ||
774
+ fsync(directory_fd) != 0 || fstat(reader_fd, &unlinked_stat) != 0 ||
775
+ !same_snapshot_identity(&reader_stat, &unlinked_stat) ||
776
+ unlinked_stat.st_nlink != 0) {
777
+ goto done;
778
+ }
779
+ reader_stat = unlinked_stat;
780
+ }
781
+ if (snapshot_result != NULL) *snapshot_result = reader_stat;
782
+ if (reader_result != NULL) {
783
+ *reader_result = reader_fd;
784
+ reader_fd = -1;
785
+ }
786
+ result = 0;
787
+
788
+ done:
789
+ free(buffer);
790
+ if (reader_fd >= 0) {
791
+ close(reader_fd);
792
+ }
793
+ if (writer_fd >= 0) {
794
+ close(writer_fd);
795
+ }
796
+ return result;
797
+ }
798
+
799
+ static int create_root_snapshots(int root_fd,
800
+ const struct launch_request *request,
801
+ struct source_evidence *evidence,
802
+ char snapshot_path[PATH_MAX],
803
+ int *entry_snapshot_fd) {
804
+ int directory_fd = -1;
805
+ int entry_reader_fd = -1;
806
+ struct stat directory_stat;
807
+ int result = -1;
808
+
809
+ if (mkdirat(root_fd, request->nonce, 0700) != 0) return -1;
810
+ directory_fd = openat(root_fd, request->nonce,
811
+ O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
812
+ if (directory_fd < 0 || fstat(directory_fd, &directory_stat) != 0 ||
813
+ !S_ISDIR(directory_stat.st_mode) || directory_stat.st_uid != 0 ||
814
+ directory_stat.st_gid != 0 ||
815
+ (directory_stat.st_mode & 07777) != 0700 ||
816
+ fchown(directory_fd, 0, 0) != 0 || fchmod(directory_fd, 0700) != 0 ||
817
+ copy_root_file_snapshot(
818
+ CC_RUNTIME_FD, request->runtime_bytes, request->runtime_sha256,
819
+ &evidence->runtime_stat, directory_fd, "node", 0500, 0555, 0, NULL,
820
+ NULL) != 0 ||
821
+ copy_root_file_snapshot(
822
+ CC_ENTRY_FD, request->entry_bytes, request->entry_sha256,
823
+ &evidence->entry_stat, directory_fd, "entry", 0600, 0400, 1,
824
+ &entry_reader_fd, &evidence->entry_snapshot_stat) != 0 ||
825
+ fchmod(directory_fd, 0711) != 0 || fsync(directory_fd) != 0 ||
826
+ fsync(root_fd) != 0 ||
827
+ snprintf(snapshot_path, PATH_MAX, "%s/%s/node", CC_SNAPSHOT_ROOT,
828
+ request->nonce) >= PATH_MAX) {
829
+ goto done;
830
+ }
831
+ *entry_snapshot_fd = entry_reader_fd;
832
+ entry_reader_fd = -1;
833
+ result = 0;
834
+
835
+ done:
836
+ if (entry_reader_fd >= 0) close(entry_reader_fd);
837
+ if (directory_fd >= 0) close(directory_fd);
838
+ if (result != 0) {
839
+ (void)cleanup_snapshot_directory(root_fd, request->nonce, 0);
840
+ }
841
+ return result;
842
+ }
843
+
844
+ static int safe_profile_literal(const char *source, char *output,
845
+ size_t capacity) {
846
+ size_t used = 0;
847
+ const unsigned char *cursor = (const unsigned char *)source;
848
+ while (*cursor != '\0') {
849
+ unsigned char value = *cursor++;
850
+ if (value < 0x20 || value == 0x7f) {
851
+ return -1;
852
+ }
853
+ if (value == '\\' || value == '"') {
854
+ if (used + 2 >= capacity) {
855
+ return -1;
856
+ }
857
+ output[used++] = '\\';
858
+ } else if (used + 1 >= capacity) {
859
+ return -1;
860
+ }
861
+ output[used++] = (char)value;
862
+ }
863
+ output[used] = '\0';
864
+ return 0;
865
+ }
866
+
867
+ static int build_fixed_profile(const char *snapshot_path,
868
+ const char *capsule_path, char **result) {
869
+ char escaped_snapshot[PATH_MAX * 2];
870
+ char escaped_capsule[PATH_MAX * 2];
871
+ char *profile = NULL;
872
+ int length;
873
+ static const char format[] =
874
+ "(version 1)\n"
875
+ "(deny default)\n"
876
+ "(import \"system.sb\")\n"
877
+ "(allow signal (target self))\n"
878
+ "(allow sysctl-read)\n"
879
+ "(allow mach-lookup)\n"
880
+ "(deny network*)\n"
881
+ "(deny process-fork)\n"
882
+ "(allow process-exec (literal \"%s\"))\n"
883
+ "(allow file-read* (literal \"%s\"))\n"
884
+ "(allow file-read* (subpath \"/bin\"))\n"
885
+ "(allow file-read* (subpath \"/usr/bin\"))\n"
886
+ "(allow file-read* (subpath \"/usr/lib\"))\n"
887
+ "(allow file-read* (subpath \"/usr/libexec\"))\n"
888
+ "(allow file-read* (subpath \"/System/Library\"))\n"
889
+ "(allow file-read* (subpath \"/Library/Frameworks\"))\n"
890
+ "(allow file-read* (subpath \"/usr/local/lib\"))\n"
891
+ "(allow file-read* file-write* (subpath \"%s\"))\n"
892
+ "(allow file-read* file-write* (literal \"/dev/null\") "
893
+ "(literal \"/dev/stdin\") (literal \"/dev/stdout\") "
894
+ "(literal \"/dev/stderr\"))\n"
895
+ "(allow file-read* (literal \"/dev/urandom\"))\n"
896
+ "(allow file-read* (literal \"/etc/passwd\"))";
897
+
898
+ if (safe_profile_literal(snapshot_path, escaped_snapshot,
899
+ sizeof(escaped_snapshot)) != 0 ||
900
+ safe_profile_literal(capsule_path, escaped_capsule,
901
+ sizeof(escaped_capsule)) != 0) {
902
+ return -1;
903
+ }
904
+ profile = (char *)malloc(CC_PROFILE_CAPACITY);
905
+ if (profile == NULL) {
906
+ return -1;
907
+ }
908
+ length = snprintf(profile, CC_PROFILE_CAPACITY, format, escaped_snapshot,
909
+ escaped_snapshot, escaped_capsule);
910
+ if (length < 0 || length >= CC_PROFILE_CAPACITY) {
911
+ free(profile);
912
+ return -1;
913
+ }
914
+ *result = profile;
915
+ return 0;
916
+ }
917
+
918
+ static int unsafe_environment_key(const char *entry) {
919
+ static const char *exact[] = {
920
+ "CLASSPATH", "DOTNET_ADDITIONAL_DEPS", "DOTNET_ROOT",
921
+ "JAVA_TOOL_OPTIONS", "LD_AUDIT", "LD_LIBRARY_PATH", "LD_PRELOAD",
922
+ "NODE_OPTIONS", "NODE_PATH", "NPM_CONFIG_NODE_OPTIONS", "OPENSSL_CONF",
923
+ "OPENSSL_CONF_INCLUDE", "OPENSSL_ENGINES", "OPENSSL_MODULES",
924
+ "PERL5LIB", "PERL5OPT",
925
+ "PYTHONHOME", "PYTHONPATH", "RUBYLIB", "RUBYOPT", "_JAVA_OPTIONS",
926
+ NULL};
927
+ const char *equals = strchr(entry, '=');
928
+ size_t key_length;
929
+ size_t index;
930
+ if (equals == NULL || equals == entry) {
931
+ return 1;
932
+ }
933
+ key_length = (size_t)(equals - entry);
934
+ if ((key_length >= 5 && strncasecmp(entry, "DYLD_", 5) == 0) ||
935
+ (key_length >= 8 && strncasecmp(entry, "COMPLUS_", 8) == 0) ||
936
+ (key_length >= 8 && strncasecmp(entry, "CORECLR_", 8) == 0)) {
937
+ return 1;
938
+ }
939
+ for (index = 0; exact[index] != NULL; index += 1) {
940
+ if (strlen(exact[index]) == key_length &&
941
+ strncasecmp(entry, exact[index], key_length) == 0) {
942
+ return 1;
943
+ }
944
+ }
945
+ return 0;
946
+ }
947
+
948
+ static char **sanitized_environment(void) {
949
+ size_t count = 0;
950
+ size_t kept = 0;
951
+ char **result;
952
+ while (environ[count] != NULL) {
953
+ if (count >= 4096) {
954
+ return NULL;
955
+ }
956
+ count += 1;
957
+ }
958
+ result = (char **)calloc(count + 1, sizeof(char *));
959
+ if (result == NULL) {
960
+ return NULL;
961
+ }
962
+ for (count = 0; environ[count] != NULL; count += 1) {
963
+ if (!unsafe_environment_key(environ[count])) {
964
+ result[kept++] = environ[count];
965
+ }
966
+ }
967
+ result[kept] = NULL;
968
+ return result;
969
+ }
970
+
971
+ static int validate_capsule_descriptor(const struct launch_request *request,
972
+ const struct source_evidence *evidence,
973
+ char capsule_path[PATH_MAX]) {
974
+ struct stat current;
975
+ struct stat named;
976
+ char resolved[PATH_MAX];
977
+ if (fstat(CC_CAPSULE_ROOT_FD, &current) != 0 ||
978
+ !same_file_stat(&current, &evidence->capsule_stat) ||
979
+ !S_ISDIR(current.st_mode) || current.st_uid != request->caller_uid ||
980
+ (current.st_mode & 0022) != 0 ||
981
+ fcntl(CC_CAPSULE_ROOT_FD, F_GETPATH, capsule_path) != 0 ||
982
+ capsule_path[0] != '/' || realpath(capsule_path, resolved) == NULL ||
983
+ strcmp(capsule_path, resolved) != 0 || lstat(capsule_path, &named) != 0 ||
984
+ S_ISLNK(named.st_mode) || !same_file_stat(&current, &named)) {
985
+ return -1;
986
+ }
987
+ return 0;
988
+ }
989
+
990
+ static int clear_close_on_exec(int fd) {
991
+ int flags = fcntl(fd, F_GETFD);
992
+ return flags >= 0 && fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC) == 0 ? 0 : -1;
993
+ }
994
+
995
+ static int install_fixed_pipe_fd(int source, int destination) {
996
+ if (source != destination && dup2(source, destination) < 0) {
997
+ return -1;
998
+ }
999
+ return clear_close_on_exec(destination);
1000
+ }
1001
+
1002
+ static int install_null_placeholders(void) {
1003
+ int null_fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
1004
+ if (null_fd < 0 ||
1005
+ (null_fd != CC_RUNTIME_FD && dup2(null_fd, CC_RUNTIME_FD) < 0) ||
1006
+ (null_fd != CC_CAPSULE_ROOT_FD &&
1007
+ dup2(null_fd, CC_CAPSULE_ROOT_FD) < 0) ||
1008
+ clear_close_on_exec(CC_RUNTIME_FD) != 0 ||
1009
+ clear_close_on_exec(CC_CAPSULE_ROOT_FD) != 0) {
1010
+ if (null_fd >= 0 && null_fd != CC_RUNTIME_FD &&
1011
+ null_fd != CC_CAPSULE_ROOT_FD) {
1012
+ close(null_fd);
1013
+ }
1014
+ return -1;
1015
+ }
1016
+ if (null_fd != CC_RUNTIME_FD && null_fd != CC_CAPSULE_ROOT_FD) {
1017
+ close(null_fd);
1018
+ }
1019
+ return 0;
1020
+ }
1021
+
1022
+ static int drop_to_caller(uid_t uid, gid_t gid) {
1023
+ if (setgroups(0, NULL) != 0 || setgid(gid) != 0 || setuid(uid) != 0 ||
1024
+ getuid() != uid || geteuid() != uid || getgid() != gid ||
1025
+ getegid() != gid || getgroups(0, NULL) != 0) {
1026
+ return -1;
1027
+ }
1028
+ /* Darwin does not expose getresuid/getresgid. A privileged setuid/setgid
1029
+ * changes all three IDs; attempts to regain root additionally prove no saved
1030
+ * root credential survived. Any unexpected success is fatal in this child. */
1031
+ if (seteuid(0) == 0 || setegid(0) == 0 || getuid() != uid ||
1032
+ geteuid() != uid || getgid() != gid || getegid() != gid) {
1033
+ return -1;
1034
+ }
1035
+ return 0;
1036
+ }
1037
+
1038
+ static int target_exec(const struct launch_request *request,
1039
+ const struct source_evidence *evidence,
1040
+ const char *snapshot_path, int entry_snapshot_fd,
1041
+ int gate_read_fd, int ready_write_fd) {
1042
+ char entry_digest[CC_SHA256_HEX_BYTES + 1];
1043
+ char profile_digest[CC_SHA256_HEX_BYTES + 1];
1044
+ char capsule_path[PATH_MAX];
1045
+ char *profile = NULL;
1046
+ char **safe_env = NULL;
1047
+ char **target_argv = NULL;
1048
+ struct stat entry_stat;
1049
+ int target_argc;
1050
+ int index;
1051
+
1052
+ if (setpgid(0, 0) != 0 ||
1053
+ drop_to_caller(request->caller_uid, request->caller_gid) != 0) {
1054
+ return -1;
1055
+ }
1056
+ /* No user pathname, argv payload, environment, or profile is touched before
1057
+ * the irreversible credential drop above. */
1058
+ if (hash_open_file(entry_snapshot_fd, request->entry_bytes, entry_digest,
1059
+ &entry_stat) != 0 ||
1060
+ !same_file_stat(&entry_stat, &evidence->entry_snapshot_stat) ||
1061
+ entry_stat.st_uid != 0 || entry_stat.st_gid != 0 ||
1062
+ (entry_stat.st_mode & 07777) != 0400 || entry_stat.st_nlink != 0 ||
1063
+ strcmp(entry_digest, request->entry_sha256) != 0 ||
1064
+ validate_capsule_descriptor(request, evidence, capsule_path) != 0 ||
1065
+ fchdir(CC_CAPSULE_ROOT_FD) != 0 ||
1066
+ build_fixed_profile(snapshot_path, capsule_path, &profile) != 0 ||
1067
+ hash_bytes(profile, strlen(profile), profile_digest) != 0 ||
1068
+ strcmp(profile_digest, request->policy_sha256) != 0) {
1069
+ goto failure;
1070
+ }
1071
+ safe_env = sanitized_environment();
1072
+ if (safe_env == NULL) {
1073
+ goto failure;
1074
+ }
1075
+ target_argc = 7 + request->passthrough_argc;
1076
+ target_argv = (char **)calloc((size_t)target_argc + 1, sizeof(char *));
1077
+ if (target_argv == NULL) {
1078
+ goto failure;
1079
+ }
1080
+ target_argv[0] = (char *)CC_SANDBOX_EXECUTABLE;
1081
+ target_argv[1] = (char *)"-p";
1082
+ target_argv[2] = profile;
1083
+ target_argv[3] = (char *)snapshot_path;
1084
+ target_argv[4] = (char *)"-e";
1085
+ target_argv[5] = (char *)CC_GATE_BOOTSTRAP;
1086
+ target_argv[6] = (char *)"--";
1087
+ for (index = 0; index < request->passthrough_argc; index += 1) {
1088
+ const char *argument = request->passthrough_argv[index];
1089
+ if (argument == NULL || strlen(argument) > 65536) {
1090
+ goto failure;
1091
+ }
1092
+ target_argv[7 + index] = request->passthrough_argv[index];
1093
+ }
1094
+ target_argv[target_argc] = NULL;
1095
+
1096
+ if (install_fixed_pipe_fd(entry_snapshot_fd, CC_ENTRY_FD) != 0 ||
1097
+ install_fixed_pipe_fd(gate_read_fd, CC_GATE_FD) != 0 ||
1098
+ install_fixed_pipe_fd(ready_write_fd, CC_READY_FD) != 0 ||
1099
+ clear_close_on_exec(CC_ENTRY_FD) != 0 ||
1100
+ install_null_placeholders() != 0) {
1101
+ goto failure;
1102
+ }
1103
+ if (entry_snapshot_fd != CC_ENTRY_FD) close(entry_snapshot_fd);
1104
+ if (gate_read_fd != CC_GATE_FD) close(gate_read_fd);
1105
+ if (ready_write_fd != CC_READY_FD) close(ready_write_fd);
1106
+ /* fd 3 and fd 5 are harmless /dev/null placeholders across exec so Node
1107
+ * cannot reuse those numbers before the compiled bootstrap closes them. */
1108
+ if (close_descriptors_from(CC_CALLER_LIFELINE_FD) != 0) {
1109
+ goto failure;
1110
+ }
1111
+ execve(CC_SANDBOX_EXECUTABLE, target_argv, safe_env);
1112
+
1113
+ failure:
1114
+ free(target_argv);
1115
+ free(safe_env);
1116
+ free(profile);
1117
+ return -1;
1118
+ }
1119
+
1120
+ static int poll_ready_or_parent(int ready_fd, int control_fd, int lifeline_fd,
1121
+ pid_t target_pid) {
1122
+ struct pollfd fds[3];
1123
+ int remaining = CC_READY_TIMEOUT_MS;
1124
+ fds[0].fd = ready_fd;
1125
+ fds[0].events = POLLIN | POLLHUP | POLLERR;
1126
+ fds[1].fd = control_fd;
1127
+ fds[1].events = POLLIN | POLLHUP | POLLERR;
1128
+ fds[2].fd = lifeline_fd;
1129
+ fds[2].events = POLLIN | POLLHUP | POLLERR;
1130
+ while (remaining > 0) {
1131
+ int slice = remaining > 250 ? 250 : remaining;
1132
+ int value = poll(fds, 3, slice);
1133
+ if (value < 0 && errno == EINTR) {
1134
+ continue;
1135
+ }
1136
+ if (value < 0) {
1137
+ return -1;
1138
+ }
1139
+ remaining -= slice;
1140
+ if ((fds[2].revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)) != 0 ||
1141
+ (fds[1].revents & (POLLHUP | POLLERR | POLLNVAL)) != 0) {
1142
+ return -1;
1143
+ }
1144
+ if ((fds[1].revents & POLLIN) != 0) {
1145
+ unsigned char signal_number;
1146
+ ssize_t count = read(control_fd, &signal_number, 1);
1147
+ if (count != 1) {
1148
+ return -1;
1149
+ }
1150
+ (void)kill(-target_pid, (int)signal_number);
1151
+ return -1;
1152
+ }
1153
+ if ((fds[0].revents & POLLIN) != 0) {
1154
+ unsigned char ready;
1155
+ return read(ready_fd, &ready, 1) == 1 && ready == 'R' ? 0 : -1;
1156
+ }
1157
+ if ((fds[0].revents & (POLLHUP | POLLERR)) != 0) {
1158
+ return -1;
1159
+ }
1160
+ }
1161
+ return -1;
1162
+ }
1163
+
1164
+ static int translate_wait_status(int status) {
1165
+ if (WIFEXITED(status)) {
1166
+ return WEXITSTATUS(status);
1167
+ }
1168
+ if (WIFSIGNALED(status)) {
1169
+ return 128 + WTERMSIG(status);
1170
+ }
1171
+ return CC_EXIT_INTERNAL;
1172
+ }
1173
+
1174
+ static void signal_target_tree(pid_t target_pid, int signal_number) {
1175
+ if (target_pid <= 0) return;
1176
+ (void)kill(-target_pid, signal_number);
1177
+ (void)kill(target_pid, signal_number);
1178
+ }
1179
+
1180
+ static int supervise_target(pid_t target_pid, int control_fd,
1181
+ int lifeline_fd) {
1182
+ struct pollfd descriptors[2];
1183
+ int status;
1184
+ descriptors[0].fd = control_fd;
1185
+ descriptors[0].events = POLLIN | POLLHUP | POLLERR;
1186
+ descriptors[1].fd = lifeline_fd;
1187
+ descriptors[1].events = POLLIN | POLLHUP | POLLERR;
1188
+ for (;;) {
1189
+ pid_t waited = waitpid(target_pid, &status, WNOHANG);
1190
+ if (waited == target_pid) {
1191
+ return translate_wait_status(status);
1192
+ }
1193
+ if (waited < 0 && errno != EINTR) {
1194
+ signal_target_tree(target_pid, SIGKILL);
1195
+ return CC_EXIT_INTERNAL;
1196
+ }
1197
+ if (poll(descriptors, 2, 100) < 0 && errno != EINTR) {
1198
+ signal_target_tree(target_pid, SIGKILL);
1199
+ (void)waitpid(target_pid, &status, 0);
1200
+ return CC_EXIT_INTERNAL;
1201
+ }
1202
+ if ((descriptors[1].revents &
1203
+ (POLLIN | POLLHUP | POLLERR | POLLNVAL)) != 0 ||
1204
+ (descriptors[0].revents & (POLLHUP | POLLERR | POLLNVAL)) != 0) {
1205
+ signal_target_tree(target_pid, SIGKILL);
1206
+ (void)waitpid(target_pid, &status, 0);
1207
+ return CC_EXIT_SECURITY;
1208
+ }
1209
+ if ((descriptors[0].revents & POLLIN) != 0) {
1210
+ unsigned char signal_number;
1211
+ ssize_t count = read(control_fd, &signal_number, 1);
1212
+ if (count != 1) {
1213
+ signal_target_tree(target_pid, SIGKILL);
1214
+ } else {
1215
+ signal_target_tree(target_pid, (int)signal_number);
1216
+ }
1217
+ }
1218
+ descriptors[0].revents = 0;
1219
+ descriptors[1].revents = 0;
1220
+ }
1221
+ }
1222
+
1223
+ static int abort_channels_quiet(int control_fd, int lifeline_fd) {
1224
+ struct pollfd descriptors[2];
1225
+ descriptors[0].fd = control_fd;
1226
+ descriptors[0].events = POLLIN | POLLHUP | POLLERR;
1227
+ descriptors[0].revents = 0;
1228
+ descriptors[1].fd = lifeline_fd;
1229
+ descriptors[1].events = POLLIN | POLLHUP | POLLERR;
1230
+ descriptors[1].revents = 0;
1231
+ return poll(descriptors, 2, 0) == 0 ? 0 : -1;
1232
+ }
1233
+
1234
+ static int watchdog_run(int root_fd, int lock_fd, int control_fd,
1235
+ int lifeline_fd, const struct launch_request *request,
1236
+ struct source_evidence *evidence) {
1237
+ char snapshot_path[PATH_MAX];
1238
+ int entry_snapshot_fd = -1;
1239
+ int gate_pipe[2] = {-1, -1};
1240
+ int ready_pipe[2] = {-1, -1};
1241
+ pid_t target_pid = -1;
1242
+ int status = CC_EXIT_SECURITY;
1243
+ int target_status;
1244
+
1245
+ (void)lock_fd;
1246
+ if (setgid(0) != 0 || setuid(0) != 0 || getuid() != 0 || geteuid() != 0 ||
1247
+ getgid() != 0 || getegid() != 0 || cleanup_stale_snapshots(root_fd) != 0 ||
1248
+ abort_channels_quiet(control_fd, lifeline_fd) != 0 ||
1249
+ create_root_snapshots(root_fd, request, evidence, snapshot_path,
1250
+ &entry_snapshot_fd) != 0 ||
1251
+ abort_channels_quiet(control_fd, lifeline_fd) != 0 ||
1252
+ pipe(gate_pipe) != 0 || pipe(ready_pipe) != 0) {
1253
+ goto done;
1254
+ }
1255
+ close(CC_RUNTIME_FD);
1256
+ close(CC_ENTRY_FD);
1257
+ target_pid = fork();
1258
+ if (target_pid < 0) {
1259
+ goto done;
1260
+ }
1261
+ if (target_pid == 0) {
1262
+ close(gate_pipe[1]);
1263
+ close(ready_pipe[0]);
1264
+ close(control_fd);
1265
+ close(lifeline_fd);
1266
+ close(root_fd);
1267
+ close(lock_fd);
1268
+ if (target_exec(request, evidence, snapshot_path, entry_snapshot_fd,
1269
+ gate_pipe[0], ready_pipe[1]) != 0) {
1270
+ _exit(CC_EXIT_SECURITY);
1271
+ }
1272
+ _exit(CC_EXIT_INTERNAL);
1273
+ }
1274
+ (void)setpgid(target_pid, target_pid);
1275
+ close(CC_CAPSULE_ROOT_FD);
1276
+ close(entry_snapshot_fd);
1277
+ entry_snapshot_fd = -1;
1278
+ close(gate_pipe[0]);
1279
+ gate_pipe[0] = -1;
1280
+ close(ready_pipe[1]);
1281
+ ready_pipe[1] = -1;
1282
+ if (poll_ready_or_parent(ready_pipe[0], control_fd, lifeline_fd,
1283
+ target_pid) != 0) {
1284
+ goto kill_target;
1285
+ }
1286
+ close(ready_pipe[0]);
1287
+ ready_pipe[0] = -1;
1288
+
1289
+ /* READY can only be emitted by the exact compiled Node bootstrap. Keep the
1290
+ * entry blocked until the protected runtime pathname is gone durably. */
1291
+ if (cleanup_snapshot_directory(root_fd, request->nonce, 1) != 0 ||
1292
+ write(gate_pipe[1], "G", 1) != 1) {
1293
+ goto kill_target;
1294
+ }
1295
+ close(gate_pipe[1]);
1296
+ gate_pipe[1] = -1;
1297
+ status = supervise_target(target_pid, control_fd, lifeline_fd);
1298
+ target_pid = -1;
1299
+ goto done;
1300
+
1301
+ kill_target:
1302
+ if (gate_pipe[1] >= 0) {
1303
+ close(gate_pipe[1]);
1304
+ gate_pipe[1] = -1;
1305
+ }
1306
+ signal_target_tree(target_pid, SIGKILL);
1307
+ (void)waitpid(target_pid, &target_status, 0);
1308
+ target_pid = -1;
1309
+ status = CC_EXIT_SECURITY;
1310
+
1311
+ done:
1312
+ if (target_pid > 0) {
1313
+ signal_target_tree(target_pid, SIGKILL);
1314
+ (void)waitpid(target_pid, &target_status, 0);
1315
+ }
1316
+ if (gate_pipe[0] >= 0) close(gate_pipe[0]);
1317
+ if (gate_pipe[1] >= 0) close(gate_pipe[1]);
1318
+ if (ready_pipe[0] >= 0) close(ready_pipe[0]);
1319
+ if (ready_pipe[1] >= 0) close(ready_pipe[1]);
1320
+ if (entry_snapshot_fd >= 0) close(entry_snapshot_fd);
1321
+ /* Exact cleanup is safe even when creation failed before the directory
1322
+ * existed. A cleanup failure never releases entry bytes. */
1323
+ (void)cleanup_snapshot_directory(root_fd, request->nonce, 0);
1324
+ return status;
1325
+ }
1326
+
1327
+ static int probe(const char *nonce) {
1328
+ char self_digest[CC_SHA256_HEX_BYTES + 1];
1329
+ struct stat self_stat;
1330
+ int self_fd = -1;
1331
+ int root_fd = -1;
1332
+ int lock_fd = -1;
1333
+ if (!is_lower_hex(nonce, CC_NONCE_HEX_BYTES) || geteuid() != 0 ||
1334
+ validate_fixed_installation() != 0 ||
1335
+ validate_snapshot_root(&root_fd, &lock_fd) != 0) {
1336
+ return CC_EXIT_SECURITY;
1337
+ }
1338
+ self_fd = open(CC_HELPER_INSTALL_PATH, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
1339
+ if (self_fd < 0 || fstat(self_fd, &self_stat) != 0 ||
1340
+ hash_open_file(self_fd, self_stat.st_size, self_digest, NULL) != 0) {
1341
+ if (self_fd >= 0) close(self_fd);
1342
+ close(root_fd);
1343
+ close(lock_fd);
1344
+ return CC_EXIT_SECURITY;
1345
+ }
1346
+ close(self_fd);
1347
+ close(root_fd);
1348
+ close(lock_fd);
1349
+ printf("{\"schema\":\"chainlesschain.macos-mcp-launcher-probe.v1\","
1350
+ "\"nonce\":\"%s\",\"protocolVersion\":%d,"
1351
+ "\"protocolSha256\":\"%s\",\"sourceSha256\":\"%s\","
1352
+ "\"gateBootstrapSha256\":\"%s\",\"selfSha256\":\"%s\","
1353
+ "\"realUid\":%u,\"effectiveUid\":%u,\"realGid\":%u,"
1354
+ "\"effectiveGid\":%u,\"installedMode\":\"4555\","
1355
+ "\"snapshotRootMode\":\"0711\",\"readyGate\":true,"
1356
+ "\"rootWatchdog\":true,\"staleCleanupBounded\":true,"
1357
+ "\"maximumStaleSnapshots\":%d,\"globalLaunchLock\":true,"
1358
+ "\"callerLifelineFd\":%d,\"callerLifelineWatched\":true,"
1359
+ "\"signalRelayNonblocking\":true,"
1360
+ "\"relayParentCredentialsDropped\":true,"
1361
+ "\"entryRootOwnedAnonymousSnapshot\":true,"
1362
+ "\"entrySourcePrePostStat\":true,"
1363
+ "\"entryWriterClosedBeforeReadonlyReopen\":true,"
1364
+ "\"entryReadonlyIdentityRechecked\":true,"
1365
+ "\"entryUnlinkedAndDirectoryFsyncedBeforeTarget\":true,"
1366
+ "\"targetInheritedEntrySnapshotOnly\":true,"
1367
+ "\"runtimeAndCapsuleSlotsNullBeforeExec\":true,"
1368
+ "\"bootstrapClosesNullAndReadyDescriptors\":true,"
1369
+ "\"processForkExplicitlyDenied\":true}\n",
1370
+ nonce, CC_PROTOCOL_VERSION, CC_PROTOCOL_SHA256,
1371
+ CC_HELPER_SOURCE_SHA256, CC_GATE_BOOTSTRAP_SHA256, self_digest,
1372
+ (unsigned)getuid(), (unsigned)geteuid(), (unsigned)getgid(),
1373
+ (unsigned)getegid(), CC_MAX_STALE_SNAPSHOTS,
1374
+ CC_CALLER_LIFELINE_FD);
1375
+ return fflush(stdout) == 0 ? 0 : CC_EXIT_INTERNAL;
1376
+ }
1377
+
1378
+ static int run_launch(int argc, char **argv) {
1379
+ struct launch_request request;
1380
+ struct source_evidence evidence;
1381
+ int root_fd = -1;
1382
+ int lock_fd = -1;
1383
+ int control_pipe[2] = {-1, -1};
1384
+ pid_t watchdog_pid;
1385
+ int watchdog_status;
1386
+ int control_flags;
1387
+ struct sigaction action;
1388
+ int result = CC_EXIT_SECURITY;
1389
+
1390
+ (void)close(CC_GATE_FD);
1391
+ (void)close(CC_READY_FD);
1392
+ if (close_descriptors_from(CC_CALLER_LIFELINE_FD + 1) != 0) {
1393
+ return CC_EXIT_SECURITY;
1394
+ }
1395
+ if (fcntl(CC_GATE_FD, F_GETFD) >= 0 || errno != EBADF ||
1396
+ fcntl(CC_READY_FD, F_GETFD) >= 0 || errno != EBADF) {
1397
+ return CC_EXIT_SECURITY;
1398
+ }
1399
+ memset(&request, 0, sizeof(request));
1400
+ memset(&evidence, 0, sizeof(evidence));
1401
+ if (geteuid() != 0 || getuid() == 0 || getgid() == 0 ||
1402
+ parse_launch_request(argc, argv, &request) != 0 ||
1403
+ validate_fixed_installation() != 0 ||
1404
+ validate_descriptor_sources(&request, &evidence) != 0 ||
1405
+ validate_snapshot_root(&root_fd, &lock_fd) != 0 || pipe(control_pipe) != 0) {
1406
+ goto done;
1407
+ }
1408
+ control_flags = fcntl(control_pipe[1], F_GETFL);
1409
+ if (control_flags < 0 ||
1410
+ fcntl(control_pipe[1], F_SETFL, control_flags | O_NONBLOCK) != 0) {
1411
+ goto done;
1412
+ }
1413
+ watchdog_pid = fork();
1414
+ if (watchdog_pid < 0) {
1415
+ goto done;
1416
+ }
1417
+ if (watchdog_pid == 0) {
1418
+ int watchdog_result;
1419
+ close(control_pipe[1]);
1420
+ watchdog_result =
1421
+ watchdog_run(root_fd, lock_fd, control_pipe[0],
1422
+ CC_CALLER_LIFELINE_FD, &request, &evidence);
1423
+ close(control_pipe[0]);
1424
+ close(root_fd);
1425
+ close(lock_fd);
1426
+ _exit(watchdog_result);
1427
+ }
1428
+ close(control_pipe[0]);
1429
+ control_pipe[0] = -1;
1430
+ close(CC_RUNTIME_FD);
1431
+ close(CC_ENTRY_FD);
1432
+ close(CC_CAPSULE_ROOT_FD);
1433
+ close(CC_CALLER_LIFELINE_FD);
1434
+ close(root_fd);
1435
+ root_fd = -1;
1436
+ close(lock_fd);
1437
+ lock_fd = -1;
1438
+ if (drop_to_caller(request.caller_uid, request.caller_gid) != 0) {
1439
+ close(control_pipe[1]);
1440
+ control_pipe[1] = -1;
1441
+ (void)waitpid(watchdog_pid, &watchdog_status, 0);
1442
+ goto done;
1443
+ }
1444
+ control_write_fd = control_pipe[1];
1445
+ pending_signal = 0;
1446
+ memset(&action, 0, sizeof(action));
1447
+ action.sa_handler = relay_signal;
1448
+ sigemptyset(&action.sa_mask);
1449
+ sigaddset(&action.sa_mask, SIGINT);
1450
+ sigaddset(&action.sa_mask, SIGTERM);
1451
+ sigaddset(&action.sa_mask, SIGHUP);
1452
+ sigaddset(&action.sa_mask, SIGQUIT);
1453
+ action.sa_flags = 0;
1454
+ if (sigaction(SIGINT, &action, NULL) != 0 ||
1455
+ sigaction(SIGTERM, &action, NULL) != 0 ||
1456
+ sigaction(SIGHUP, &action, NULL) != 0 ||
1457
+ sigaction(SIGQUIT, &action, NULL) != 0) {
1458
+ close(control_pipe[1]);
1459
+ control_pipe[1] = -1;
1460
+ (void)waitpid(watchdog_pid, &watchdog_status, 0);
1461
+ goto done;
1462
+ }
1463
+ while (waitpid(watchdog_pid, &watchdog_status, 0) < 0) {
1464
+ if (errno != EINTR) {
1465
+ close(control_pipe[1]);
1466
+ control_pipe[1] = -1;
1467
+ result = CC_EXIT_INTERNAL;
1468
+ goto done;
1469
+ }
1470
+ flush_pending_signal();
1471
+ }
1472
+ result = translate_wait_status(watchdog_status);
1473
+
1474
+ done:
1475
+ control_write_fd = -1;
1476
+ if (control_pipe[0] >= 0) close(control_pipe[0]);
1477
+ if (control_pipe[1] >= 0) close(control_pipe[1]);
1478
+ if (root_fd >= 0) close(root_fd);
1479
+ if (lock_fd >= 0) close(lock_fd);
1480
+ return result;
1481
+ }
1482
+
1483
+ int main(int argc, char **argv) {
1484
+ if (argc == 2 && strcmp(argv[1], "--install-lock-v1") == 0) {
1485
+ return install_snapshot_lock();
1486
+ }
1487
+ if (argc == 3 && strcmp(argv[1], "--probe-v1") == 0) {
1488
+ if (close_descriptors_from(3) != 0) {
1489
+ return CC_EXIT_SECURITY;
1490
+ }
1491
+ return probe(argv[2]);
1492
+ }
1493
+ if (argc >= 2 && strcmp(argv[1], "--launch-v1") == 0) {
1494
+ return run_launch(argc, argv);
1495
+ }
1496
+ return CC_EXIT_USAGE;
1497
+ }