chainlesschain 0.162.183 → 0.162.185

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 (205) 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-Co4ipAKx.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CXYdXEUI.js → ActionButton-BpsoDrDq.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-CKR5Lexn.js → Analytics-DMUqeBik.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-DATVUOXB.js → AppLayout-C6fD_dXr.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-vb_0ZlV1.js → Artifacts-DW0YlQlV.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-BI1l9GWt.js → Audit-DXtHiiFK.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-BFXbaUEf.js → BackgroundAgents-BgmF62U-.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-DEYBJJ6H.js → Backup-D8vhVLQC.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-COdzeO0M.js → BaseInput-D7nCtY2X.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-oSN6sfM5.js → Chat-CBZLEV2G.js} +6 -6
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CI_jT0ru.js → ChatBubbleRenderer-CtDb-2Qu.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-BezEa4tm.js → Checkbox-DJLkeD4M.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CNEL0Ife.js → Codegen-Cs6xO8Fd.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-Bf2Y2Bn1.js → Col-Dtx9wxfT.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-BfUlu4Hc.js → Community-DiigJg4n.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-BN4xWP9e.js → Compact-B67oMDDL.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-Bt6BKmEP.js → Compliance-BBDl-Jos.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DKLp8YxO.js → Cowork-Ba98hSsa.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-o7Hhw6ky.js → Cron-DBWQZIz7.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-DaNXWJIw.js → Crosschain-OcrKzxAt.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-B-Qa2mha.js → DID-mW2eSbjM.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-CMX0Qnxw.js → Dashboard-BmsDPmKJ.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BLuLyRu_.js → Dropdown-CVzmCyRx.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-CFBAeMij.js → EmailListRenderer-vVgqKzhh.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DWzXQ67V.js → FamilyGuardDashboard-CWY5t_ZJ.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-DB1QSLgr.js → Federation-ZGomhjxr.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-C-OKCToP.js → FormItemContext-DyaknbLr.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-DMJFtFZf.js → GenericCardRenderer-CmIvN1qr.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-DsGSujOV.js → Git-DQOJIYkG.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-B0iS9NEW.js → Governance-CMiY-EEp.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CH004H7c.js → Inference-D6IXuucf.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-Q9MTQ83c.js → KnowledgeGraph-6jkTL0hV.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-B_fyjZK9.js → Logs-N0jD-349.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-BL1IVXm-.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-BIStF9d1.js → Marketplace-WhYuhBxm.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-pGfal768.js → McpTools-DTeO28xy.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-xbNqpIqB.js → Memory-MSHzPSE-.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-B5EQGrIK.js → MobileBridge-Cyqs3J3W.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-7IN2hWHg.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-a_rmfxJH.js → Mtc-MWBnezNM.js} +5 -5
  43. package/src/assets/web-panel/assets/{MtcAudit-BmKuofUr.js → MtcAudit-DQu35Nu-.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-BQbJ-CAN.js → Multisig-CQS_M-DP.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-JtPJKX25.js → NLProgramming-WD16vQvY.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-ZFKznTSG.js → Notes-DqsiSwOq.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-B6aHRBBR.js → NotificationSettings-D3LoxO-P.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-DdcsOqEz.js → OrderTableRenderer-C5jPoTd6.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-BFDTAUgS.js → Organization-48aMBJso.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-0FoTfbWH.js → Overflow-j-a2XkhR.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-CWd90etw.js → P2P-BYpBNYnh.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-DtCGC-W-.js → PdhVaultBrowser-CYCsrrLg.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-CvQ7nr7T.js → Permissions-ZRXGXDFb.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-rdNPcR-Z.js → PersonalDataHub-6OWUPHyL.js} +5 -5
  55. package/src/assets/web-panel/assets/{Pipeline-CmrfiNOs.js → Pipeline-FcjlDK9E.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-D7ep1h_t.js → Privacy-vD_6Tldp.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-C0xKINHS.js → ProjectInit-B_CHOLXp.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-AOmZFp-F.js → ProjectSettings-rEiqQ2da.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-BWHSkXZP.js → Projects-Bg8obRAM.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-Cb7JCTPL.js → Providers-Dgtt97B4.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-BDMehcXC.js → QrScannerModal-DaGVylZO.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-BF6_1nvx.js → QuickAsk-DP2_2x3u.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-BHfT-A28.js → Recommend-D29yj4ki.js} +1 -1
  64. package/src/assets/web-panel/assets/RemoteSession-Ue2wnBLp.js +5 -0
  65. package/src/assets/web-panel/assets/{Reputation-CsWHLEcj.js → Reputation-CrxIHqeC.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-D6srnWhm.js → Row-BgSzJPCn.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-Xhr-KyVd.js → RssFeed-AwhYOrP9.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-vIbIJvwL.js → Search-D2shpEcm.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-Bzi5Aiko.js → Security-BtKzhTbT.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-BHtVX52P.js → Services-BY0DsUSm.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-C0R8B2jf.js → Skeleton-B3Sb0UI9.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-DtT6rWOX.js → Skills-Ne5w0bDs.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-0B37mxVU.js → Sla-C8VkaP4T.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-0V2jE1Bs.js → SpeechSettings-BQdIEYP9.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-CYedRzaj.js → SyncSettings-CwFafrUg.js} +2 -2
  76. package/src/assets/web-panel/assets/Tasks-yJuKoDlR.js +1 -0
  77. package/src/assets/web-panel/assets/{Templates-BKoHztGT.js → Templates-DYn9qCmx.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant--17nwVNZ.js → Tenant-DkKReHvn.js} +1 -1
  79. package/src/assets/web-panel/assets/Terminal-r6bd-owt.js +3 -0
  80. package/src/assets/web-panel/assets/{TimelineRenderer-C5jx4AE3.js → TimelineRenderer-Mq2juUC3.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-DYJKYa9t.js → Tokens-BnMw7MRi.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-DChpWiQ9.js → Trigger-CTzKp4o9.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DfWG52EA.js → Trust-BBrSZm9U.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-CMugYcyA.js → UkeySign-D9fDvEMq.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-BOSmrHAd.js → VideoEditing-DUi5ve-0.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-D5g3k3Sa.js → Wallet-C8Fi1P8_.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-D-7u4Mkc.js → WebAuthn-s1LppYi2.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-CFzZMSUJ.js → WorkflowEditor-DJL2C4lj.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-VbR6l6mc.js → chat-D3F2ndOB.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BSZn8EDS.js → colors-B7739-e3.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-y74obMEq.js → compact-item-CcJbFTF4.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-C326bjex.js → createContext-CuP_DEFS.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-Cj_36GHq.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-D_INJQT_.js → hasIn-6YEXfIik.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DuIkfnTF.js → index-4h3lpWJ3.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DBkzrpiL.js → index-BIID4XcP.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-B7SrC-On.js → index-BOHUo3aM.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BnSfnSMP.js → index-BYYSLEjn.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-KF9Zmqln.js → index-Bh4hcNWW.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C327pLbw.js → index-BhIKnQAW.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-E_v1ZCTP.js → index-Bru_kpZl.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Bfw2hf2s.js → index-BsdSnHQE.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-jYaogFE8.js → index-BxSqVMGh.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Dlxh9D8D.js → index-C3ZA_h1j.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DdyjrCAu.js → index-C3pctdCb.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-abmn_8Yz.js → index-CRuoRapN.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-qMA4GYdQ.js → index-CY0zd_jc.js} +1 -1
  108. package/src/assets/web-panel/assets/index-CezFrAmD.js +1 -0
  109. package/src/assets/web-panel/assets/index-Cxgguez6.js +1 -0
  110. package/src/assets/web-panel/assets/{index-Cr3uuz0C.js → index-CxradkS6.js} +19 -19
  111. package/src/assets/web-panel/assets/{index-DwHSZLXg.js → index-D0K_rFC1.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CMHqs3Jb.js → index-D0zdPyTD.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Bnua8IX3.js → index-D3haorUt.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BY8-nCgs.js → index-D6Ij_2uw.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-FQBsuExX.js → index-DDRlBaDt.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C-ZCaUJc.js → index-DGsE7MQz.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BjcWKg1S.js → index-DHvxXXNA.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-GA80ir_u.js → index-DV11jpeO.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CsovTpx9.js → index-DXdnfDVK.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-nSn2-C4N.js → index-D_75jYWk.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BJi3UqBt.js → index-DjI9MkId.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-gINyI1rh.js → index-DlSvpNMf.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CfLJ42kv.js → index-Dox44j29.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C2NyllnW.js → index-DpEKmjoa.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Iw0FfqUz.js → index-Dsy7_x4s.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-4eyH-eC2.js → index-Gxw09e9R.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-4PIeTM--.js → index-LfRlSmqe.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-IZAWt0H8.js → index-Nz-MUHD1.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DUU2606o.js → index-b9cuvFEv.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CzE53HiB.js → index-eR6H6NR7.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-9PIVuXqo.js → index-fWgUQZ9d.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DkH1JvEt.js → index-mfWQRSLn.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DNMxIsDg.js → index-yOZYSNmG.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-DI0Zaf8-.js → initDefaultProps-CJ1Bq2L3.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BZDKSy3Z.js → motion-rYJN_vzf.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-DsIW01Vp.js → move-DnDK2gTP.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-CohkDhsf.js → mtc-parser-CKnXy7ES.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-DpiN5rzg.js → omit-3nRc3hWT.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-qbuqni3y.js → pickAttrs-Ddjj3sLu.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-mBky6714.js → placementArrow-CbsbphQY.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-Bas3eQ2K.js → responsiveObserve-D9aZ4VDJ.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-CBx_SKoK.js → slide-D4pvdFj0.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-BlYguJSV.js → statusUtils-C2W-vY-S.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BCYQmRGv.js → styleChecker-Cbq5150d.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-Dp5dybLM.js → useFlexGapSupport-BA2B7R82.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-DHAMBgA2.js → useFs-CLrwg3xg.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-W6jsSw76.js → usePersonalDataHub-DQeAI5qs.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-BkI6aayc.js → vnode-8BDGy-YU.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-C_IMGueq.js → zoom-B2SzFprl.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 +26 -0
  158. package/src/gateways/terminal/PtyManager.js +45 -15
  159. package/src/gateways/ws/ws-agent-handler.js +15 -1
  160. package/src/gateways/ws/ws-session-gateway.js +88 -0
  161. package/src/harness/background-task-command-runner.js +234 -0
  162. package/src/harness/background-task-manager.js +150 -20
  163. package/src/harness/background-task-worker.js +17 -11
  164. package/src/harness/jsonl-session-store.js +57 -6
  165. package/src/harness/mcp-client.js +65 -43
  166. package/src/lib/agent-batch.js +4 -1
  167. package/src/lib/agent-team/team-worktree.js +3 -0
  168. package/src/lib/async-hook-supervisor.cjs +82 -10
  169. package/src/lib/async-hook-supervisor.js +50 -4
  170. package/src/lib/background-agent-supervisor.js +46 -0
  171. package/src/lib/collaboration-run-store.js +362 -0
  172. package/src/lib/event-runtime-host.js +12 -10
  173. package/src/lib/hook-runner.cjs +105 -18
  174. package/src/lib/hook-runner.js +79 -6
  175. package/src/lib/hook-shell-command.cjs +148 -0
  176. package/src/lib/hooks-v2-runtime.js +280 -81
  177. package/src/lib/hooks-v2-workspace-context.js +258 -0
  178. package/src/lib/jsonl-session-store.js +2 -0
  179. package/src/lib/lsp/__tests__/lsp-client.test.js +15 -0
  180. package/src/lib/lsp/lsp-client.js +12 -1
  181. package/src/lib/plugin-monitor-supervisor.js +53 -16
  182. package/src/lib/plugin-runtime/bin.js +21 -5
  183. package/src/lib/plugin-runtime/install.js +249 -57
  184. package/src/lib/plugin-runtime/mcp.js +21 -2
  185. package/src/lib/plugin-runtime/monitors.js +19 -3
  186. package/src/lib/plugin-runtime/sandbox-policy.js +100 -4
  187. package/src/lib/process-execution-broker/index.js +1151 -44
  188. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +1557 -0
  189. package/src/lib/process-execution-broker/linux-generic-bwrap.js +1378 -0
  190. package/src/lib/process-execution-broker/platform-sandbox.js +586 -58
  191. package/src/lib/session-usage.js +202 -2
  192. package/src/lib/stream-retry.js +29 -0
  193. package/src/repl/agent-repl.js +40 -3
  194. package/src/runtime/agent-core.js +304 -42
  195. package/src/runtime/headless-runner.js +48 -1
  196. package/src/runtime/headless-stream.js +42 -2
  197. package/src/workers/background-agent-worker.js +5 -11
  198. package/src/assets/web-panel/assets/MarkdownRenderer-C0o4FtpD.js +0 -1
  199. package/src/assets/web-panel/assets/MobileProjects-VTc3gT1y.js +0 -1
  200. package/src/assets/web-panel/assets/RemoteSession-B6EEf8hh.js +0 -5
  201. package/src/assets/web-panel/assets/Tasks-D-R1x8vm.js +0 -1
  202. package/src/assets/web-panel/assets/Terminal-Dnkos7vW.js +0 -3
  203. package/src/assets/web-panel/assets/devWarning-D3Bho_6u.js +0 -1
  204. package/src/assets/web-panel/assets/index-5iAFwYx1.js +0 -1
  205. package/src/assets/web-panel/assets/index-BfnB7LPx.js +0 -1
@@ -27,6 +27,7 @@ import crypto from "node:crypto";
27
27
  import os from "node:os";
28
28
  import path from "node:path";
29
29
  import * as fs from "node:fs";
30
+ import * as tty from "node:tty";
30
31
  import { createRequire } from "node:module";
31
32
 
32
33
  const require = createRequire(import.meta.url);
@@ -38,7 +39,13 @@ import {
38
39
  postSpawnSandbox as _postSpawnSandbox,
39
40
  SANDBOX_BOUNDARIES,
40
41
  } from "./platform-sandbox.js";
42
+ import {
43
+ admitLinuxGenericSandboxExecutionContract,
44
+ issueLinuxGenericSandboxExecutionContract,
45
+ LINUX_GENERIC_CONTRACT_KIND,
46
+ } from "./linux-generic-bwrap.js";
41
47
  import { consumeIssuedPluginSandboxExecutionContract } from "../plugin-runtime/bin.js";
48
+ import runtimeProvenanceLedger from "../runtime-provenance-ledger.js";
42
49
  import { credentialAgent } from "./credential-agent.js";
43
50
 
44
51
  const SUPPORTED_SANDBOX_BOUNDARIES = new Set(Object.values(SANDBOX_BOUNDARIES));
@@ -50,8 +57,6 @@ const SUPPORTED_SANDBOX_PROFILES = new Set([
50
57
 
51
58
  // 延迟导入避免循环依赖
52
59
  let _traceCtx = null;
53
- let _rpl = null;
54
- let _hooksV2 = null;
55
60
  const _ipcBus = null;
56
61
 
57
62
  function getTraceCtx() {
@@ -66,25 +71,431 @@ function getTraceCtx() {
66
71
  }
67
72
 
68
73
  function getRpl() {
69
- if (!_rpl) {
70
- try {
71
- _rpl = require("../runtime-provenance-ledger.js");
72
- } catch {
73
- _rpl = null;
74
- }
75
- }
76
- return _rpl;
74
+ return runtimeProvenanceLedger;
77
75
  }
78
76
 
79
- function getHooksV2() {
80
- if (!_hooksV2) {
77
+ function createNativePtyAdapter() {
78
+ const MAX_PENDING_WRITE_BYTES = 1024 * 1024;
79
+ const terminalStates = new WeakMap();
80
+ const blockingSlaveOwners = new Map();
81
+ const closeStream = (stream) => {
81
82
  try {
82
- _hooksV2 = require("../hooks-v2-runtime.js");
83
+ stream?.destroy?.();
83
84
  } catch {
84
- _hooksV2 = null;
85
+ // PTY cleanup is best-effort after the owning process has terminated.
86
+ }
87
+ };
88
+ const terminalState = (terminal) => {
89
+ const state = terminalStates.get(terminal);
90
+ if (!state) {
91
+ throw new Error("pty_terminal_state_unavailable");
92
+ }
93
+ return state;
94
+ };
95
+ const invalidateMasterState = (state) => {
96
+ state.disposed = true;
97
+ state.fd = null;
98
+ state.writeQueue.length = 0;
99
+ state.pendingWriteBytes = 0;
100
+ if (state.writeImmediate) {
101
+ clearImmediate(state.writeImmediate);
102
+ state.writeImmediate = null;
103
+ }
104
+ };
105
+ const hasLiveMaster = (state) =>
106
+ !state.disposed &&
107
+ Number.isInteger(state.fd) &&
108
+ state.master?.destroyed !== true &&
109
+ state.master?.closed !== true;
110
+ const processWriteQueue = (state) => {
111
+ if (!hasLiveMaster(state)) {
112
+ invalidateMasterState(state);
113
+ return;
85
114
  }
86
- }
87
- return _hooksV2;
115
+ if (state.writeQueue.length === 0) {
116
+ return;
117
+ }
118
+ const task = state.writeQueue[0];
119
+ let written = 0;
120
+ try {
121
+ // The node-pty master is non-blocking. Keep the syscall synchronous so
122
+ // releaseTerminal() can never close and recycle this numeric fd while a
123
+ // libuv fs.write request still refers to it. Bound each turn for event
124
+ // loop fairness; EAGAIN is retried from the next turn.
125
+ written = fs.writeSync(
126
+ state.fd,
127
+ task.buffer,
128
+ task.offset,
129
+ Math.min(task.buffer.byteLength - task.offset, 64 * 1024),
130
+ null,
131
+ );
132
+ } catch (error) {
133
+ if (error.code !== "EAGAIN" && error.code !== "EWOULDBLOCK") {
134
+ invalidateMasterState(state);
135
+ }
136
+ }
137
+ if (written > 0) {
138
+ task.offset += written;
139
+ state.pendingWriteBytes -= written;
140
+ if (task.offset >= task.buffer.byteLength) {
141
+ state.writeQueue.shift();
142
+ }
143
+ }
144
+ if (
145
+ !state.disposed &&
146
+ state.writeQueue.length > 0 &&
147
+ !state.writeImmediate
148
+ ) {
149
+ state.writeImmediate = setImmediate(() => {
150
+ state.writeImmediate = null;
151
+ processWriteQueue(state);
152
+ });
153
+ }
154
+ };
155
+ return {
156
+ allocate(ptyModule, options) {
157
+ if (
158
+ typeof ptyModule?.native?.open !== "function" ||
159
+ typeof ptyModule.native.resize !== "function"
160
+ ) {
161
+ throw new TypeError("pty_module_native_open_unavailable");
162
+ }
163
+ const cols = options?.cols || 80;
164
+ const rows = options?.rows || 24;
165
+ if (
166
+ !Number.isInteger(cols) ||
167
+ cols <= 0 ||
168
+ cols > 65_535 ||
169
+ !Number.isInteger(rows) ||
170
+ rows <= 0 ||
171
+ rows > 65_535
172
+ ) {
173
+ throw new TypeError("pty_dimensions_invalid");
174
+ }
175
+ let opened;
176
+ let master = null;
177
+ try {
178
+ opened = ptyModule.native.open(cols, rows);
179
+ if (
180
+ !Number.isInteger(opened?.master) ||
181
+ opened.master < 0 ||
182
+ !Number.isInteger(opened?.slave) ||
183
+ opened.slave < 0 ||
184
+ opened.master === opened.slave ||
185
+ typeof opened?.pty !== "string" ||
186
+ !/^\/dev\/pts\/\d+$/.test(opened.pty)
187
+ ) {
188
+ throw new Error("pty_native_identity_unavailable");
189
+ }
190
+ const masterStat = fs.fstatSync(opened.master);
191
+ const slaveStat = fs.fstatSync(opened.slave);
192
+ const ptmxStat = fs.statSync("/dev/ptmx");
193
+ const pathStat = fs.statSync(opened.pty);
194
+ if (
195
+ !masterStat.isCharacterDevice() ||
196
+ !slaveStat.isCharacterDevice() ||
197
+ !ptmxStat.isCharacterDevice() ||
198
+ !pathStat.isCharacterDevice() ||
199
+ String(masterStat.dev) !== String(ptmxStat.dev) ||
200
+ String(masterStat.rdev) !== String(ptmxStat.rdev) ||
201
+ String(pathStat.dev) !== String(slaveStat.dev) ||
202
+ String(pathStat.rdev) !== String(slaveStat.rdev)
203
+ ) {
204
+ throw new Error("pty_native_identity_invalid");
205
+ }
206
+ master = new tty.ReadStream(opened.master);
207
+ const encoding =
208
+ options?.encoding === null
209
+ ? null
210
+ : typeof options?.encoding === "string"
211
+ ? options.encoding
212
+ : "utf8";
213
+ if (encoding !== null) {
214
+ master.setEncoding(encoding);
215
+ }
216
+ const terminal = {
217
+ master,
218
+ ptsName: opened.pty,
219
+ };
220
+ const state = {
221
+ fd: opened.master,
222
+ rawSlaveFd: opened.slave,
223
+ blockingSlaveFd: null,
224
+ native: ptyModule.native,
225
+ cols,
226
+ rows,
227
+ encoding: encoding || "utf8",
228
+ writeQueue: [],
229
+ pendingWriteBytes: 0,
230
+ writeImmediate: null,
231
+ disposed: false,
232
+ readError: null,
233
+ master,
234
+ };
235
+ terminalStates.set(terminal, state);
236
+ master.on("error", (error) => {
237
+ state.readError = error;
238
+ invalidateMasterState(state);
239
+ });
240
+ master.once("close", () => invalidateMasterState(state));
241
+ return terminal;
242
+ } catch (error) {
243
+ if (master) {
244
+ closeStream(master);
245
+ } else if (Number.isInteger(opened?.master)) {
246
+ try {
247
+ fs.closeSync(opened.master);
248
+ } catch {
249
+ // Preserve the allocation failure.
250
+ }
251
+ }
252
+ if (Number.isInteger(opened?.slave)) {
253
+ try {
254
+ fs.closeSync(opened.slave);
255
+ } catch {
256
+ // Preserve the allocation failure.
257
+ }
258
+ }
259
+ throw error;
260
+ }
261
+ },
262
+ openBlockingSlave(terminal) {
263
+ const ptsName = terminal?.ptsName;
264
+ const state = terminalState(terminal);
265
+ const originalFd = state.rawSlaveFd;
266
+ if (
267
+ typeof ptsName !== "string" ||
268
+ !/^\/dev\/pts\/\d+$/.test(ptsName) ||
269
+ !Number.isInteger(originalFd)
270
+ ) {
271
+ throw new Error("pty_slave_identity_unavailable");
272
+ }
273
+ const originalStat = fs.fstatSync(originalFd);
274
+ if (!originalStat.isCharacterDevice()) {
275
+ throw new Error("pty_slave_not_character_device");
276
+ }
277
+ const flags =
278
+ Number(fs.constants.O_RDWR) |
279
+ Number(fs.constants.O_NOCTTY || 0) |
280
+ Number(fs.constants.O_CLOEXEC || 0);
281
+ const fd = fs.openSync(ptsName, flags);
282
+ try {
283
+ const openedStat = fs.fstatSync(fd);
284
+ const pathStat = fs.statSync(ptsName);
285
+ if (
286
+ !openedStat.isCharacterDevice() ||
287
+ !pathStat.isCharacterDevice() ||
288
+ String(openedStat.dev) !== String(originalStat.dev) ||
289
+ String(openedStat.rdev) !== String(originalStat.rdev) ||
290
+ String(pathStat.dev) !== String(originalStat.dev) ||
291
+ String(pathStat.rdev) !== String(originalStat.rdev)
292
+ ) {
293
+ throw new Error("pty_slave_identity_changed");
294
+ }
295
+ fs.closeSync(originalFd);
296
+ state.rawSlaveFd = null;
297
+ state.blockingSlaveFd = fd;
298
+ blockingSlaveOwners.set(fd, state);
299
+ return fd;
300
+ } catch (error) {
301
+ fs.closeSync(fd);
302
+ throw error;
303
+ }
304
+ },
305
+ closeFd(fd) {
306
+ fs.closeSync(fd);
307
+ const state = blockingSlaveOwners.get(fd);
308
+ if (state) {
309
+ state.blockingSlaveFd = null;
310
+ blockingSlaveOwners.delete(fd);
311
+ }
312
+ },
313
+ getCols(terminal) {
314
+ return terminalState(terminal).cols;
315
+ },
316
+ getRows(terminal) {
317
+ return terminalState(terminal).rows;
318
+ },
319
+ onData(terminal, listener) {
320
+ const master = terminal?.master;
321
+ if (typeof master?.on !== "function") {
322
+ throw new Error("pty_master_stream_unavailable");
323
+ }
324
+ master.on("data", listener);
325
+ return {
326
+ dispose() {
327
+ if (typeof master.off === "function") {
328
+ master.off("data", listener);
329
+ } else {
330
+ master.removeListener?.("data", listener);
331
+ }
332
+ },
333
+ };
334
+ },
335
+ write(terminal, data) {
336
+ const state = terminalState(terminal);
337
+ if (!hasLiveMaster(state)) {
338
+ invalidateMasterState(state);
339
+ return;
340
+ }
341
+ const buffer =
342
+ typeof data === "string"
343
+ ? Buffer.from(data, state.encoding)
344
+ : Buffer.from(data);
345
+ if (buffer.byteLength === 0) return;
346
+ if (
347
+ buffer.byteLength > MAX_PENDING_WRITE_BYTES ||
348
+ state.pendingWriteBytes > MAX_PENDING_WRITE_BYTES - buffer.byteLength
349
+ ) {
350
+ const error = new Error("pty_write_queue_limit_exceeded");
351
+ error.code = "ERR_PTY_WRITE_BACKPRESSURE";
352
+ throw error;
353
+ }
354
+ state.writeQueue.push({ buffer, offset: 0 });
355
+ state.pendingWriteBytes += buffer.byteLength;
356
+ processWriteQueue(state);
357
+ },
358
+ resize(terminal, cols, rows) {
359
+ const state = terminalState(terminal);
360
+ if (!hasLiveMaster(state)) {
361
+ invalidateMasterState(state);
362
+ const error = new Error("pty_terminal_closed");
363
+ error.code = "ERR_PTY_TERMINAL_CLOSED";
364
+ throw error;
365
+ }
366
+ if (
367
+ !Number.isInteger(cols) ||
368
+ cols <= 0 ||
369
+ cols > 65_535 ||
370
+ !Number.isInteger(rows) ||
371
+ rows <= 0 ||
372
+ rows > 65_535
373
+ ) {
374
+ throw new TypeError("pty_dimensions_invalid");
375
+ }
376
+ try {
377
+ state.native.resize(state.fd, cols, rows);
378
+ } catch (error) {
379
+ invalidateMasterState(state);
380
+ throw error;
381
+ }
382
+ state.cols = cols;
383
+ state.rows = rows;
384
+ },
385
+ clear() {},
386
+ pause(terminal) {
387
+ return terminal?.master?.pause?.();
388
+ },
389
+ resume(terminal) {
390
+ return terminal?.master?.resume?.();
391
+ },
392
+ setEncoding(terminal, encoding) {
393
+ const master = terminal?.master;
394
+ if (master?._decoder) {
395
+ delete master._decoder;
396
+ }
397
+ if (encoding) {
398
+ master?.setEncoding?.(encoding);
399
+ }
400
+ },
401
+ releaseTerminal(terminal) {
402
+ const state = terminalStates.get(terminal);
403
+ if (state) {
404
+ invalidateMasterState(state);
405
+ if (Number.isInteger(state.rawSlaveFd)) {
406
+ try {
407
+ fs.closeSync(state.rawSlaveFd);
408
+ } catch {
409
+ // Closing the master below remains authoritative.
410
+ }
411
+ state.rawSlaveFd = null;
412
+ }
413
+ if (Number.isInteger(state.blockingSlaveFd)) {
414
+ try {
415
+ fs.closeSync(state.blockingSlaveFd);
416
+ } catch {
417
+ // The descriptor may already have been closed by the launch path.
418
+ }
419
+ blockingSlaveOwners.delete(state.blockingSlaveFd);
420
+ state.blockingSlaveFd = null;
421
+ }
422
+ }
423
+ closeStream(terminal?.master);
424
+ },
425
+ };
426
+ }
427
+
428
+ function wrapSandboxedPty(terminal, child, command, ptyAdapter) {
429
+ const exitEmitter = new EventEmitter();
430
+ let exitState = null;
431
+ let closed = false;
432
+ const finish = (exitCode, signal) => {
433
+ if (closed) return;
434
+ closed = true;
435
+ exitState = {
436
+ exitCode: Number.isInteger(exitCode) ? exitCode : 1,
437
+ signal: signal ?? null,
438
+ };
439
+ exitEmitter.emit("exit", exitState);
440
+ };
441
+ child.once("exit", finish);
442
+ child.once("error", () => finish(1, null));
443
+
444
+ return {
445
+ pid: child.pid,
446
+ process: command,
447
+ childProcess: child,
448
+ get cols() {
449
+ return ptyAdapter.getCols(terminal);
450
+ },
451
+ get rows() {
452
+ return ptyAdapter.getRows(terminal);
453
+ },
454
+ onData(listener) {
455
+ return ptyAdapter.onData(terminal, listener);
456
+ },
457
+ onExit(listener) {
458
+ if (exitState) {
459
+ queueMicrotask(() => listener(exitState));
460
+ return { dispose() {} };
461
+ }
462
+ exitEmitter.once("exit", listener);
463
+ return {
464
+ dispose() {
465
+ exitEmitter.off("exit", listener);
466
+ },
467
+ };
468
+ },
469
+ write(data) {
470
+ return ptyAdapter.write(terminal, data);
471
+ },
472
+ resize(cols, rows) {
473
+ if (closed) {
474
+ const error = new Error("pty_terminal_closed");
475
+ error.code = "ERR_PTY_TERMINAL_CLOSED";
476
+ throw error;
477
+ }
478
+ return ptyAdapter.resize(terminal, cols, rows);
479
+ },
480
+ clear() {
481
+ return ptyAdapter.clear(terminal);
482
+ },
483
+ pause() {
484
+ return ptyAdapter.pause(terminal);
485
+ },
486
+ resume() {
487
+ return ptyAdapter.resume(terminal);
488
+ },
489
+ setEncoding(encoding) {
490
+ return ptyAdapter.setEncoding(terminal, encoding);
491
+ },
492
+ kill(signal = "SIGHUP") {
493
+ return child.kill(signal);
494
+ },
495
+ destroy() {
496
+ return child.kill("SIGHUP");
497
+ },
498
+ };
88
499
  }
89
500
 
90
501
  class ProcessExecutionBroker extends EventEmitter {
@@ -124,6 +535,8 @@ class ProcessExecutionBroker extends EventEmitter {
124
535
  applySandbox: _applySandbox,
125
536
  postSpawnSandbox: _postSpawnSandbox,
126
537
  };
538
+ this._ptyAdapter = createNativePtyAdapter();
539
+ this._hooksEventSink = null;
127
540
 
128
541
  this._ensureLogDir();
129
542
  this._loadPermissions();
@@ -271,6 +684,25 @@ class ProcessExecutionBroker extends EventEmitter {
271
684
  },
272
685
  );
273
686
  };
687
+ if (raw?.kind === LINUX_GENERIC_CONTRACT_KIND) {
688
+ const admitted = admitLinuxGenericSandboxExecutionContract(raw, {
689
+ origin: options.origin || "unknown",
690
+ command: launch?.command,
691
+ args: launch?.args,
692
+ cwd: options.cwd || process.cwd(),
693
+ shell: options.shell,
694
+ sync: launch?.sync === true,
695
+ pty: launch?.pty === true,
696
+ stdio: options.stdio,
697
+ requiredBoundaries,
698
+ });
699
+ if (!admitted) {
700
+ return invalid(
701
+ "sandboxExecutionContract was not issued for this workspace launch provenance",
702
+ );
703
+ }
704
+ return admitted;
705
+ }
274
706
  if (
275
707
  !consumeIssuedPluginSandboxExecutionContract(raw, {
276
708
  origin: options.origin,
@@ -315,6 +747,7 @@ class ProcessExecutionBroker extends EventEmitter {
315
747
  ![
316
748
  "strict-plugin-node-bin",
317
749
  "strict-plugin-native-static-elf-bin",
750
+ "strict-plugin-native-elf-bin",
318
751
  ].includes(raw.kind)
319
752
  ) {
320
753
  return invalid("unsupported sandboxExecutionContract kind or version");
@@ -407,6 +840,58 @@ class ProcessExecutionBroker extends EventEmitter {
407
840
  });
408
841
  }
409
842
 
843
+ /**
844
+ * Issue a private one-launch Linux workspace authority for trusted host
845
+ * call-sites. The writable root is a positional host value, never read from
846
+ * a manifest/spawn option. Unsupported platforms and mixed boundary sets
847
+ * return null so the normal fail-closed adapter path remains authoritative.
848
+ */
849
+ issueLinuxWorkspaceSandboxExecutionContract(
850
+ command,
851
+ args = [],
852
+ options = {},
853
+ trustedWorkspaceRoot = process.cwd(),
854
+ { sync = false, pty = false } = {},
855
+ ) {
856
+ const requiredBoundaries = [
857
+ ...new Set([
858
+ ...(options.requiredBoundaries || []),
859
+ ...(options.sandboxPolicy?.requiredBoundaries || []),
860
+ ]),
861
+ ];
862
+ if (
863
+ process.platform !== "linux" ||
864
+ requiredBoundaries.length === 0 ||
865
+ (options.shell !== undefined &&
866
+ options.shell !== null &&
867
+ options.shell !== false) ||
868
+ requiredBoundaries.some(
869
+ (boundary) =>
870
+ boundary !== SANDBOX_BOUNDARIES.FILESYSTEM &&
871
+ boundary !== SANDBOX_BOUNDARIES.NETWORK,
872
+ )
873
+ ) {
874
+ return null;
875
+ }
876
+ return issueLinuxGenericSandboxExecutionContract({
877
+ origin: options.origin || "unknown",
878
+ command,
879
+ args,
880
+ cwd: options.cwd || trustedWorkspaceRoot,
881
+ workspaceRoot: trustedWorkspaceRoot,
882
+ shell: options.shell,
883
+ sync,
884
+ pty,
885
+ stdio: options.stdio,
886
+ requiredBoundaries,
887
+ detached: options.detached,
888
+ uid: options.uid,
889
+ gid: options.gid,
890
+ argv0: options.argv0,
891
+ serialization: options.serialization,
892
+ });
893
+ }
894
+
410
895
  _stripPluginControlOptions(options) {
411
896
  delete options.pluginId;
412
897
  delete options.pluginVersion;
@@ -636,6 +1121,10 @@ class ProcessExecutionBroker extends EventEmitter {
636
1121
  );
637
1122
  }
638
1123
  let runtimeProbe = null;
1124
+ let genericWorkspaceProbe = false;
1125
+ let sanitizedFilesystemPolicy = null;
1126
+ let sanitizedNetworkPolicy = null;
1127
+ let sanitizedPtyPolicy = null;
639
1128
  if (plan.runtimeProbe !== null && plan.runtimeProbe !== undefined) {
640
1129
  if (
641
1130
  typeof plan.runtimeProbe !== "object" ||
@@ -659,6 +1148,9 @@ class ProcessExecutionBroker extends EventEmitter {
659
1148
  "pluginTreeContentSnapshotScope",
660
1149
  "pluginTreeContentSnapshotMechanism",
661
1150
  "pluginTreeSnapshotConsistency",
1151
+ "initialDynamicLoadClosureScope",
1152
+ "initialDynamicLoadClosureMechanism",
1153
+ "initialDynamicInterpreter",
662
1154
  ]) {
663
1155
  if (
664
1156
  plan.runtimeProbe[field] !== undefined &&
@@ -677,6 +1169,7 @@ class ProcessExecutionBroker extends EventEmitter {
677
1169
  "pluginTreeContentSnapshot",
678
1170
  "pluginTreeSnapshotContractBound",
679
1171
  "pluginTreeSnapshotAtomic",
1172
+ "initialDynamicLoadClosureDescriptorBound",
680
1173
  ]) {
681
1174
  if (
682
1175
  plan.runtimeProbe[field] !== undefined &&
@@ -691,6 +1184,8 @@ class ProcessExecutionBroker extends EventEmitter {
691
1184
  for (const field of [
692
1185
  "pluginTreeContentSnapshotFiles",
693
1186
  "pluginTreeContentSnapshotBytes",
1187
+ "initialDynamicDependencyCount",
1188
+ "initialDynamicRuntimeFileCount",
694
1189
  ]) {
695
1190
  if (
696
1191
  plan.runtimeProbe[field] !== undefined &&
@@ -716,6 +1211,19 @@ class ProcessExecutionBroker extends EventEmitter {
716
1211
  "Sandbox runtime probe pluginTreeContentSnapshotDigest must be a lowercase SHA-256 value",
717
1212
  );
718
1213
  }
1214
+ if (
1215
+ plan.runtimeProbe.initialDynamicLoadClosureDigest !== undefined &&
1216
+ (typeof plan.runtimeProbe.initialDynamicLoadClosureDigest !==
1217
+ "string" ||
1218
+ !/^[a-f0-9]{64}$/.test(
1219
+ plan.runtimeProbe.initialDynamicLoadClosureDigest,
1220
+ ))
1221
+ ) {
1222
+ throw this._sandboxError(
1223
+ "invalid_sandbox_plan",
1224
+ "Sandbox runtime probe initialDynamicLoadClosureDigest must be a lowercase SHA-256 value",
1225
+ );
1226
+ }
719
1227
  const pluginTreeSnapshot =
720
1228
  plan.runtimeProbe.pluginTreeContentSnapshot === true;
721
1229
  const pluginTreeEvidenceFields = [
@@ -899,6 +1407,329 @@ class ProcessExecutionBroker extends EventEmitter {
899
1407
  );
900
1408
  }
901
1409
  }
1410
+ const initialDynamicLoadClosureBound =
1411
+ plan.runtimeProbe.initialDynamicLoadClosureDescriptorBound === true;
1412
+ const initialDynamicEvidenceFields = [
1413
+ "initialDynamicLoadClosureScope",
1414
+ "initialDynamicLoadClosureMechanism",
1415
+ "initialDynamicInterpreter",
1416
+ "initialDynamicDependencyCount",
1417
+ "initialDynamicRuntimeFileCount",
1418
+ "initialDynamicLoadClosureDigest",
1419
+ ];
1420
+ const initialDynamicInterpreter =
1421
+ plan.runtimeProbe.initialDynamicInterpreter;
1422
+ const initialDynamicDependencyCount =
1423
+ plan.runtimeProbe.initialDynamicDependencyCount;
1424
+ const initialDynamicRuntimeFileCount =
1425
+ plan.runtimeProbe.initialDynamicRuntimeFileCount;
1426
+ const successfulDynamicProbe =
1427
+ plan.applied === true &&
1428
+ plan.runtimeProbe.kind ===
1429
+ "linux-bwrap-plugin-native-dynamic-elf-policy-v1" &&
1430
+ plan.runtimeProbe.runnable === true;
1431
+ if (
1432
+ initialDynamicLoadClosureBound &&
1433
+ (plan.applied !== true ||
1434
+ plan.platform !== "linux" ||
1435
+ plan.enforcement !== "linux-bwrap" ||
1436
+ backend !== "linux-bwrap" ||
1437
+ candidateBackend !== null ||
1438
+ policyAttested !== true ||
1439
+ policyDigest === null ||
1440
+ !guarantees.includes(SANDBOX_BOUNDARIES.FILESYSTEM) ||
1441
+ !guarantees.includes(SANDBOX_BOUNDARIES.NETWORK) ||
1442
+ plan.runtimeProbe.attempted !== true ||
1443
+ plan.runtimeProbe.runnable !== true ||
1444
+ plan.runtimeProbe.reason !== null ||
1445
+ plan.runtimeProbe.kind !==
1446
+ "linux-bwrap-plugin-native-dynamic-elf-policy-v1" ||
1447
+ plan.runtimeProbe.probeRuntime !== "node" ||
1448
+ plan.runtimeProbe.targetRuntime !== "native-dynamic-elf" ||
1449
+ plan.runtimeProbe.contentSnapshot !== true ||
1450
+ plan.runtimeProbe.contentSnapshotScope !==
1451
+ "plugin-entry-executable" ||
1452
+ plan.runtimeProbe.contentSnapshotMechanism !==
1453
+ "verified-o_tmpfile-copy-bwrap-ro-bind-data-v1" ||
1454
+ plan.runtimeProbe.handleAtomic !== false ||
1455
+ !supervisorBound ||
1456
+ plan.runtimeProbe.initialDynamicLoadClosureScope !==
1457
+ "initial-pt_interp-and-direct-dt_needed-attested-system-set" ||
1458
+ plan.runtimeProbe.initialDynamicLoadClosureMechanism !==
1459
+ "parsed-elf-direct-system-set-to-attested-node-runtime-fds-v1" ||
1460
+ typeof initialDynamicInterpreter !== "string" ||
1461
+ !path.posix.isAbsolute(initialDynamicInterpreter) ||
1462
+ path.posix.normalize(initialDynamicInterpreter) !==
1463
+ initialDynamicInterpreter ||
1464
+ !["/lib/", "/lib64/", "/usr/lib/", "/usr/lib64/"].some((prefix) =>
1465
+ initialDynamicInterpreter.startsWith(prefix),
1466
+ ) ||
1467
+ !Number.isSafeInteger(initialDynamicDependencyCount) ||
1468
+ initialDynamicDependencyCount < 0 ||
1469
+ initialDynamicDependencyCount > 128 ||
1470
+ !Number.isSafeInteger(initialDynamicRuntimeFileCount) ||
1471
+ initialDynamicRuntimeFileCount < 1 ||
1472
+ initialDynamicRuntimeFileCount > 129 ||
1473
+ initialDynamicRuntimeFileCount <
1474
+ Math.max(1, initialDynamicDependencyCount) ||
1475
+ initialDynamicRuntimeFileCount > initialDynamicDependencyCount + 1 ||
1476
+ !/^[a-f0-9]{64}$/.test(
1477
+ plan.runtimeProbe.initialDynamicLoadClosureDigest || "",
1478
+ ))
1479
+ ) {
1480
+ throw this._sandboxError(
1481
+ "invalid_sandbox_plan",
1482
+ "Sandbox runtime probe initial dynamic direct system set evidence must use the typed descriptor-bound contract",
1483
+ );
1484
+ }
1485
+ if (
1486
+ !initialDynamicLoadClosureBound &&
1487
+ initialDynamicEvidenceFields.some(
1488
+ (field) => plan.runtimeProbe[field] !== undefined,
1489
+ )
1490
+ ) {
1491
+ throw this._sandboxError(
1492
+ "invalid_sandbox_plan",
1493
+ "Sandbox runtime probe initial dynamic direct system set evidence requires initialDynamicLoadClosureDescriptorBound",
1494
+ );
1495
+ }
1496
+ if (successfulDynamicProbe && !initialDynamicLoadClosureBound) {
1497
+ throw this._sandboxError(
1498
+ "invalid_sandbox_plan",
1499
+ "Sandbox runtime probe successful dynamic ELF evidence requires a descriptor-bound initial direct system set",
1500
+ );
1501
+ }
1502
+ const genericWorkspaceEvidenceFields = [
1503
+ "contractDigest",
1504
+ "policyDigest",
1505
+ "mountTopologyDigest",
1506
+ "sourceMountSetDigest",
1507
+ "emptyRoot",
1508
+ "undeclaredRootReadOnly",
1509
+ "workspaceReadWrite",
1510
+ "workspaceMountTopologyAttested",
1511
+ "workspaceRootAliasAttested",
1512
+ "anonymousDevWritable",
1513
+ "systemReadOnly",
1514
+ "hostHomeHidden",
1515
+ "outsideMarkerHidden",
1516
+ "networkNamespace",
1517
+ "networkNamespaceChanged",
1518
+ "socketCreationDenied",
1519
+ "descriptorMounts",
1520
+ "mountTopologyAtomic",
1521
+ "sourceMountPropagationPrivateAtAttestation",
1522
+ ];
1523
+ for (const field of [
1524
+ "contractDigest",
1525
+ "policyDigest",
1526
+ "mountTopologyDigest",
1527
+ "sourceMountSetDigest",
1528
+ ]) {
1529
+ if (
1530
+ plan.runtimeProbe[field] !== undefined &&
1531
+ (typeof plan.runtimeProbe[field] !== "string" ||
1532
+ !/^[a-f0-9]{64}$/.test(plan.runtimeProbe[field]))
1533
+ ) {
1534
+ throw this._sandboxError(
1535
+ "invalid_sandbox_plan",
1536
+ `Sandbox runtime probe ${field} must be a lowercase SHA-256 value`,
1537
+ );
1538
+ }
1539
+ }
1540
+ for (const field of genericWorkspaceEvidenceFields.slice(4)) {
1541
+ if (
1542
+ plan.runtimeProbe[field] !== undefined &&
1543
+ typeof plan.runtimeProbe[field] !== "boolean"
1544
+ ) {
1545
+ throw this._sandboxError(
1546
+ "invalid_sandbox_plan",
1547
+ `Sandbox runtime probe ${field} must be boolean`,
1548
+ );
1549
+ }
1550
+ }
1551
+ const genericWorkspaceKind =
1552
+ plan.runtimeProbe.kind === "linux-bwrap-generic-workspace-policy-v1";
1553
+ if (
1554
+ !genericWorkspaceKind &&
1555
+ genericWorkspaceEvidenceFields.some(
1556
+ (field) => plan.runtimeProbe[field] !== undefined,
1557
+ )
1558
+ ) {
1559
+ throw this._sandboxError(
1560
+ "invalid_sandbox_plan",
1561
+ "Linux generic workspace evidence requires its typed runtime probe kind",
1562
+ );
1563
+ }
1564
+ if (genericWorkspaceKind) {
1565
+ const filesystemPolicy = plan.filesystemPolicy;
1566
+ const networkPolicy = plan.networkPolicy;
1567
+ const ptyPolicy = plan.ptyPolicy ?? null;
1568
+ const ptyPlan =
1569
+ ptyPolicy !== null &&
1570
+ typeof ptyPolicy === "object" &&
1571
+ !Array.isArray(ptyPolicy);
1572
+ const ptyPlanValid = ptyPlan
1573
+ ? ptyPolicy.mode === "dedicated-controlling-terminal" &&
1574
+ ptyPolicy.launcherPath === "/usr/bin/setsid" &&
1575
+ typeof ptyPolicy.launcherSha256 === "string" &&
1576
+ /^[a-f0-9]{64}$/.test(ptyPolicy.launcherSha256) &&
1577
+ Number.isSafeInteger(ptyPolicy.launcherBytes) &&
1578
+ ptyPolicy.launcherBytes > 0 &&
1579
+ ptyPolicy.launcherBytes <= 256 * 1024 * 1024 &&
1580
+ ptyPolicy.launcherDescriptorBound === true &&
1581
+ ptyPolicy.launcherExecutablePinned === true &&
1582
+ ptyPolicy.launcherDescriptorConsumedBeforeTarget === true &&
1583
+ ptyPolicy.launcherStagingPathHidden === true &&
1584
+ ptyPolicy.bwrapNewSession === false &&
1585
+ plan.args[0] === "--ctty" &&
1586
+ /^\/proc\/self\/fd\/\d+$/.test(plan.args[1] || "") &&
1587
+ !plan.args.includes("--new-session")
1588
+ : ptyPolicy === null &&
1589
+ plan.args[0] !== "--ctty" &&
1590
+ plan.args.includes("--new-session");
1591
+ const workspaceRoot = filesystemPolicy?.workspaceRoot;
1592
+ const workingDirectory = filesystemPolicy?.workingDirectory;
1593
+ const workspaceRelative =
1594
+ typeof workspaceRoot === "string" &&
1595
+ typeof workingDirectory === "string"
1596
+ ? path.posix.relative(workspaceRoot, workingDirectory)
1597
+ : "..";
1598
+ genericWorkspaceProbe =
1599
+ plan.applied === true &&
1600
+ plan.platform === "linux" &&
1601
+ plan.enforcement === "linux-bwrap-workspace" &&
1602
+ backend === "linux-bwrap-workspace" &&
1603
+ candidateBackend === null &&
1604
+ policyAttested === true &&
1605
+ policyDigest !== null &&
1606
+ plan.runtimeProbe.policyDigest === policyDigest &&
1607
+ /^[a-f0-9]{64}$/.test(plan.runtimeProbe.contractDigest || "") &&
1608
+ guarantees.includes(SANDBOX_BOUNDARIES.FILESYSTEM) &&
1609
+ guarantees.includes(SANDBOX_BOUNDARIES.NETWORK) &&
1610
+ plan.runtimeProbe.attempted === true &&
1611
+ plan.runtimeProbe.runnable === true &&
1612
+ plan.runtimeProbe.reason === null &&
1613
+ plan.runtimeProbe.probeRuntime === "posix-sh" &&
1614
+ plan.runtimeProbe.targetRuntime === "generic-command" &&
1615
+ plan.runtimeProbe.contentSnapshot === false &&
1616
+ plan.runtimeProbe.handleAtomic === false &&
1617
+ plan.runtimeProbe.mountTopologyAtomic === false &&
1618
+ plan.runtimeProbe.mountTopologyDigest ===
1619
+ filesystemPolicy?.mountTopologyDigest &&
1620
+ plan.runtimeProbe.sourceMountSetDigest ===
1621
+ filesystemPolicy?.sourceMountSetDigest &&
1622
+ plan.runtimeProbe.sourceMountPropagationPrivateAtAttestation ===
1623
+ true &&
1624
+ [
1625
+ "emptyRoot",
1626
+ "undeclaredRootReadOnly",
1627
+ "workspaceReadWrite",
1628
+ "workspaceMountTopologyAttested",
1629
+ "workspaceRootAliasAttested",
1630
+ "anonymousDevWritable",
1631
+ "systemReadOnly",
1632
+ "hostHomeHidden",
1633
+ "outsideMarkerHidden",
1634
+ "networkNamespace",
1635
+ "networkNamespaceChanged",
1636
+ "socketCreationDenied",
1637
+ "descriptorMounts",
1638
+ ].every((field) => plan.runtimeProbe[field] === true) &&
1639
+ typeof workspaceRoot === "string" &&
1640
+ path.posix.isAbsolute(workspaceRoot) &&
1641
+ workspaceRoot !== "/" &&
1642
+ typeof workingDirectory === "string" &&
1643
+ path.posix.isAbsolute(workingDirectory) &&
1644
+ workspaceRelative !== ".." &&
1645
+ !workspaceRelative.startsWith("../") &&
1646
+ !path.posix.isAbsolute(workspaceRelative) &&
1647
+ filesystemPolicy.workspaceAccess === "read-write" &&
1648
+ filesystemPolicy.systemAccess === "read-only" &&
1649
+ filesystemPolicy.undeclaredRootAccess === "read-only" &&
1650
+ filesystemPolicy.hostRootMapped === false &&
1651
+ filesystemPolicy.hostHomeMapped === false &&
1652
+ filesystemPolicy.workspaceDescriptorBound === true &&
1653
+ filesystemPolicy.systemDescriptorBound === true &&
1654
+ filesystemPolicy.exactEtcFileDescriptors === true &&
1655
+ filesystemPolicy.workspaceRecursiveBind === true &&
1656
+ filesystemPolicy.workspaceMountTopology ===
1657
+ "no-strict-descendants-or-forbidden-root-aliases-at-attestation" &&
1658
+ filesystemPolicy.mountTopologySource === "proc-self-mountinfo" &&
1659
+ /^[a-f0-9]{64}$/.test(filesystemPolicy.mountTopologyDigest || "") &&
1660
+ /^[a-f0-9]{64}$/.test(filesystemPolicy.sourceMountSetDigest || "") &&
1661
+ filesystemPolicy.sourceMountPropagationPrivateAtAttestation ===
1662
+ true &&
1663
+ filesystemPolicy.mountTopologyAtomic === false &&
1664
+ Array.isArray(filesystemPolicy.anonymousWritablePaths) &&
1665
+ filesystemPolicy.anonymousWritablePaths.length === 5 &&
1666
+ ["/home/sandbox", "/dev", "/run", "/tmp", "/var/tmp"].every(
1667
+ (value, index) =>
1668
+ filesystemPolicy.anonymousWritablePaths[index] === value,
1669
+ ) &&
1670
+ networkPolicy?.namespace === "new" &&
1671
+ networkPolicy?.namespaceIdentityChanged === true &&
1672
+ networkPolicy?.seccomp === "deny-network-creation" &&
1673
+ plan.command.startsWith("/proc/self/fd/") &&
1674
+ plan.options?.cwd === "/" &&
1675
+ plan.options?.shell === false &&
1676
+ Array.isArray(plan.options?.stdio) &&
1677
+ plan.args.includes("--bind-fd") &&
1678
+ plan.args.includes("--ro-bind-fd") &&
1679
+ plan.args.includes("--remount-ro") &&
1680
+ plan.args.includes("--unshare-net") &&
1681
+ plan.args.includes("--seccomp") &&
1682
+ !plan.args.includes("--ro-bind") &&
1683
+ ptyPlanValid;
1684
+ if (!genericWorkspaceProbe) {
1685
+ throw this._sandboxError(
1686
+ "invalid_sandbox_plan",
1687
+ "Linux generic workspace evidence must use the typed descriptor-bound empty-root contract",
1688
+ );
1689
+ }
1690
+ sanitizedFilesystemPolicy = Object.freeze({
1691
+ workspaceRoot,
1692
+ workingDirectory,
1693
+ workspaceAccess: filesystemPolicy.workspaceAccess,
1694
+ systemAccess: filesystemPolicy.systemAccess,
1695
+ undeclaredRootAccess: filesystemPolicy.undeclaredRootAccess,
1696
+ anonymousWritablePaths: Object.freeze([
1697
+ ...filesystemPolicy.anonymousWritablePaths,
1698
+ ]),
1699
+ hostRootMapped: filesystemPolicy.hostRootMapped,
1700
+ hostHomeMapped: filesystemPolicy.hostHomeMapped,
1701
+ workspaceDescriptorBound: filesystemPolicy.workspaceDescriptorBound,
1702
+ systemDescriptorBound: filesystemPolicy.systemDescriptorBound,
1703
+ exactEtcFileDescriptors: filesystemPolicy.exactEtcFileDescriptors,
1704
+ workspaceRecursiveBind: filesystemPolicy.workspaceRecursiveBind,
1705
+ workspaceMountTopology: filesystemPolicy.workspaceMountTopology,
1706
+ mountTopologySource: filesystemPolicy.mountTopologySource,
1707
+ mountTopologyDigest: filesystemPolicy.mountTopologyDigest,
1708
+ sourceMountSetDigest: filesystemPolicy.sourceMountSetDigest,
1709
+ sourceMountPropagationPrivateAtAttestation:
1710
+ filesystemPolicy.sourceMountPropagationPrivateAtAttestation,
1711
+ mountTopologyAtomic: filesystemPolicy.mountTopologyAtomic,
1712
+ });
1713
+ sanitizedNetworkPolicy = Object.freeze({
1714
+ namespace: networkPolicy.namespace,
1715
+ namespaceIdentityChanged: networkPolicy.namespaceIdentityChanged,
1716
+ seccomp: networkPolicy.seccomp,
1717
+ });
1718
+ sanitizedPtyPolicy = ptyPlan
1719
+ ? Object.freeze({
1720
+ mode: ptyPolicy.mode,
1721
+ launcherPath: ptyPolicy.launcherPath,
1722
+ launcherSha256: ptyPolicy.launcherSha256,
1723
+ launcherBytes: ptyPolicy.launcherBytes,
1724
+ launcherDescriptorBound: ptyPolicy.launcherDescriptorBound,
1725
+ launcherExecutablePinned: ptyPolicy.launcherExecutablePinned,
1726
+ launcherDescriptorConsumedBeforeTarget:
1727
+ ptyPolicy.launcherDescriptorConsumedBeforeTarget,
1728
+ launcherStagingPathHidden: ptyPolicy.launcherStagingPathHidden,
1729
+ bwrapNewSession: ptyPolicy.bwrapNewSession,
1730
+ })
1731
+ : null;
1732
+ }
902
1733
  runtimeProbe = {
903
1734
  kind: plan.runtimeProbe.kind,
904
1735
  attempted: plan.runtimeProbe.attempted,
@@ -944,6 +1775,19 @@ class ProcessExecutionBroker extends EventEmitter {
944
1775
  runtimeProbe[field] = plan.runtimeProbe[field];
945
1776
  }
946
1777
  }
1778
+ for (const field of [
1779
+ "initialDynamicLoadClosureDescriptorBound",
1780
+ ...initialDynamicEvidenceFields,
1781
+ ]) {
1782
+ if (plan.runtimeProbe[field] !== undefined) {
1783
+ runtimeProbe[field] = plan.runtimeProbe[field];
1784
+ }
1785
+ }
1786
+ for (const field of genericWorkspaceEvidenceFields) {
1787
+ if (plan.runtimeProbe[field] !== undefined) {
1788
+ runtimeProbe[field] = plan.runtimeProbe[field];
1789
+ }
1790
+ }
947
1791
  if (supervisorIdentity !== undefined) {
948
1792
  runtimeProbe.supervisorExecutableIdentity = {
949
1793
  path: supervisorIdentity.path,
@@ -960,6 +1804,12 @@ class ProcessExecutionBroker extends EventEmitter {
960
1804
  };
961
1805
  }
962
1806
  }
1807
+ if (backend === "linux-bwrap-workspace" && !genericWorkspaceProbe) {
1808
+ throw this._sandboxError(
1809
+ "invalid_sandbox_plan",
1810
+ "Applied Linux generic workspace plans require typed runtime evidence",
1811
+ );
1812
+ }
963
1813
  const postSpawn = plan.postSpawn || { required: false, mode: "none" };
964
1814
  if (
965
1815
  postSpawn.required &&
@@ -981,6 +1831,13 @@ class ProcessExecutionBroker extends EventEmitter {
981
1831
  policyAttested,
982
1832
  policyDigest,
983
1833
  runtimeProbe,
1834
+ ...(sanitizedFilesystemPolicy
1835
+ ? {
1836
+ filesystemPolicy: sanitizedFilesystemPolicy,
1837
+ networkPolicy: sanitizedNetworkPolicy,
1838
+ ptyPolicy: sanitizedPtyPolicy,
1839
+ }
1840
+ : {}),
984
1841
  postSpawn: { ...postSpawn },
985
1842
  };
986
1843
  }
@@ -992,9 +1849,18 @@ class ProcessExecutionBroker extends EventEmitter {
992
1849
  (boundary) => !guaranteed.has(boundary),
993
1850
  );
994
1851
  if (missingBoundaries.length > 0) {
1852
+ const candidateReason =
1853
+ typeof plan.reason === "string" && plan.reason
1854
+ ? `; reason=${plan.reason}${
1855
+ typeof plan.runtimeProbe?.reason === "string" &&
1856
+ plan.runtimeProbe.reason
1857
+ ? `:${plan.runtimeProbe.reason}`
1858
+ : ""
1859
+ }`
1860
+ : "";
995
1861
  throw this._sandboxBoundaryError(
996
1862
  "required_boundaries_unsatisfied",
997
- `Sandbox backend ${plan.backend || plan.candidateBackend || "unavailable"} cannot satisfy required boundaries: ${missingBoundaries.join(", ")}`,
1863
+ `Sandbox backend ${plan.backend || plan.candidateBackend || "unavailable"} cannot satisfy required boundaries: ${missingBoundaries.join(", ")}${candidateReason}`,
998
1864
  {
999
1865
  requiredBoundaries,
1000
1866
  actualGuarantees,
@@ -1018,7 +1884,7 @@ class ProcessExecutionBroker extends EventEmitter {
1018
1884
  command,
1019
1885
  args,
1020
1886
  options,
1021
- { sync = false, sandboxPolicy = {} } = {},
1887
+ { sync = false, pty = false, sandboxPolicy = {} } = {},
1022
1888
  ) {
1023
1889
  const strict = this._sandboxStrictEnabled();
1024
1890
  const requiredBoundaries = sandboxPolicy.requiredBoundaries || [];
@@ -1057,6 +1923,7 @@ class ProcessExecutionBroker extends EventEmitter {
1057
1923
  profile,
1058
1924
  requiredBoundaries: Object.freeze([...requiredBoundaries]),
1059
1925
  sync,
1926
+ ...(pty ? { pty: true } : {}),
1060
1927
  ...(sandboxPolicy.executionContract
1061
1928
  ? { executionContract: sandboxPolicy.executionContract }
1062
1929
  : {}),
@@ -1135,6 +2002,57 @@ class ProcessExecutionBroker extends EventEmitter {
1135
2002
  auditEntry.sandboxPolicyAttested =
1136
2003
  typeof plan?.policyAttested === "boolean" ? plan.policyAttested : null;
1137
2004
  auditEntry.sandboxPolicyDigest = plan?.policyDigest || null;
2005
+ const genericPolicy =
2006
+ applied === true && plan?.backend === "linux-bwrap-workspace";
2007
+ auditEntry.sandboxFilesystemPolicy = genericPolicy
2008
+ ? {
2009
+ workspaceRoot: plan.filesystemPolicy.workspaceRoot,
2010
+ workingDirectory: plan.filesystemPolicy.workingDirectory,
2011
+ workspaceAccess: plan.filesystemPolicy.workspaceAccess,
2012
+ systemAccess: plan.filesystemPolicy.systemAccess,
2013
+ undeclaredRootAccess: plan.filesystemPolicy.undeclaredRootAccess,
2014
+ anonymousWritablePaths: [
2015
+ ...plan.filesystemPolicy.anonymousWritablePaths,
2016
+ ],
2017
+ hostRootMapped: plan.filesystemPolicy.hostRootMapped,
2018
+ hostHomeMapped: plan.filesystemPolicy.hostHomeMapped,
2019
+ workspaceDescriptorBound:
2020
+ plan.filesystemPolicy.workspaceDescriptorBound,
2021
+ systemDescriptorBound: plan.filesystemPolicy.systemDescriptorBound,
2022
+ exactEtcFileDescriptors:
2023
+ plan.filesystemPolicy.exactEtcFileDescriptors,
2024
+ workspaceRecursiveBind: plan.filesystemPolicy.workspaceRecursiveBind,
2025
+ workspaceMountTopology: plan.filesystemPolicy.workspaceMountTopology,
2026
+ mountTopologySource: plan.filesystemPolicy.mountTopologySource,
2027
+ mountTopologyDigest: plan.filesystemPolicy.mountTopologyDigest,
2028
+ sourceMountSetDigest: plan.filesystemPolicy.sourceMountSetDigest,
2029
+ sourceMountPropagationPrivateAtAttestation:
2030
+ plan.filesystemPolicy.sourceMountPropagationPrivateAtAttestation,
2031
+ mountTopologyAtomic: plan.filesystemPolicy.mountTopologyAtomic,
2032
+ }
2033
+ : null;
2034
+ auditEntry.sandboxNetworkPolicy = genericPolicy
2035
+ ? {
2036
+ namespace: plan.networkPolicy.namespace,
2037
+ namespaceIdentityChanged: plan.networkPolicy.namespaceIdentityChanged,
2038
+ seccomp: plan.networkPolicy.seccomp,
2039
+ }
2040
+ : null;
2041
+ auditEntry.sandboxPtyPolicy =
2042
+ genericPolicy && plan.ptyPolicy
2043
+ ? {
2044
+ mode: plan.ptyPolicy.mode,
2045
+ launcherPath: plan.ptyPolicy.launcherPath,
2046
+ launcherSha256: plan.ptyPolicy.launcherSha256,
2047
+ launcherBytes: plan.ptyPolicy.launcherBytes,
2048
+ launcherDescriptorBound: plan.ptyPolicy.launcherDescriptorBound,
2049
+ launcherExecutablePinned: plan.ptyPolicy.launcherExecutablePinned,
2050
+ launcherDescriptorConsumedBeforeTarget:
2051
+ plan.ptyPolicy.launcherDescriptorConsumedBeforeTarget,
2052
+ launcherStagingPathHidden: plan.ptyPolicy.launcherStagingPathHidden,
2053
+ bwrapNewSession: plan.ptyPolicy.bwrapNewSession,
2054
+ }
2055
+ : null;
1138
2056
  auditEntry.sandboxCandidateReason = plan?.candidateBackend
1139
2057
  ? plan?.reason || null
1140
2058
  : null;
@@ -1396,12 +2314,13 @@ class ProcessExecutionBroker extends EventEmitter {
1396
2314
 
1397
2315
  _writeRplEntry(auditEntry, status = "started", error = null) {
1398
2316
  const rpl = getRpl();
1399
- if (!rpl || !rpl.default) return;
2317
+ if (!rpl) return;
1400
2318
  try {
1401
2319
  const traceCtx = this._getTraceContext();
1402
- rpl.default.append(
2320
+ if (typeof rpl.record !== "function") return;
2321
+ rpl.record(
2322
+ "process.execution",
1403
2323
  {
1404
- kind: "process.execution",
1405
2324
  component: auditEntry.origin,
1406
2325
  action: "spawn",
1407
2326
  artifactId: auditEntry.executionId,
@@ -1422,6 +2341,15 @@ class ProcessExecutionBroker extends EventEmitter {
1422
2341
  policy: auditEntry.policy,
1423
2342
  scope: auditEntry.scope,
1424
2343
  },
2344
+ sandbox: {
2345
+ applied: auditEntry.sandboxed === true,
2346
+ backend: auditEntry.sandboxBackend || null,
2347
+ guarantees: [...(auditEntry.sandboxGuarantees || [])],
2348
+ policyAttested: auditEntry.sandboxPolicyAttested === true,
2349
+ policyDigest: auditEntry.sandboxPolicyDigest || null,
2350
+ filesystemPolicy: auditEntry.sandboxFilesystemPolicy || null,
2351
+ networkPolicy: auditEntry.sandboxNetworkPolicy || null,
2352
+ },
1425
2353
  traceId:
1426
2354
  traceCtx?.traceId || auditEntry.traceId || `trace-${Date.now()}`,
1427
2355
  parentSpanId: traceCtx?.spanId || null,
@@ -1436,15 +2364,22 @@ class ProcessExecutionBroker extends EventEmitter {
1436
2364
  }
1437
2365
 
1438
2366
  _emitHooksEvent(event, data) {
1439
- const hooks = getHooksV2();
1440
- if (!hooks || !hooks.hooksV2) return;
2367
+ const hooks = this._hooksEventSink;
2368
+ if (!hooks) return;
1441
2369
  try {
1442
- hooks.hooksV2.emit(event, data);
2370
+ hooks.emit(event, data);
1443
2371
  } catch {
1444
2372
  // Hook reporting must not hide the process execution result.
1445
2373
  }
1446
2374
  }
1447
2375
 
2376
+ _setHooksEventSink(hooks) {
2377
+ if (hooks !== null && typeof hooks?.emit !== "function") {
2378
+ throw new TypeError("hooks event sink must expose emit()");
2379
+ }
2380
+ this._hooksEventSink = hooks;
2381
+ }
2382
+
1448
2383
  spawn(command, args, options = {}) {
1449
2384
  const executionId = crypto.randomUUID();
1450
2385
  const startTime = Date.now();
@@ -1607,6 +2542,20 @@ class ProcessExecutionBroker extends EventEmitter {
1607
2542
  proc,
1608
2543
  sandboxPlan.cleanup,
1609
2544
  );
2545
+ if (
2546
+ sandboxPlan.applied === true &&
2547
+ (sandboxPlan.backend === "linux-bwrap-workspace" ||
2548
+ sandboxPlan.backend === "linux-bwrap") &&
2549
+ sandboxPlan.postSpawn.required === false
2550
+ ) {
2551
+ // child_process.spawn() has synchronously duplicated every stdio entry
2552
+ // into the launched bwrap process before returning. Both the generic
2553
+ // workspace backend and direct Plugin-bin bwrap backend are complete
2554
+ // pre-spawn plans with no parent-side enforcement handle. Retaining
2555
+ // their pinned mount/seccomp/supervisor descriptors until a long-lived
2556
+ // process exits would turn each child into an avoidable FD leak.
2557
+ cleanupSandbox();
2558
+ }
1610
2559
  try {
1611
2560
  this._runPostSpawnSandbox(proc, sandboxPlan, auditEntry);
1612
2561
  auditEntry.pid = proc.pid;
@@ -1830,9 +2779,10 @@ class ProcessExecutionBroker extends EventEmitter {
1830
2779
 
1831
2780
  /**
1832
2781
  * Route a node-pty session through the same provenance and credential
1833
- * boundary as child_process execution. node-pty owns the native PTY
1834
- * allocation, so the platform sandbox is reported as unavailable here
1835
- * rather than being falsely marked as applied.
2782
+ * boundary as child_process execution. Policy-free sessions retain native
2783
+ * node-pty semantics. A policy-bearing Linux session uses node-pty only to
2784
+ * allocate a dedicated terminal; child_process then duplicates its slave
2785
+ * together with the descriptor-pinned generic bwrap plan.
1836
2786
  */
1837
2787
  spawnPty(ptyModule, command, args = [], options = {}) {
1838
2788
  if (!ptyModule || typeof ptyModule.spawn !== "function") {
@@ -1874,6 +2824,7 @@ class ProcessExecutionBroker extends EventEmitter {
1874
2824
  command,
1875
2825
  args,
1876
2826
  sync: false,
2827
+ pty: true,
1877
2828
  });
1878
2829
  auditEntry.sandboxRequired = [...sandboxPolicy.requiredBoundaries];
1879
2830
  } catch (sandboxPolicyError) {
@@ -1895,27 +2846,19 @@ class ProcessExecutionBroker extends EventEmitter {
1895
2846
  error.auditEntry = auditEntry;
1896
2847
  throw error;
1897
2848
  }
1898
- if (sandboxPolicy.requiredBoundaries.length > 0) {
1899
- const sandboxError = this._sandboxBoundaryError(
1900
- "required_boundaries_unsatisfied",
1901
- `Native PTY host cannot satisfy required boundaries: ${sandboxPolicy.requiredBoundaries.join(", ")}`,
1902
- {
1903
- requiredBoundaries: sandboxPolicy.requiredBoundaries,
1904
- missingBoundaries: sandboxPolicy.requiredBoundaries,
1905
- },
1906
- );
1907
- this._recordSandboxDenial(auditEntry, sandboxError, startTime);
1908
- sandboxError.auditEntry = auditEntry;
1909
- throw sandboxError;
1910
- }
1911
2849
 
1912
2850
  const spawnOptions = { ...options, args: [...auditEntry.args] };
1913
2851
  this._stripSandboxControlOptions(spawnOptions);
1914
2852
  this._stripPluginControlOptions(spawnOptions);
1915
- delete spawnOptions.origin;
1916
- delete spawnOptions.policy;
1917
- delete spawnOptions.scope;
1918
2853
  try {
2854
+ const terminalName =
2855
+ typeof options.name === "string" && options.name
2856
+ ? options.name
2857
+ : "xterm";
2858
+ spawnOptions.env = {
2859
+ ...(spawnOptions.env || process.env),
2860
+ TERM: terminalName,
2861
+ };
1919
2862
  if (this._credentialBoundaryEnabled()) {
1920
2863
  spawnOptions.credentialContext = this._createCredentialContext(
1921
2864
  command,
@@ -1937,7 +2880,170 @@ class ProcessExecutionBroker extends EventEmitter {
1937
2880
  }
1938
2881
  const filteredArgs = spawnOptions.args || [];
1939
2882
  delete spawnOptions.args;
1940
- const proc = ptyModule.spawn(command, filteredArgs, spawnOptions);
2883
+
2884
+ if (sandboxPolicy.requiredBoundaries.length === 0) {
2885
+ delete spawnOptions.origin;
2886
+ delete spawnOptions.policy;
2887
+ delete spawnOptions.scope;
2888
+ const proc = ptyModule.spawn(command, filteredArgs, spawnOptions);
2889
+ auditEntry.pid = proc?.pid ?? null;
2890
+ auditEntry.endTime = Date.now();
2891
+ auditEntry.durationMs = auditEntry.endTime - startTime;
2892
+ this._recordAudit(auditEntry);
2893
+ this._writeRplEntry(auditEntry, "started");
2894
+ return proc;
2895
+ }
2896
+
2897
+ let sandboxPlan;
2898
+ try {
2899
+ sandboxPlan = this._prepareSandboxPlan(
2900
+ command,
2901
+ filteredArgs,
2902
+ spawnOptions,
2903
+ {
2904
+ pty: true,
2905
+ sandboxPolicy,
2906
+ },
2907
+ );
2908
+ if (
2909
+ sandboxPlan.applied !== true ||
2910
+ sandboxPlan.backend !== "linux-bwrap-workspace" ||
2911
+ sandboxPlan.ptyPolicy?.mode !== "dedicated-controlling-terminal"
2912
+ ) {
2913
+ throw this._sandboxBoundaryError(
2914
+ "required_boundaries_unsatisfied",
2915
+ `PTY backend ${sandboxPlan.backend || sandboxPlan.candidateBackend || "unavailable"} cannot satisfy the dedicated terminal contract`,
2916
+ {
2917
+ requiredBoundaries: sandboxPolicy.requiredBoundaries,
2918
+ actualGuarantees: sandboxPlan.guarantees,
2919
+ missingBoundaries: sandboxPolicy.requiredBoundaries.filter(
2920
+ (boundary) => !sandboxPlan.guarantees.includes(boundary),
2921
+ ),
2922
+ sandboxBackend: sandboxPlan.backend,
2923
+ sandboxCandidateBackend: sandboxPlan.candidateBackend,
2924
+ sandboxRuntimeProbe: sandboxPlan.runtimeProbe,
2925
+ sandboxPolicyAttested: sandboxPlan.policyAttested,
2926
+ sandboxPolicyDigest: sandboxPlan.policyDigest,
2927
+ },
2928
+ );
2929
+ }
2930
+ } catch (sandboxError) {
2931
+ sandboxPlan?.cleanup?.();
2932
+ this._recordSandboxDenial(auditEntry, sandboxError, startTime);
2933
+ sandboxError.auditEntry = auditEntry;
2934
+ throw sandboxError;
2935
+ }
2936
+
2937
+ let terminal;
2938
+ let slaveFd = null;
2939
+ let child;
2940
+ let proc;
2941
+ let terminalReleased = false;
2942
+ const releaseTerminal = () => {
2943
+ if (terminalReleased) return;
2944
+ terminalReleased = true;
2945
+ try {
2946
+ this._ptyAdapter.releaseTerminal(terminal);
2947
+ } catch {
2948
+ // The sandbox process exit remains authoritative.
2949
+ }
2950
+ };
2951
+ try {
2952
+ terminal = this._ptyAdapter.allocate(ptyModule, {
2953
+ cols: options.cols,
2954
+ rows: options.rows,
2955
+ encoding: options.encoding,
2956
+ });
2957
+ slaveFd = this._ptyAdapter.openBlockingSlave(terminal);
2958
+ const nativeSpawnFn = this._native?.spawn || nativeSpawn;
2959
+ child = nativeSpawnFn(sandboxPlan.command, sandboxPlan.args, {
2960
+ ...sandboxPlan.options,
2961
+ stdio: [
2962
+ slaveFd,
2963
+ slaveFd,
2964
+ slaveFd,
2965
+ ...sandboxPlan.options.stdio.slice(3),
2966
+ ],
2967
+ });
2968
+ proc = wrapSandboxedPty(terminal, child, command, this._ptyAdapter);
2969
+ } catch (ptyError) {
2970
+ if (child && typeof child.kill === "function") {
2971
+ try {
2972
+ child.kill();
2973
+ } catch {
2974
+ // Continue fail-closed cleanup after a partial child launch.
2975
+ }
2976
+ }
2977
+ if (Number.isInteger(slaveFd)) {
2978
+ try {
2979
+ this._ptyAdapter.closeFd(slaveFd);
2980
+ } catch {
2981
+ // Preserve the allocation/spawn failure.
2982
+ }
2983
+ }
2984
+ sandboxPlan.cleanup?.();
2985
+ releaseTerminal();
2986
+ const sandboxError = this._sandboxBoundaryError(
2987
+ "pty_allocation_or_spawn_failed",
2988
+ `Strong Linux PTY launch failed: ${ptyError.message}`,
2989
+ {
2990
+ requiredBoundaries: sandboxPolicy.requiredBoundaries,
2991
+ actualGuarantees: [],
2992
+ missingBoundaries: sandboxPolicy.requiredBoundaries,
2993
+ sandboxBackend: sandboxPlan.backend,
2994
+ sandboxRuntimeProbe: sandboxPlan.runtimeProbe,
2995
+ sandboxPolicyAttested: sandboxPlan.policyAttested,
2996
+ sandboxPolicyDigest: sandboxPlan.policyDigest,
2997
+ },
2998
+ );
2999
+ this._recordSandboxDenial(auditEntry, sandboxError, startTime);
3000
+ sandboxError.auditEntry = auditEntry;
3001
+ throw sandboxError;
3002
+ }
3003
+ try {
3004
+ this._ptyAdapter.closeFd(slaveFd);
3005
+ } catch {
3006
+ try {
3007
+ child.kill();
3008
+ } catch {
3009
+ // Continue fail-closed cleanup even if the partial child already died.
3010
+ }
3011
+ sandboxPlan.cleanup?.();
3012
+ releaseTerminal();
3013
+ const sandboxError = this._sandboxBoundaryError(
3014
+ "pty_parent_slave_cleanup_failed",
3015
+ "Strong Linux PTY could not close its parent-side slave descriptor",
3016
+ {
3017
+ requiredBoundaries: sandboxPolicy.requiredBoundaries,
3018
+ missingBoundaries: sandboxPolicy.requiredBoundaries,
3019
+ sandboxBackend: sandboxPlan.backend,
3020
+ },
3021
+ );
3022
+ this._recordSandboxDenial(auditEntry, sandboxError, startTime);
3023
+ sandboxError.auditEntry = auditEntry;
3024
+ throw sandboxError;
3025
+ }
3026
+ sandboxPlan.cleanup?.();
3027
+ this._applySandboxAudit(auditEntry, sandboxPlan, true);
3028
+ this._stats.sandboxed++;
3029
+
3030
+ child.once("exit", (code, signal) => {
3031
+ releaseTerminal();
3032
+ auditEntry.exitCode = code;
3033
+ auditEntry.signal = signal;
3034
+ auditEntry.endTime = Date.now();
3035
+ auditEntry.durationMs = auditEntry.endTime - startTime;
3036
+ this._writeRplEntry(auditEntry, "completed");
3037
+ this.emit("exit", auditEntry);
3038
+ });
3039
+ child.once("error", (error) => {
3040
+ releaseTerminal();
3041
+ auditEntry.error = error.message;
3042
+ auditEntry.endTime = Date.now();
3043
+ auditEntry.durationMs = auditEntry.endTime - startTime;
3044
+ this._writeRplEntry(auditEntry, "error", error);
3045
+ if (this.listenerCount("error") > 0) this.emit("error", auditEntry);
3046
+ });
1941
3047
  auditEntry.pid = proc?.pid ?? null;
1942
3048
  auditEntry.endTime = Date.now();
1943
3049
  auditEntry.durationMs = auditEntry.endTime - startTime;
@@ -1945,6 +3051,7 @@ class ProcessExecutionBroker extends EventEmitter {
1945
3051
  this._writeRplEntry(auditEntry, "started");
1946
3052
  return proc;
1947
3053
  } catch (error) {
3054
+ if (error.auditEntry === auditEntry) throw error;
1948
3055
  auditEntry.error = error.message;
1949
3056
  auditEntry.endTime = Date.now();
1950
3057
  auditEntry.durationMs = auditEntry.endTime - startTime;