chainlesschain 0.162.183 → 0.162.184

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 (196) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-BapS7lz4.js → AIOps-DzAl5fGx.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CXYdXEUI.js → ActionButton-DAHtMGeC.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-CKR5Lexn.js → Analytics-BHQaexv5.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-DATVUOXB.js → AppLayout-Phz_pD0n.js} +4 -4
  7. package/src/assets/web-panel/assets/{Artifacts-vb_0ZlV1.js → Artifacts-DHt47kCA.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-BI1l9GWt.js → Audit--Y_8FULl.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-BFXbaUEf.js → BackgroundAgents-N1zchH7D.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-DEYBJJ6H.js → Backup-B_BoyN30.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-COdzeO0M.js → BaseInput-Gl4uQHcT.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-oSN6sfM5.js → Chat-Cg-eIyAZ.js} +6 -6
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-BzeUi3JH.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-BezEa4tm.js → Checkbox-D0MGR2wA.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CNEL0Ife.js → Codegen-BdObJEFn.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-Bf2Y2Bn1.js → Col--ghxXT3z.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-BfUlu4Hc.js → Community-BUFeXOqz.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-BN4xWP9e.js → Compact-DhQO2_Mr.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-Bt6BKmEP.js → Compliance-3HoTqKYd.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DKLp8YxO.js → Cowork-B3zmO4VW.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-o7Hhw6ky.js → Cron-CJDrZOcc.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-DaNXWJIw.js → Crosschain-SMl15obd.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-B-Qa2mha.js → DID-CgTrTWrl.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-CMX0Qnxw.js → Dashboard-dXFKaKol.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BLuLyRu_.js → Dropdown-qQHEhCvI.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-CFBAeMij.js → EmailListRenderer-C_ej0MWH.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DWzXQ67V.js → FamilyGuardDashboard-D6ajoE7I.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-DB1QSLgr.js → Federation-Cio2y49h.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-C-OKCToP.js → FormItemContext-ClJz1cYp.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-DMJFtFZf.js → GenericCardRenderer-Dr_8Maca.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-DsGSujOV.js → Git-CevLwp5u.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-B0iS9NEW.js → Governance-CW9YyQUY.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CH004H7c.js → Inference-CQqeAyER.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-Q9MTQ83c.js → KnowledgeGraph-cuGVxHrw.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-B_fyjZK9.js → Logs-DIuHri5d.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-C0o4FtpD.js → MarkdownRenderer-R91Qw5JA.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-BIStF9d1.js → Marketplace-CY1VhFGx.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-pGfal768.js → McpTools-BBAbi1vt.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-xbNqpIqB.js → Memory-BWYGHxJA.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-B5EQGrIK.js → MobileBridge-CPIFkW7A.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-DQyWxCho.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-a_rmfxJH.js → Mtc-Bo3GnQbx.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-BmKuofUr.js → MtcAudit-C8-c4rSW.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-BQbJ-CAN.js → Multisig-ChqMTtoq.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-JtPJKX25.js → NLProgramming-C48TyhvI.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-ZFKznTSG.js → Notes-zogUFWxp.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-B6aHRBBR.js → NotificationSettings-v-K9XpSB.js} +1 -1
  48. package/src/assets/web-panel/assets/OrderTableRenderer-P94JGGNz.js +1 -0
  49. package/src/assets/web-panel/assets/{Organization-BFDTAUgS.js → Organization-Boi1dQfR.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-0FoTfbWH.js → Overflow-DM5YWwgA.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-CWd90etw.js → P2P-C57LKpKK.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-DtCGC-W-.js → PdhVaultBrowser-BFkPqu6Q.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-CvQ7nr7T.js → Permissions-D6fB9YSp.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-rdNPcR-Z.js → PersonalDataHub-Bp7EefEo.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-CmrfiNOs.js → Pipeline-BVln3hZf.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-D7ep1h_t.js → Privacy-Bf8P3nF6.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-C0xKINHS.js → ProjectInit-DM6wA2E2.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-AOmZFp-F.js → ProjectSettings-CGbc5Iho.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-BWHSkXZP.js → Projects-D0p9EOxm.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-Cb7JCTPL.js → Providers-B2kJcxS6.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-BDMehcXC.js → QrScannerModal-Cxu7dXYy.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-BF6_1nvx.js → QuickAsk-DTlrTPms.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-BHfT-A28.js → Recommend-Cm3X-cCj.js} +1 -1
  64. package/src/assets/web-panel/assets/RemoteSession-DNb2kjvt.js +5 -0
  65. package/src/assets/web-panel/assets/{Reputation-CsWHLEcj.js → Reputation-CZUFFjc1.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-D6srnWhm.js → Row-DAPgloxA.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-Xhr-KyVd.js → RssFeed-dsx9S793.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-vIbIJvwL.js → Search-DnnC8kz-.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-Bzi5Aiko.js → Security-VuZknRMK.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-BHtVX52P.js → Services-B3XNX8Vx.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-C0R8B2jf.js → Skeleton-Dl1nVtCj.js} +1 -1
  72. package/src/assets/web-panel/assets/Skills-CY09CZ_W.js +1 -0
  73. package/src/assets/web-panel/assets/{Sla-0B37mxVU.js → Sla-HzU56n1z.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-0V2jE1Bs.js → SpeechSettings-CRle7Jl2.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-CYedRzaj.js → SyncSettings-DWadz7Sl.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-D-R1x8vm.js → Tasks-CNVTDGg8.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-BKoHztGT.js → Templates-BBAgvrlM.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant--17nwVNZ.js → Tenant-BsbbvGN8.js} +1 -1
  79. package/src/assets/web-panel/assets/Terminal-uscpZI4q.js +3 -0
  80. package/src/assets/web-panel/assets/{TimelineRenderer-C5jx4AE3.js → TimelineRenderer-mTyjNE6r.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-DYJKYa9t.js → Tokens-Bz97oBgs.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-DChpWiQ9.js → Trigger-BTRKC140.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DfWG52EA.js → Trust-B4A_KuyX.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-CMugYcyA.js → UkeySign-DES9TmLU.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-BOSmrHAd.js → VideoEditing-ClXs7Qxp.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-D5g3k3Sa.js → Wallet-IzZqFz-7.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-D-7u4Mkc.js → WebAuthn-BtOP04g_.js} +3 -3
  88. package/src/assets/web-panel/assets/{WorkflowEditor-CFzZMSUJ.js → WorkflowEditor-_0cqnopY.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-VbR6l6mc.js → chat-ax7193uU.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BSZn8EDS.js → colors-Bu4WGJah.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-y74obMEq.js → compact-item-BX1dlTW0.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-C326bjex.js → createContext-CktA_Dj7.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-x877iKKS.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-D_INJQT_.js → hasIn-D5wd817l.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CsovTpx9.js → index-5PYYkr8S.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-9PIVuXqo.js → index-A4uBPQCz.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BjcWKg1S.js → index-BAtBNfkr.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CMHqs3Jb.js → index-BLLsUNDc.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-B7SrC-On.js → index-BQtoRyTO.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DUU2606o.js → index-BRfXoRuh.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Iw0FfqUz.js → index-Bad2CYeH.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Dlxh9D8D.js → index-BcGdL3sG.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DuIkfnTF.js → index-C3HmzCLt.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-E_v1ZCTP.js → index-C8DkWyNr.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-4eyH-eC2.js → index-C8tLR2TG.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BnSfnSMP.js → index-CP8ELJBc.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DwHSZLXg.js → index-CUForzsG.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-C2NyllnW.js → index-CYRE0NJA.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Bnua8IX3.js → index-CeVZaJSC.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-KF9Zmqln.js → index-CfzxR61U.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Cr3uuz0C.js → index-Ci3y1ZAv.js} +3 -3
  112. package/src/assets/web-panel/assets/{index-DBkzrpiL.js → index-CjEJjl5C.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-abmn_8Yz.js → index-Cv7bVWJ5.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-qMA4GYdQ.js → index-D-o4Rwjk.js} +1 -1
  115. package/src/assets/web-panel/assets/index-D7pfPouB.js +1 -0
  116. package/src/assets/web-panel/assets/{index-jYaogFE8.js → index-D9op8poE.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-GA80ir_u.js → index-DF1TysIF.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CzE53HiB.js → index-DYo9S5OT.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-gINyI1rh.js → index-D_cycOET.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-FQBsuExX.js → index-Db3CpWTr.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-IZAWt0H8.js → index-DkD1K-fF.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C327pLbw.js → index-DmPJ6T6F.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DdyjrCAu.js → index-DmVUeWUk.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CfLJ42kv.js → index-Dzdr0zDf.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DkH1JvEt.js → index-PBLzwYnu.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DNMxIsDg.js → index-bFCaM7Eq.js} +1 -1
  127. package/src/assets/web-panel/assets/index-e4i-VLBd.js +1 -0
  128. package/src/assets/web-panel/assets/{index-C-ZCaUJc.js → index-h3KJZr95.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-Bfw2hf2s.js → index-iHcPtQ2e.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BJi3UqBt.js → index-jzlzm4VK.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BY8-nCgs.js → index-o14NNnxw.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-nSn2-C4N.js → index-tYPSfGbd.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-4PIeTM--.js → index-ytp2H5Zx.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-DI0Zaf8-.js → initDefaultProps-D2Iut83B.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BZDKSy3Z.js → motion-BuaJVdK9.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-DsIW01Vp.js → move-CaEuM92y.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-CohkDhsf.js → mtc-parser-wuKNBwys.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-DpiN5rzg.js → omit-7a3eklaq.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-qbuqni3y.js → pickAttrs-Dn4z7KvJ.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-mBky6714.js → placementArrow-DVn_qT4-.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-Bas3eQ2K.js → responsiveObserve-Dodz1OMQ.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-CBx_SKoK.js → slide-CxoGskbp.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-BlYguJSV.js → statusUtils-DBh_epQH.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BCYQmRGv.js → styleChecker-RXQ4gG5P.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-Dp5dybLM.js → useFlexGapSupport-1Wxm0HEG.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-DHAMBgA2.js → useFs-CI_ySNxJ.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-W6jsSw76.js → usePersonalDataHub-C6973rZP.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-BkI6aayc.js → vnode-CalynwKr.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-C_IMGueq.js → zoom-CM1cd-Q0.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/agenda.js +74 -1
  152. package/src/commands/background-session.js +11 -1
  153. package/src/commands/batch.js +160 -5
  154. package/src/commands/plugin.js +90 -27
  155. package/src/commands/session.js +31 -1
  156. package/src/commands/team.js +212 -6
  157. package/src/data/changelog.json +13 -0
  158. package/src/harness/jsonl-session-store.js +57 -6
  159. package/src/harness/mcp-client.js +65 -43
  160. package/src/lib/agent-batch.js +4 -1
  161. package/src/lib/agent-team/team-worktree.js +3 -0
  162. package/src/lib/async-hook-supervisor.cjs +82 -10
  163. package/src/lib/async-hook-supervisor.js +50 -4
  164. package/src/lib/collaboration-run-store.js +362 -0
  165. package/src/lib/hook-runner.cjs +98 -17
  166. package/src/lib/hook-runner.js +77 -6
  167. package/src/lib/hook-shell-command.cjs +148 -0
  168. package/src/lib/hooks-v2-runtime.js +188 -77
  169. package/src/lib/jsonl-session-store.js +2 -0
  170. package/src/lib/lsp/__tests__/lsp-client.test.js +18 -0
  171. package/src/lib/lsp/lsp-client.js +12 -1
  172. package/src/lib/plugin-monitor-supervisor.js +53 -16
  173. package/src/lib/plugin-runtime/bin.js +1 -1
  174. package/src/lib/plugin-runtime/install.js +249 -57
  175. package/src/lib/plugin-runtime/mcp.js +21 -2
  176. package/src/lib/plugin-runtime/monitors.js +19 -3
  177. package/src/lib/plugin-runtime/sandbox-policy.js +99 -0
  178. package/src/lib/process-execution-broker/index.js +453 -12
  179. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +1347 -0
  180. package/src/lib/process-execution-broker/linux-generic-bwrap.js +1260 -0
  181. package/src/lib/process-execution-broker/platform-sandbox.js +580 -56
  182. package/src/lib/session-usage.js +202 -2
  183. package/src/lib/stream-retry.js +29 -0
  184. package/src/repl/agent-repl.js +32 -3
  185. package/src/runtime/agent-core.js +32 -4
  186. package/src/runtime/headless-runner.js +37 -0
  187. package/src/runtime/headless-stream.js +34 -2
  188. package/src/assets/web-panel/assets/ChatBubbleRenderer-CI_jT0ru.js +0 -1
  189. package/src/assets/web-panel/assets/MobileProjects-VTc3gT1y.js +0 -1
  190. package/src/assets/web-panel/assets/OrderTableRenderer-DdcsOqEz.js +0 -1
  191. package/src/assets/web-panel/assets/RemoteSession-B6EEf8hh.js +0 -5
  192. package/src/assets/web-panel/assets/Skills-DtT6rWOX.js +0 -1
  193. package/src/assets/web-panel/assets/Terminal-Dnkos7vW.js +0 -3
  194. package/src/assets/web-panel/assets/devWarning-D3Bho_6u.js +0 -1
  195. package/src/assets/web-panel/assets/index-5iAFwYx1.js +0 -1
  196. package/src/assets/web-panel/assets/index-BfnB7LPx.js +0 -1
@@ -0,0 +1,1347 @@
1
+ /**
2
+ * Production resource acquisition for the generic Linux bubblewrap backend.
3
+ *
4
+ * Caller-supplied callbacks never cross this boundary. Every mount, the
5
+ * bubblewrap executable, and the seccomp program is opened by this module and
6
+ * independently re-attested before the final spawn plan is returned.
7
+ */
8
+
9
+ import crypto from "node:crypto";
10
+ import fs from "node:fs";
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+ import { spawnSync as nativeSpawnSync } from "node:child_process";
14
+ import {
15
+ LINUX_GENERIC_BWRAP_BACKEND,
16
+ LINUX_GENERIC_CONTRACT_KIND,
17
+ planLinuxGenericBubblewrap,
18
+ } from "./linux-generic-bwrap.js";
19
+
20
+ const BWRAP_PATH = "/usr/bin/bwrap";
21
+ const MAX_ATTESTED_FILE_BYTES = 256 * 1024 * 1024;
22
+ const MAX_MOUNTINFO_BYTES = 4 * 1024 * 1024;
23
+ const MOUNTINFO_PATH = "/proc/self/mountinfo";
24
+ const MOUNT_TOPOLOGY_VERSION = 1;
25
+ const O_TMPFILE = 0x410000;
26
+ const SYSTEM_DESTINATIONS = [
27
+ "/usr",
28
+ "/bin",
29
+ "/sbin",
30
+ "/lib",
31
+ "/lib32",
32
+ "/lib64",
33
+ ];
34
+ const SYSTEM_ROOTS = new Set(SYSTEM_DESTINATIONS);
35
+ const SYSTEM_SYMLINKS = new Map([
36
+ ["/bin", "usr/bin"],
37
+ ["/sbin", "usr/sbin"],
38
+ ["/lib", "usr/lib"],
39
+ ["/lib32", "usr/lib32"],
40
+ ["/lib64", "usr/lib64"],
41
+ ]);
42
+ const ETC_ALLOWLIST = [
43
+ "/etc/group",
44
+ "/etc/hosts",
45
+ "/etc/ld.so.cache",
46
+ "/etc/nsswitch.conf",
47
+ "/etc/passwd",
48
+ ];
49
+ const FORBIDDEN_EXACT_WORKSPACE_ALIASES = [
50
+ "/",
51
+ "/home",
52
+ "/home/sandbox",
53
+ "/media",
54
+ "/mnt",
55
+ "/opt",
56
+ "/root",
57
+ "/run",
58
+ "/srv",
59
+ "/tmp",
60
+ "/var",
61
+ "/var/tmp",
62
+ ];
63
+ const FORBIDDEN_WORKSPACE_ALIAS_SUBTREES = [
64
+ "/bin",
65
+ "/boot",
66
+ "/dev",
67
+ "/etc",
68
+ "/lib",
69
+ "/lib32",
70
+ "/lib64",
71
+ "/proc",
72
+ "/sbin",
73
+ "/sys",
74
+ "/usr",
75
+ ];
76
+ const OPAQUE_NON_ROOT_FILESYSTEMS = new Set([
77
+ "9p",
78
+ "aufs",
79
+ "ceph",
80
+ "cifs",
81
+ "ecryptfs",
82
+ "glusterfs",
83
+ "nfs",
84
+ "nfs4",
85
+ "overlay",
86
+ "smb3",
87
+ "virtiofs",
88
+ ]);
89
+ const REQUIRED_BWRAP_OPTIONS = [
90
+ "--assert-userns-disabled",
91
+ "--bind-fd",
92
+ "--disable-userns",
93
+ "--file",
94
+ "--perms",
95
+ "--remount-ro",
96
+ "--ro-bind-fd",
97
+ "--seccomp",
98
+ ];
99
+ const SECCOMP_ARCHITECTURES = Object.freeze({
100
+ x64: Object.freeze({
101
+ auditArch: 0xc000003e,
102
+ socketSyscall: 41,
103
+ socketpairSyscall: 53,
104
+ ioUringSetupSyscall: 425,
105
+ x32SyscallBit: 0x40000000,
106
+ }),
107
+ arm64: Object.freeze({
108
+ auditArch: 0xc00000b7,
109
+ socketSyscall: 198,
110
+ socketpairSyscall: 199,
111
+ ioUringSetupSyscall: 425,
112
+ }),
113
+ riscv64: Object.freeze({
114
+ auditArch: 0xc00000f3,
115
+ socketSyscall: 198,
116
+ socketpairSyscall: 199,
117
+ ioUringSetupSyscall: 425,
118
+ }),
119
+ });
120
+
121
+ const DEFAULT_RUNTIME = Object.freeze({
122
+ platform: os.platform(),
123
+ arch: process.arch,
124
+ execPath: process.execPath,
125
+ fs,
126
+ homedir: () => os.homedir(),
127
+ tmpdir: () => os.tmpdir(),
128
+ randomBytes: (size) => crypto.randomBytes(size),
129
+ spawnSync: nativeSpawnSync,
130
+ });
131
+
132
+ function runtimeFor(overrides = {}) {
133
+ return { ...DEFAULT_RUNTIME, ...overrides };
134
+ }
135
+
136
+ function realpath(runtime, value) {
137
+ const implementation =
138
+ runtime.fs.realpathSync?.native || runtime.fs.realpathSync;
139
+ if (typeof implementation !== "function") {
140
+ throw new Error("realpath_unavailable");
141
+ }
142
+ return implementation.call(runtime.fs.realpathSync, value);
143
+ }
144
+
145
+ function fileId(stat) {
146
+ return { dev: String(stat.dev), ino: String(stat.ino) };
147
+ }
148
+
149
+ function identity(realPath, stat) {
150
+ return Object.freeze({
151
+ realPath,
152
+ fileId: Object.freeze(fileId(stat)),
153
+ mode: Number(stat.mode),
154
+ uid: Number(stat.uid),
155
+ gid: Number(stat.gid),
156
+ });
157
+ }
158
+
159
+ function identityMatches(left, right) {
160
+ return (
161
+ left?.realPath === right?.realPath &&
162
+ String(left?.fileId?.dev) === String(right?.fileId?.dev) &&
163
+ String(left?.fileId?.ino) === String(right?.fileId?.ino) &&
164
+ Number(left?.mode) === Number(right?.mode) &&
165
+ Number(left?.uid) === Number(right?.uid) &&
166
+ Number(left?.gid) === Number(right?.gid)
167
+ );
168
+ }
169
+
170
+ function statMatchesIdentity(stat, expected, { directory }) {
171
+ const expectedType = directory ? 0o040000 : 0o100000;
172
+ return (
173
+ String(stat?.dev) === String(expected?.fileId?.dev) &&
174
+ String(stat?.ino) === String(expected?.fileId?.ino) &&
175
+ Number(stat?.mode) === Number(expected?.mode) &&
176
+ Number(stat?.uid) === Number(expected?.uid) &&
177
+ Number(stat?.gid) === Number(expected?.gid) &&
178
+ (Number(stat?.mode) & 0o170000) === expectedType
179
+ );
180
+ }
181
+
182
+ function isWithin(root, target) {
183
+ const relative = path.posix.relative(root, target);
184
+ return (
185
+ relative === "" ||
186
+ (relative !== ".." &&
187
+ !relative.startsWith("../") &&
188
+ !path.posix.isAbsolute(relative))
189
+ );
190
+ }
191
+
192
+ function rootOwned(stat, { directory, executable = false }) {
193
+ const expectedType = directory ? 0o040000 : 0o100000;
194
+ return (
195
+ (Number(stat?.mode) & 0o170000) === expectedType &&
196
+ Number(stat?.uid) === 0 &&
197
+ (Number(stat?.mode) & 0o022) === 0 &&
198
+ (!executable || (Number(stat?.mode) & 0o111) !== 0)
199
+ );
200
+ }
201
+
202
+ function hashOpenFile(runtime, fd, bytes) {
203
+ if (
204
+ !Number.isSafeInteger(bytes) ||
205
+ bytes < 1 ||
206
+ bytes > MAX_ATTESTED_FILE_BYTES
207
+ ) {
208
+ throw new Error("attested_file_size_invalid");
209
+ }
210
+ const digest = crypto.createHash("sha256");
211
+ const buffer = Buffer.allocUnsafe(Math.min(1024 * 1024, bytes));
212
+ let offset = 0;
213
+ while (offset < bytes) {
214
+ const requested = Math.min(buffer.length, bytes - offset);
215
+ const read = runtime.fs.readSync(fd, buffer, 0, requested, offset);
216
+ if (read <= 0) throw new Error("attested_file_read_failed");
217
+ digest.update(buffer.subarray(0, read));
218
+ offset += read;
219
+ }
220
+ return digest.digest("hex");
221
+ }
222
+
223
+ function buildNetworkSeccompFilter(arch) {
224
+ const selected = SECCOMP_ARCHITECTURES[arch];
225
+ if (!selected) {
226
+ throw new Error(`unsupported_seccomp_architecture:${String(arch)}`);
227
+ }
228
+ const instructions = [
229
+ [0x20, 0, 0, 4],
230
+ [0x15, 1, 0, selected.auditArch],
231
+ [0x06, 0, 0, 0x80000000],
232
+ [0x20, 0, 0, 0],
233
+ ];
234
+ if (selected.x32SyscallBit) {
235
+ instructions.push([0x54, 0, 0, ~selected.x32SyscallBit]);
236
+ }
237
+ instructions.push(
238
+ [0x15, 0, 1, selected.socketSyscall],
239
+ [0x06, 0, 0, 0x00050001],
240
+ [0x15, 0, 1, selected.socketpairSyscall],
241
+ [0x06, 0, 0, 0x00050001],
242
+ [0x15, 0, 1, selected.ioUringSetupSyscall],
243
+ [0x06, 0, 0, 0x00050001],
244
+ [0x06, 0, 0, 0x7fff0000],
245
+ );
246
+ const buffer = Buffer.alloc(instructions.length * 8);
247
+ instructions.forEach(([code, jumpTrue, jumpFalse, value], index) => {
248
+ const offset = index * 8;
249
+ buffer.writeUInt16LE(code, offset);
250
+ buffer.writeUInt8(jumpTrue, offset + 2);
251
+ buffer.writeUInt8(jumpFalse, offset + 3);
252
+ buffer.writeUInt32LE(value >>> 0, offset + 4);
253
+ });
254
+ return Object.freeze({
255
+ arch,
256
+ buffer,
257
+ sha256: crypto.createHash("sha256").update(buffer).digest("hex"),
258
+ });
259
+ }
260
+
261
+ function shellQuote(value) {
262
+ return `'${String(value).replaceAll("'", `'\\''`)}'`;
263
+ }
264
+
265
+ function stableJson(value) {
266
+ if (Array.isArray(value)) {
267
+ return `[${value.map((entry) => stableJson(entry)).join(",")}]`;
268
+ }
269
+ if (value && typeof value === "object") {
270
+ return `{${Object.keys(value)
271
+ .sort()
272
+ .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
273
+ .join(",")}}`;
274
+ }
275
+ return JSON.stringify(value);
276
+ }
277
+
278
+ function sha256(value) {
279
+ return crypto.createHash("sha256").update(value).digest("hex");
280
+ }
281
+
282
+ function decodeMountInfoPath(value) {
283
+ if (typeof value !== "string" || !value) {
284
+ throw new Error("mountinfo_path_invalid");
285
+ }
286
+ const decoded = [];
287
+ const escapes = new Map([
288
+ ["040", " "],
289
+ ["011", "\t"],
290
+ ["012", "\n"],
291
+ ["134", "\\"],
292
+ ]);
293
+ for (let index = 0; index < value.length; index += 1) {
294
+ if (value[index] !== "\\") {
295
+ decoded.push(value[index]);
296
+ continue;
297
+ }
298
+ const octal = value.slice(index + 1, index + 4);
299
+ if (!escapes.has(octal)) {
300
+ throw new Error("mountinfo_path_escape_invalid");
301
+ }
302
+ decoded.push(escapes.get(octal));
303
+ index += 3;
304
+ }
305
+ const result = decoded.join("");
306
+ if (
307
+ result.includes("\0") ||
308
+ !path.posix.isAbsolute(result) ||
309
+ path.posix.normalize(result) !== result
310
+ ) {
311
+ throw new Error("mountinfo_path_invalid");
312
+ }
313
+ return result;
314
+ }
315
+
316
+ function encodeMountInfoPath(value) {
317
+ return String(value)
318
+ .replaceAll("\\", "\\134")
319
+ .replaceAll(" ", "\\040")
320
+ .replaceAll("\t", "\\011")
321
+ .replaceAll("\n", "\\012");
322
+ }
323
+
324
+ function parseMountInfo(runtime) {
325
+ const raw = String(runtime.fs.readFileSync(MOUNTINFO_PATH, "utf8"));
326
+ if (
327
+ Buffer.byteLength(raw, "utf8") < 1 ||
328
+ Buffer.byteLength(raw, "utf8") > MAX_MOUNTINFO_BYTES
329
+ ) {
330
+ throw new Error("mountinfo_size_invalid");
331
+ }
332
+ const entries = [];
333
+ for (const line of raw.split("\n")) {
334
+ if (!line) continue;
335
+ const fields = line.split(" ");
336
+ const separator = fields.indexOf("-");
337
+ if (
338
+ separator < 6 ||
339
+ fields.length < separator + 4 ||
340
+ !/^\d+$/.test(fields[0] || "") ||
341
+ !/^\d+$/.test(fields[1] || "") ||
342
+ !/^\d+:\d+$/.test(fields[2] || "")
343
+ ) {
344
+ throw new Error("mountinfo_entry_invalid");
345
+ }
346
+ const root = decodeMountInfoPath(fields[3]);
347
+ const mountPoint = decodeMountInfoPath(fields[4]);
348
+ const fsType = fields[separator + 1];
349
+ if (
350
+ !fsType ||
351
+ fsType.includes("\0") ||
352
+ fields.slice(separator + 1).some((field) => !field)
353
+ ) {
354
+ throw new Error("mountinfo_entry_invalid");
355
+ }
356
+ entries.push(
357
+ Object.freeze({
358
+ mountId: fields[0],
359
+ parentId: fields[1],
360
+ majorMinor: fields[2],
361
+ root,
362
+ mountPoint,
363
+ fsType,
364
+ lineDigest: sha256(line),
365
+ }),
366
+ );
367
+ }
368
+ if (entries.length === 0) throw new Error("mountinfo_empty");
369
+ return entries;
370
+ }
371
+
372
+ function mountDepth(value) {
373
+ return value === "/" ? 0 : value.split("/").filter(Boolean).length;
374
+ }
375
+
376
+ function containingMount(entries, target) {
377
+ const candidates = entries.filter((entry) =>
378
+ isWithin(entry.mountPoint, target),
379
+ );
380
+ if (candidates.length === 0) throw new Error("mountinfo_root_unavailable");
381
+ const deepest = Math.max(
382
+ ...candidates.map((entry) => mountDepth(entry.mountPoint)),
383
+ );
384
+ const matches = candidates.filter(
385
+ (entry) => mountDepth(entry.mountPoint) === deepest,
386
+ );
387
+ if (matches.length !== 1) {
388
+ throw new Error("mountinfo_workspace_mount_ambiguous");
389
+ }
390
+ return matches[0];
391
+ }
392
+
393
+ function sameDirectoryIdentity(left, right) {
394
+ return (
395
+ String(left?.dev ?? left?.fileId?.dev) ===
396
+ String(right?.dev ?? right?.fileId?.dev) &&
397
+ String(left?.ino ?? left?.fileId?.ino) ===
398
+ String(right?.ino ?? right?.fileId?.ino)
399
+ );
400
+ }
401
+
402
+ function workspaceMountTopology(runtime, workspaceRoot, workspaceIdentity) {
403
+ const entries = parseMountInfo(runtime);
404
+ const descendants = entries.filter(
405
+ (entry) =>
406
+ entry.mountPoint !== workspaceRoot &&
407
+ isWithin(workspaceRoot, entry.mountPoint),
408
+ );
409
+ if (descendants.length > 0) {
410
+ throw new Error("workspace_descendant_mount_detected");
411
+ }
412
+
413
+ const mountedBy = containingMount(entries, workspaceRoot);
414
+ const rootMount = containingMount(entries, "/");
415
+ const workspaceRelative = path.posix.relative(
416
+ mountedBy.mountPoint,
417
+ workspaceRoot,
418
+ );
419
+ const filesystemPath = path.posix.join(mountedBy.root, workspaceRelative);
420
+ const sameFilesystem = entries.filter(
421
+ (entry) => entry.majorMinor === mountedBy.majorMinor,
422
+ );
423
+ const aliases = new Set();
424
+ for (const entry of sameFilesystem) {
425
+ if (!isWithin(entry.root, filesystemPath)) continue;
426
+ aliases.add(
427
+ path.posix.join(
428
+ entry.mountPoint,
429
+ path.posix.relative(entry.root, filesystemPath),
430
+ ),
431
+ );
432
+ }
433
+ if (aliases.size === 0) {
434
+ throw new Error("workspace_mount_provenance_unavailable");
435
+ }
436
+ if (
437
+ mountedBy.mountPoint !== "/" &&
438
+ mountedBy.root !== "/" &&
439
+ mountedBy.mountPoint === workspaceRoot
440
+ ) {
441
+ throw new Error("workspace_root_bind_alias_forbidden");
442
+ }
443
+ if ([...aliases].some((alias) => alias !== workspaceRoot)) {
444
+ throw new Error("workspace_root_alias_forbidden");
445
+ }
446
+
447
+ let home;
448
+ try {
449
+ home = realpath(runtime, runtime.homedir());
450
+ } catch {
451
+ throw new Error("workspace_home_identity_unavailable");
452
+ }
453
+ const forbiddenExact = new Set([
454
+ ...FORBIDDEN_EXACT_WORKSPACE_ALIASES,
455
+ ...FORBIDDEN_WORKSPACE_ALIAS_SUBTREES,
456
+ home,
457
+ ]);
458
+ for (const alias of aliases) {
459
+ if (
460
+ forbiddenExact.has(alias) ||
461
+ FORBIDDEN_WORKSPACE_ALIAS_SUBTREES.some((root) => isWithin(root, alias))
462
+ ) {
463
+ throw new Error("workspace_root_alias_forbidden");
464
+ }
465
+ }
466
+
467
+ const forbiddenIdentities = [];
468
+ for (const candidate of forbiddenExact) {
469
+ try {
470
+ const canonical = realpath(runtime, candidate);
471
+ const stat = runtime.fs.statSync(canonical);
472
+ if (!stat.isDirectory?.()) continue;
473
+ const observed = {
474
+ path: candidate,
475
+ canonical,
476
+ dev: String(stat.dev),
477
+ ino: String(stat.ino),
478
+ };
479
+ forbiddenIdentities.push(observed);
480
+ if (sameDirectoryIdentity(stat, workspaceIdentity)) {
481
+ throw new Error("workspace_root_identity_alias_forbidden");
482
+ }
483
+ } catch (error) {
484
+ if (error?.message === "workspace_root_identity_alias_forbidden") {
485
+ throw error;
486
+ }
487
+ // Missing optional system roots do not weaken the paths that exist.
488
+ }
489
+ }
490
+
491
+ const opaqueFilesystem =
492
+ mountedBy.fsType.startsWith("fuse") ||
493
+ OPAQUE_NON_ROOT_FILESYSTEMS.has(mountedBy.fsType);
494
+ if (mountedBy.mountPoint !== "/" && opaqueFilesystem) {
495
+ throw new Error("workspace_mount_provenance_opaque");
496
+ }
497
+
498
+ const lineage = entries.filter((entry) =>
499
+ isWithin(entry.mountPoint, workspaceRoot),
500
+ );
501
+ const binding = {
502
+ version: MOUNT_TOPOLOGY_VERSION,
503
+ source: "proc-self-mountinfo",
504
+ workspaceRoot,
505
+ containingMount: mountedBy.lineDigest,
506
+ rootMount: rootMount.lineDigest,
507
+ lineageEntryDigests: lineage.map((entry) => entry.lineDigest).sort(),
508
+ filesystemEntryDigests: sameFilesystem
509
+ .map((entry) => entry.lineDigest)
510
+ .sort(),
511
+ aliasDigests: [...aliases].map(sha256).sort(),
512
+ forbiddenIdentityDigests: forbiddenIdentities
513
+ .map((entry) => sha256(stableJson(entry)))
514
+ .sort(),
515
+ strictDescendantMountsAtAttestation: 0,
516
+ recursiveBind: true,
517
+ mountTopologyAtomic: false,
518
+ };
519
+ return Object.freeze({
520
+ version: binding.version,
521
+ source: binding.source,
522
+ workspaceRoot: binding.workspaceRoot,
523
+ digest: sha256(stableJson(binding)),
524
+ lineageEntryCount: binding.lineageEntryDigests.length,
525
+ filesystemEntryCount: binding.filesystemEntryDigests.length,
526
+ aliasCount: binding.aliasDigests.length,
527
+ forbiddenIdentityCount: binding.forbiddenIdentityDigests.length,
528
+ strictDescendantMountsAtAttestation:
529
+ binding.strictDescendantMountsAtAttestation,
530
+ rootAliasAttested: true,
531
+ recursiveBind: binding.recursiveBind,
532
+ mountTopologyAtomic: binding.mountTopologyAtomic,
533
+ });
534
+ }
535
+
536
+ function normalizedStdio(stdio) {
537
+ if (stdio === undefined || stdio === null || stdio === "pipe") {
538
+ return ["pipe", "pipe", "pipe"];
539
+ }
540
+ if (stdio === "ignore") return ["ignore", "ignore", "ignore"];
541
+ if (!Array.isArray(stdio)) return null;
542
+ const result = [...stdio];
543
+ while (result.length < 3) result.push(undefined);
544
+ return result.map((entry, index) => {
545
+ if (entry === undefined || entry === null) {
546
+ return index < 3 ? "pipe" : "ignore";
547
+ }
548
+ return entry;
549
+ });
550
+ }
551
+
552
+ function resolveExecutable(runtime, requested, cwd, environment) {
553
+ if (typeof requested !== "string" || !requested || requested.includes("\0")) {
554
+ throw new Error("generic_target_invalid");
555
+ }
556
+ const candidates = [];
557
+ if (requested.includes("/")) {
558
+ candidates.push(
559
+ path.posix.isAbsolute(requested)
560
+ ? requested
561
+ : path.posix.resolve(cwd, requested),
562
+ );
563
+ } else {
564
+ for (const entry of String(
565
+ environment?.PATH || "/usr/local/bin:/usr/bin:/bin",
566
+ ).split(":")) {
567
+ if (!entry) continue;
568
+ const directory = path.posix.isAbsolute(entry)
569
+ ? entry
570
+ : path.posix.resolve(cwd, entry);
571
+ candidates.push(path.posix.join(directory, requested));
572
+ }
573
+ }
574
+ for (const candidate of candidates) {
575
+ try {
576
+ runtime.fs.accessSync(
577
+ candidate,
578
+ runtime.fs.constants?.X_OK ?? fs.constants.X_OK,
579
+ );
580
+ const canonical = realpath(runtime, candidate);
581
+ const stat = runtime.fs.statSync(canonical);
582
+ if (!stat.isFile() || (Number(stat.mode) & 0o111) === 0) continue;
583
+ return { canonical, stat };
584
+ } catch {
585
+ // Continue PATH resolution exactly as child_process would.
586
+ }
587
+ }
588
+ throw new Error("generic_target_unavailable");
589
+ }
590
+
591
+ function systemTarget(canonical) {
592
+ return [...SYSTEM_ROOTS].some((root) => isWithin(root, canonical));
593
+ }
594
+
595
+ function createTrustedResources(
596
+ contract,
597
+ command,
598
+ args,
599
+ spawnOpts,
600
+ sandboxOpts,
601
+ runtime,
602
+ ) {
603
+ const ownedFds = new Set();
604
+ const probeFds = new Set();
605
+ const finalFds = new Set();
606
+ const closeFd = (fd) => {
607
+ if (!Number.isInteger(fd) || !ownedFds.delete(fd)) return;
608
+ probeFds.delete(fd);
609
+ finalFds.delete(fd);
610
+ try {
611
+ runtime.fs.closeSync(fd);
612
+ } catch {
613
+ // Cleanup must preserve the fail-closed boundary result.
614
+ }
615
+ };
616
+ const closeSet = (set) => {
617
+ for (const fd of [...set]) closeFd(fd);
618
+ };
619
+ const cleanup = () => closeSet(ownedFds);
620
+ const track = (fd, phase) => {
621
+ ownedFds.add(fd);
622
+ (phase === "probe" ? probeFds : finalFds).add(fd);
623
+ return fd;
624
+ };
625
+ const openFlags = ({ directory, readWrite = false }) => {
626
+ const constants = runtime.fs.constants || fs.constants;
627
+ return (
628
+ Number(readWrite ? constants.O_RDWR : constants.O_RDONLY) |
629
+ Number(directory ? constants.O_DIRECTORY || 0 : 0) |
630
+ Number(constants.O_NOFOLLOW || 0) |
631
+ Number(constants.O_NONBLOCK || 0)
632
+ );
633
+ };
634
+ const openPair = (
635
+ source,
636
+ { directory, rootRequired = false, executable = false },
637
+ ) => {
638
+ const canonical = realpath(runtime, source);
639
+ if (canonical !== source) throw new Error("resource_path_not_canonical");
640
+ const pathStat = runtime.fs.lstatSync(source);
641
+ if (
642
+ pathStat.isSymbolicLink?.() ||
643
+ (directory ? !pathStat.isDirectory() : !pathStat.isFile()) ||
644
+ (rootRequired && !rootOwned(pathStat, { directory, executable }))
645
+ ) {
646
+ throw new Error("resource_path_unattested");
647
+ }
648
+ const probeFd = track(
649
+ runtime.fs.openSync(source, openFlags({ directory })),
650
+ "probe",
651
+ );
652
+ const finalFd = track(
653
+ runtime.fs.openSync(source, openFlags({ directory })),
654
+ "final",
655
+ );
656
+ const probeStat = runtime.fs.fstatSync(probeFd);
657
+ const finalStat = runtime.fs.fstatSync(finalFd);
658
+ const observedIdentity = identity(source, pathStat);
659
+ if (
660
+ !statMatchesIdentity(probeStat, observedIdentity, { directory }) ||
661
+ !statMatchesIdentity(finalStat, observedIdentity, { directory })
662
+ ) {
663
+ throw new Error("resource_descriptor_identity_changed");
664
+ }
665
+ return {
666
+ source,
667
+ probeFd,
668
+ finalFd,
669
+ identity: observedIdentity,
670
+ };
671
+ };
672
+
673
+ try {
674
+ if (
675
+ runtime.platform !== "linux" ||
676
+ contract?.kind !== LINUX_GENERIC_CONTRACT_KIND ||
677
+ spawnOpts?.shell === true ||
678
+ typeof spawnOpts?.shell === "string" ||
679
+ spawnOpts?.detached === true ||
680
+ spawnOpts?.uid !== undefined ||
681
+ spawnOpts?.gid !== undefined ||
682
+ spawnOpts?.argv0 !== undefined ||
683
+ spawnOpts?.serialization !== undefined
684
+ ) {
685
+ throw new Error("generic_launch_options_unsupported");
686
+ }
687
+ const hostNetworkNamespace = String(
688
+ runtime.fs.readlinkSync("/proc/self/ns/net"),
689
+ );
690
+ if (!/^net:\[\d+\]$/.test(hostNetworkNamespace)) {
691
+ throw new Error("host_network_namespace_identity_unavailable");
692
+ }
693
+
694
+ const workspace = openPair(contract.workspaceRoot, {
695
+ directory: true,
696
+ });
697
+ const cwdPins = openPair(contract.workingDirectory, {
698
+ directory: true,
699
+ });
700
+ const initialWorkspaceMountTopology = workspaceMountTopology(
701
+ runtime,
702
+ contract.workspaceRoot,
703
+ workspace.identity,
704
+ );
705
+
706
+ const supervisor = openPair(BWRAP_PATH, {
707
+ directory: false,
708
+ rootRequired: true,
709
+ executable: true,
710
+ });
711
+ const supervisorBytes = Number(
712
+ runtime.fs.fstatSync(supervisor.probeFd).size,
713
+ );
714
+ const supervisorProbeDigest = hashOpenFile(
715
+ runtime,
716
+ supervisor.probeFd,
717
+ supervisorBytes,
718
+ );
719
+ const supervisorFinalDigest = hashOpenFile(
720
+ runtime,
721
+ supervisor.finalFd,
722
+ supervisorBytes,
723
+ );
724
+ if (supervisorProbeDigest !== supervisorFinalDigest) {
725
+ throw new Error("bubblewrap_supervisor_digest_mismatch");
726
+ }
727
+ supervisor.identity = Object.freeze({
728
+ ...supervisor.identity,
729
+ sha256: supervisorFinalDigest,
730
+ bytes: supervisorBytes,
731
+ });
732
+
733
+ const capabilityFd = runtime.fs.openSync(
734
+ BWRAP_PATH,
735
+ openFlags({ directory: false }),
736
+ );
737
+ ownedFds.add(capabilityFd);
738
+ const capabilityStat = runtime.fs.fstatSync(capabilityFd);
739
+ if (
740
+ !statMatchesIdentity(capabilityStat, supervisor.identity, {
741
+ directory: false,
742
+ }) ||
743
+ hashOpenFile(runtime, capabilityFd, supervisorBytes) !==
744
+ supervisorFinalDigest
745
+ ) {
746
+ throw new Error("bubblewrap_capability_reader_unattested");
747
+ }
748
+ const capabilityResult = runtime.spawnSync("/proc/self/fd/3", ["--help"], {
749
+ shell: false,
750
+ encoding: "utf8",
751
+ stdio: ["ignore", "pipe", "pipe", capabilityFd],
752
+ timeout: 10_000,
753
+ env: { PATH: "/usr/bin:/bin", LANG: "C", LC_ALL: "C" },
754
+ });
755
+ closeFd(capabilityFd);
756
+ if (capabilityResult?.error || capabilityResult?.status !== 0) {
757
+ const error = new Error("bubblewrap_capability_probe_failed");
758
+ error.code = "LINUX_GENERIC_BWRAP_UNAVAILABLE";
759
+ throw error;
760
+ }
761
+ const supported = new Set(
762
+ `${String(capabilityResult.stdout || "")}\n${String(
763
+ capabilityResult.stderr || "",
764
+ )}`
765
+ .split(/\s+/)
766
+ .filter(Boolean),
767
+ );
768
+ if (REQUIRED_BWRAP_OPTIONS.some((option) => !supported.has(option))) {
769
+ const error = new Error("bubblewrap_required_option_missing");
770
+ error.code = "LINUX_GENERIC_BWRAP_UNAVAILABLE";
771
+ throw error;
772
+ }
773
+
774
+ const system = [];
775
+ const systemSymlinks = [];
776
+ for (const destination of SYSTEM_DESTINATIONS) {
777
+ if (!runtime.fs.existsSync(destination)) continue;
778
+ const entryStat = runtime.fs.lstatSync(destination);
779
+ if (entryStat.isSymbolicLink?.()) {
780
+ const expectedTarget = SYSTEM_SYMLINKS.get(destination);
781
+ const observedTarget = runtime.fs.readlinkSync(destination);
782
+ const normalizedTarget = observedTarget.startsWith("/")
783
+ ? observedTarget.slice(1)
784
+ : observedTarget;
785
+ if (
786
+ !expectedTarget ||
787
+ normalizedTarget !== expectedTarget ||
788
+ !runtime.fs.existsSync(`/${expectedTarget}`)
789
+ ) {
790
+ throw new Error("system_runtime_symlink_unattested");
791
+ }
792
+ systemSymlinks.push({
793
+ destination,
794
+ target: expectedTarget,
795
+ });
796
+ continue;
797
+ }
798
+ const pair = openPair(destination, {
799
+ directory: true,
800
+ rootRequired: true,
801
+ });
802
+ system.push({
803
+ destination,
804
+ probeFd: pair.probeFd,
805
+ finalFd: pair.finalFd,
806
+ identity: pair.identity,
807
+ });
808
+ }
809
+ if (!system.some((entry) => entry.destination === "/usr")) {
810
+ throw new Error("system_usr_runtime_unavailable");
811
+ }
812
+
813
+ const etc = [];
814
+ for (const destination of ETC_ALLOWLIST) {
815
+ if (!runtime.fs.existsSync(destination)) continue;
816
+ const pair = openPair(destination, {
817
+ directory: false,
818
+ rootRequired: true,
819
+ });
820
+ etc.push({
821
+ destination,
822
+ probeFd: pair.probeFd,
823
+ finalFd: pair.finalFd,
824
+ identity: pair.identity,
825
+ });
826
+ }
827
+
828
+ const filter = buildNetworkSeccompFilter(runtime.arch);
829
+ const openSeccomp = (phase) => {
830
+ const constants = runtime.fs.constants || fs.constants;
831
+ const flags =
832
+ Number(constants.O_RDWR) |
833
+ Number(constants.O_EXCL) |
834
+ Number(constants.O_TMPFILE ?? O_TMPFILE) |
835
+ Number(constants.O_NOFOLLOW || 0) |
836
+ Number(constants.O_NONBLOCK || 0);
837
+ const fd = track(runtime.fs.openSync("/tmp", flags, 0o400), phase);
838
+ runtime.fs.fchmodSync(fd, 0o400);
839
+ let offset = 0;
840
+ while (offset < filter.buffer.length) {
841
+ const written = runtime.fs.writeSync(
842
+ fd,
843
+ filter.buffer,
844
+ offset,
845
+ filter.buffer.length - offset,
846
+ offset,
847
+ );
848
+ if (written <= 0) throw new Error("seccomp_filter_write_failed");
849
+ offset += written;
850
+ }
851
+ runtime.fs.fsyncSync(fd);
852
+ const stat = runtime.fs.fstatSync(fd);
853
+ if (
854
+ !stat.isFile() ||
855
+ Number(stat.size) !== filter.buffer.length ||
856
+ hashOpenFile(runtime, fd, filter.buffer.length) !== filter.sha256
857
+ ) {
858
+ throw new Error("seccomp_filter_unattested");
859
+ }
860
+ return fd;
861
+ };
862
+ const seccomp = {
863
+ probeFd: openSeccomp("probe"),
864
+ finalFd: openSeccomp("final"),
865
+ sha256: filter.sha256,
866
+ policy: "deny-network-creation",
867
+ };
868
+
869
+ const environment = spawnOpts?.env || process.env;
870
+ const resolvedTarget = resolveExecutable(
871
+ runtime,
872
+ command,
873
+ contract.workingDirectory,
874
+ environment,
875
+ );
876
+ let targetScope;
877
+ if (isWithin(contract.workspaceRoot, resolvedTarget.canonical)) {
878
+ targetScope = "workspace";
879
+ } else if (
880
+ systemTarget(resolvedTarget.canonical) &&
881
+ rootOwned(resolvedTarget.stat, {
882
+ directory: false,
883
+ executable: true,
884
+ })
885
+ ) {
886
+ targetScope = "system";
887
+ } else {
888
+ throw new Error("generic_target_outside_trusted_runtime");
889
+ }
890
+ const target = {
891
+ attestedContractDigest: contract.contractDigest,
892
+ requestedCommand: command,
893
+ resolvedCommand: resolvedTarget.canonical,
894
+ args: [...(args || [])],
895
+ scope: targetScope,
896
+ ...(targetScope === "system"
897
+ ? {
898
+ identity: identity(resolvedTarget.canonical, resolvedTarget.stat),
899
+ }
900
+ : {}),
901
+ };
902
+
903
+ const socketProbeCandidates = [
904
+ { command: runtime.execPath, kind: "node" },
905
+ { command: "/usr/local/bin/node", kind: "node" },
906
+ { command: "/usr/bin/node", kind: "node" },
907
+ { command: "/usr/bin/python3", kind: "python" },
908
+ { command: "/usr/bin/python", kind: "python" },
909
+ ];
910
+ let socketProbeRuntime = null;
911
+ for (const candidate of socketProbeCandidates) {
912
+ if (typeof candidate.command !== "string" || !candidate.command) continue;
913
+ try {
914
+ const resolved = resolveExecutable(
915
+ runtime,
916
+ candidate.command,
917
+ contract.workingDirectory,
918
+ environment,
919
+ );
920
+ if (
921
+ systemTarget(resolved.canonical) &&
922
+ rootOwned(resolved.stat, {
923
+ directory: false,
924
+ executable: true,
925
+ })
926
+ ) {
927
+ socketProbeRuntime = Object.freeze({
928
+ command: resolved.canonical,
929
+ kind: candidate.kind,
930
+ });
931
+ break;
932
+ }
933
+ } catch {
934
+ // Try the next root-owned Node runtime.
935
+ }
936
+ }
937
+ if (!socketProbeRuntime) {
938
+ throw new Error("socket_probe_runtime_unavailable");
939
+ }
940
+
941
+ const descriptorAttested = (fd, expected, directory) => {
942
+ if (!ownedFds.has(fd)) return false;
943
+ try {
944
+ return statMatchesIdentity(runtime.fs.fstatSync(fd), expected, {
945
+ directory,
946
+ });
947
+ } catch {
948
+ return false;
949
+ }
950
+ };
951
+ const pathAttested = (source, expected, directory) => {
952
+ try {
953
+ return (
954
+ realpath(runtime, source) === source &&
955
+ statMatchesIdentity(runtime.fs.lstatSync(source), expected, {
956
+ directory,
957
+ })
958
+ );
959
+ } catch {
960
+ return false;
961
+ }
962
+ };
963
+ const launchStaysBound = (issued) => {
964
+ const stdio = normalizedStdio(spawnOpts?.stdio);
965
+ return (
966
+ issued?.contractDigest === contract.contractDigest &&
967
+ issued.workspaceRoot === contract.workspaceRoot &&
968
+ issued.workingDirectory === contract.workingDirectory &&
969
+ issued.origin === (spawnOpts?.origin || "unknown") &&
970
+ issued.command === command &&
971
+ issued.sync === (sandboxOpts?.sync === true) &&
972
+ issued.shell === false &&
973
+ Array.isArray(stdio) &&
974
+ issued.stdio.length === stdio.length &&
975
+ issued.stdio.every((value, index) => value === stdio[index]) &&
976
+ issued.args.length === (args || []).length &&
977
+ issued.args.every((value, index) => value === args[index]) &&
978
+ identityMatches(issued.rootIdentity, workspace.identity) &&
979
+ identityMatches(issued.cwdIdentity, cwdPins.identity)
980
+ );
981
+ };
982
+ const attestFinal = (issued) => {
983
+ let finalWorkspaceMountTopology;
984
+ try {
985
+ finalWorkspaceMountTopology = workspaceMountTopology(
986
+ runtime,
987
+ contract.workspaceRoot,
988
+ workspace.identity,
989
+ );
990
+ } catch {
991
+ return false;
992
+ }
993
+ if (
994
+ !launchStaysBound(issued) ||
995
+ finalWorkspaceMountTopology.digest !==
996
+ initialWorkspaceMountTopology.digest ||
997
+ !pathAttested(contract.workspaceRoot, workspace.identity, true) ||
998
+ !pathAttested(contract.workingDirectory, cwdPins.identity, true) ||
999
+ !descriptorAttested(workspace.finalFd, workspace.identity, true) ||
1000
+ !descriptorAttested(cwdPins.finalFd, cwdPins.identity, true) ||
1001
+ !descriptorAttested(supervisor.finalFd, supervisor.identity, false) ||
1002
+ hashOpenFile(runtime, supervisor.finalFd, supervisorBytes) !==
1003
+ supervisorFinalDigest ||
1004
+ hashOpenFile(runtime, seccomp.finalFd, filter.buffer.length) !==
1005
+ filter.sha256
1006
+ ) {
1007
+ return false;
1008
+ }
1009
+ for (const entry of [...system, ...etc]) {
1010
+ if (
1011
+ !descriptorAttested(
1012
+ entry.finalFd,
1013
+ entry.identity,
1014
+ system.includes(entry),
1015
+ )
1016
+ ) {
1017
+ return false;
1018
+ }
1019
+ }
1020
+ try {
1021
+ const currentTarget = resolveExecutable(
1022
+ runtime,
1023
+ command,
1024
+ contract.workingDirectory,
1025
+ environment,
1026
+ );
1027
+ return (
1028
+ currentTarget.canonical === target.resolvedCommand &&
1029
+ (target.scope === "workspace"
1030
+ ? isWithin(contract.workspaceRoot, currentTarget.canonical)
1031
+ : rootOwned(currentTarget.stat, {
1032
+ directory: false,
1033
+ executable: true,
1034
+ }) &&
1035
+ statMatchesIdentity(currentTarget.stat, target.identity, {
1036
+ directory: false,
1037
+ }))
1038
+ );
1039
+ } catch {
1040
+ return false;
1041
+ }
1042
+ };
1043
+
1044
+ const probe = (call) => {
1045
+ const nonce = runtime.randomBytes(12).toString("hex");
1046
+ const workspaceMarker = path.posix.join(
1047
+ contract.workspaceRoot,
1048
+ `.chainless-bwrap-probe-${nonce}`,
1049
+ );
1050
+ const outsideMarker = path.posix.join(
1051
+ runtime.tmpdir(),
1052
+ `.chainless-bwrap-outside-${nonce}`,
1053
+ );
1054
+ const homeMarker = path.posix.join(
1055
+ runtime.homedir(),
1056
+ `.chainless-bwrap-home-${nonce}`,
1057
+ );
1058
+ const workspaceHomeAliasMarker = path.posix.join(
1059
+ contract.workspaceRoot,
1060
+ path.posix.basename(homeMarker),
1061
+ );
1062
+ const devShmMarker = path.posix.join(
1063
+ "/dev/shm",
1064
+ `.chainless-bwrap-dev-shm-${nonce}`,
1065
+ );
1066
+ const outsideContents = `host-only-${nonce}`;
1067
+ const homeContents = `home-only-${nonce}`;
1068
+ let outsideCreated = false;
1069
+ let homeCreated = false;
1070
+ let devShmCreated = false;
1071
+ try {
1072
+ runtime.fs.writeFileSync(outsideMarker, outsideContents, {
1073
+ encoding: "utf8",
1074
+ flag: "wx",
1075
+ mode: 0o600,
1076
+ });
1077
+ outsideCreated = true;
1078
+ runtime.fs.writeFileSync(homeMarker, homeContents, {
1079
+ encoding: "utf8",
1080
+ flag: "wx",
1081
+ mode: 0o600,
1082
+ });
1083
+ homeCreated = true;
1084
+ runtime.fs.writeFileSync(devShmMarker, `host-dev-shm-${nonce}`, {
1085
+ encoding: "utf8",
1086
+ flag: "wx",
1087
+ mode: 0o600,
1088
+ });
1089
+ devShmCreated = true;
1090
+ const socketProbe =
1091
+ socketProbeRuntime.kind === "node"
1092
+ ? {
1093
+ args: [
1094
+ "-e",
1095
+ [
1096
+ 'const net = require("node:net");',
1097
+ 'const socket = net.connect({host:"127.0.0.1",port:9});',
1098
+ "const timer = setTimeout(() => process.exit(4), 1000);",
1099
+ 'socket.once("connect", () => { clearTimeout(timer); process.exit(5); });',
1100
+ 'socket.once("error", (error) => {',
1101
+ " clearTimeout(timer);",
1102
+ ' process.exit(error?.code === "EPERM" ? 0 : 6);',
1103
+ "});",
1104
+ ].join(""),
1105
+ ],
1106
+ }
1107
+ : {
1108
+ args: [
1109
+ "-I",
1110
+ "-S",
1111
+ "-c",
1112
+ [
1113
+ "import errno,socket,sys",
1114
+ "try:",
1115
+ " s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)",
1116
+ "except OSError as e:",
1117
+ " sys.exit(0 if e.errno==errno.EPERM else 6)",
1118
+ "else:",
1119
+ " s.close()",
1120
+ " sys.exit(5)",
1121
+ ].join("\n"),
1122
+ ],
1123
+ };
1124
+ const socketProbeCommand = [
1125
+ shellQuote(socketProbeRuntime.command),
1126
+ ...socketProbe.args.map(shellQuote),
1127
+ ].join(" ");
1128
+ const script = [
1129
+ "set -eu",
1130
+ "mount_is_read_only() {",
1131
+ ' want="$1"',
1132
+ " seen=0",
1133
+ " while IFS=' ' read -r _ _ _ _ mountpoint mountopts _; do",
1134
+ ' [ "$mountpoint" = "$want" ] || continue',
1135
+ " seen=1",
1136
+ ' case ",$mountopts," in',
1137
+ " *,ro,*) ;;",
1138
+ " *) return 1 ;;",
1139
+ " esac",
1140
+ " done < /proc/self/mountinfo",
1141
+ ' [ "$seen" = 1 ]',
1142
+ "}",
1143
+ "workspace_mount_topology_is_attested() {",
1144
+ ` want=${shellQuote(encodeMountInfoPath(contract.workspaceRoot))}`,
1145
+ ' prefix="${want}/"',
1146
+ " seen=0",
1147
+ " while IFS=' ' read -r _ _ _ _ mountpoint _; do",
1148
+ ' [ "$mountpoint" = "$want" ] && seen=1',
1149
+ ' case "$mountpoint" in',
1150
+ ' "$prefix"*) return 1 ;;',
1151
+ " esac",
1152
+ ` done < ${MOUNTINFO_PATH}`,
1153
+ ' [ "$seen" = 1 ]',
1154
+ "}",
1155
+ "mount_is_read_only /",
1156
+ "mount_is_read_only /usr",
1157
+ "workspace_mount_topology_is_attested",
1158
+ `test ! -e ${shellQuote("/etc/shadow")}`,
1159
+ `test ! -e ${shellQuote(homeMarker)}`,
1160
+ `test ! -e ${shellQuote(workspaceHomeAliasMarker)}`,
1161
+ `test ! -e ${shellQuote(outsideMarker)}`,
1162
+ `test ! -e ${shellQuote(devShmMarker)}`,
1163
+ `printf %s ${shellQuote(nonce)} > ${shellQuote(devShmMarker)}`,
1164
+ `test "$(cat ${shellQuote(devShmMarker)})" = ${shellQuote(nonce)}`,
1165
+ `rm -f ${shellQuote(devShmMarker)}`,
1166
+ "if printf x > /chainless-undeclared-root 2>/dev/null; then exit 12; fi",
1167
+ "test ! -e /chainless-undeclared-root",
1168
+ "if printf x > /usr/.chainless-system-write 2>/dev/null; then exit 13; fi",
1169
+ "test ! -e /usr/.chainless-system-write",
1170
+ `test "$(readlink /proc/self/ns/net)" != ${shellQuote(
1171
+ hostNetworkNamespace,
1172
+ )}`,
1173
+ `printf %s ${shellQuote(nonce)} > ${shellQuote(workspaceMarker)}`,
1174
+ `test "$(cat ${shellQuote(workspaceMarker)})" = ${shellQuote(nonce)}`,
1175
+ `rm -f ${shellQuote(workspaceMarker)}`,
1176
+ socketProbeCommand,
1177
+ `printf '%s\\n%s\\n' ${shellQuote(call.policyDigest)} ${shellQuote(
1178
+ call.contractDigest,
1179
+ )}`,
1180
+ ].join("\n");
1181
+ const result = runtime.spawnSync(
1182
+ call.command,
1183
+ [...call.args, "--", "/bin/sh", "-c", script],
1184
+ call.options,
1185
+ );
1186
+ const outsideUnchanged =
1187
+ outsideCreated &&
1188
+ String(runtime.fs.readFileSync(outsideMarker, "utf8")) ===
1189
+ outsideContents;
1190
+ const homeUnchanged =
1191
+ homeCreated &&
1192
+ String(runtime.fs.readFileSync(homeMarker, "utf8")) === homeContents;
1193
+ const devShmUnchanged =
1194
+ devShmCreated &&
1195
+ String(runtime.fs.readFileSync(devShmMarker, "utf8")) ===
1196
+ `host-dev-shm-${nonce}`;
1197
+ const expectedOutput = `${call.policyDigest}\n${call.contractDigest}\n`;
1198
+ const runnable =
1199
+ !result?.error &&
1200
+ result?.status === 0 &&
1201
+ String(result.stdout) === expectedOutput &&
1202
+ outsideUnchanged &&
1203
+ homeUnchanged &&
1204
+ devShmUnchanged;
1205
+ return {
1206
+ runnable,
1207
+ policyDigest: runnable ? call.policyDigest : null,
1208
+ contractDigest: runnable ? call.contractDigest : null,
1209
+ emptyRoot: runnable,
1210
+ undeclaredRootReadOnly: runnable,
1211
+ workspaceReadWrite: runnable,
1212
+ workspaceMountTopologyAttested: runnable,
1213
+ anonymousDevWritable: runnable,
1214
+ systemReadOnly: runnable,
1215
+ hostHomeHidden: runnable,
1216
+ outsideMarkerHidden: runnable,
1217
+ networkNamespace: runnable,
1218
+ networkNamespaceChanged: runnable,
1219
+ socketCreationDenied: runnable,
1220
+ };
1221
+ } catch {
1222
+ return { runnable: false };
1223
+ } finally {
1224
+ for (const marker of [
1225
+ workspaceMarker,
1226
+ outsideMarker,
1227
+ homeMarker,
1228
+ devShmMarker,
1229
+ ]) {
1230
+ try {
1231
+ runtime.fs.unlinkSync(marker);
1232
+ } catch {
1233
+ // Probe cleanup is best effort; a failed probe stays fail-closed.
1234
+ }
1235
+ }
1236
+ }
1237
+ };
1238
+
1239
+ return {
1240
+ attestedContractDigest: contract.contractDigest,
1241
+ attestContract: launchStaysBound,
1242
+ attestFinal,
1243
+ closeProbe: () => closeSet(probeFds),
1244
+ cleanup,
1245
+ supervisor: {
1246
+ probeFd: supervisor.probeFd,
1247
+ finalFd: supervisor.finalFd,
1248
+ identity: supervisor.identity,
1249
+ sha256: supervisorFinalDigest,
1250
+ bytes: supervisorBytes,
1251
+ },
1252
+ workspace: {
1253
+ probeFd: workspace.probeFd,
1254
+ finalFd: workspace.finalFd,
1255
+ identity: workspace.identity,
1256
+ mountTopology: initialWorkspaceMountTopology,
1257
+ },
1258
+ system,
1259
+ systemSymlinks,
1260
+ etc,
1261
+ seccomp,
1262
+ target,
1263
+ probe,
1264
+ };
1265
+ } catch (error) {
1266
+ cleanup();
1267
+ throw error;
1268
+ }
1269
+ }
1270
+
1271
+ /**
1272
+ * Apply the generic strong backend. This function is called only by the Linux
1273
+ * branch in platform-sandbox; all runtime overrides are internal test seams.
1274
+ */
1275
+ export function applyLinuxGenericWorkspaceSandbox(
1276
+ command,
1277
+ args,
1278
+ spawnOpts,
1279
+ sandboxOpts,
1280
+ runtimeOverrides = {},
1281
+ ) {
1282
+ const runtime = runtimeFor(runtimeOverrides);
1283
+ const contract = sandboxOpts?.executionContract;
1284
+ const requiredBoundaries = Array.isArray(sandboxOpts?.requiredBoundaries)
1285
+ ? sandboxOpts.requiredBoundaries
1286
+ : [];
1287
+ const provenance = {
1288
+ origin: spawnOpts?.origin || "unknown",
1289
+ command,
1290
+ args: [...(args || [])],
1291
+ cwd: spawnOpts?.cwd || process.cwd(),
1292
+ shell: spawnOpts?.shell,
1293
+ sync: sandboxOpts?.sync === true,
1294
+ stdio: spawnOpts?.stdio,
1295
+ requiredBoundaries,
1296
+ };
1297
+ let resources;
1298
+ try {
1299
+ resources = createTrustedResources(
1300
+ contract,
1301
+ command,
1302
+ args || [],
1303
+ spawnOpts || {},
1304
+ sandboxOpts || {},
1305
+ runtime,
1306
+ );
1307
+ } catch (error) {
1308
+ const preflightFailure =
1309
+ error?.code === "LINUX_GENERIC_BWRAP_UNAVAILABLE"
1310
+ ? "linux_generic_bwrap_unavailable"
1311
+ : "linux_generic_resources_unattested";
1312
+ return planLinuxGenericBubblewrap(
1313
+ {
1314
+ contract,
1315
+ provenance,
1316
+ resources: null,
1317
+ environment: spawnOpts?.env || process.env,
1318
+ spawnOptions: spawnOpts,
1319
+ preflightFailure,
1320
+ },
1321
+ { platform: runtime.platform },
1322
+ );
1323
+ }
1324
+ return planLinuxGenericBubblewrap(
1325
+ {
1326
+ contract,
1327
+ provenance,
1328
+ resources,
1329
+ environment: spawnOpts?.env || process.env,
1330
+ spawnOptions: spawnOpts,
1331
+ probe: resources.probe,
1332
+ },
1333
+ { platform: runtime.platform },
1334
+ );
1335
+ }
1336
+
1337
+ export function isLinuxGenericWorkspaceContract(contract) {
1338
+ return (
1339
+ contract?.contractVersion === 1 &&
1340
+ contract?.kind === LINUX_GENERIC_CONTRACT_KIND &&
1341
+ typeof contract?.contractDigest === "string" &&
1342
+ contract?.workspaceRoot?.startsWith("/") &&
1343
+ contract?.workingDirectory?.startsWith("/")
1344
+ );
1345
+ }
1346
+
1347
+ export { LINUX_GENERIC_BWRAP_BACKEND };