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
@@ -4,10 +4,11 @@
4
4
  * Platform enforcement currently available:
5
5
  * - macOS: Seatbelt sandbox-exec profiles
6
6
  * - Linux: prlimit resource-limit wrapper, plus a narrow bubblewrap backend
7
- * for an attested direct strict Plugin Node bin or static ELF native bin.
8
- * Shells, dynamically linked binaries, broader working directories,
9
- * host-writable paths, inherited descriptors, and network egress remain
10
- * fail-closed.
7
+ * for an attested direct strict Plugin Node bin or a narrow static/dynamic
8
+ * ELF native bin. Dynamic acceptance binds PT_INTERP plus the entry's direct
9
+ * DT_NEEDED names to an attested system-file set; broader loader surfaces,
10
+ * shells, broader working directories, host-writable paths, inherited
11
+ * descriptors, and network egress remain fail-closed.
11
12
  * - Windows: Win32 Job Object + restricted-token wrapper, with an attested
12
13
  * zero-capability AppContainer for explicit filesystem/network boundaries
13
14
  *
@@ -25,6 +26,10 @@ import * as fs from "node:fs";
25
26
  import crypto from "node:crypto";
26
27
  import { spawnSync as nativeSpawnSync } from "node:child_process";
27
28
  import { fileURLToPath } from "node:url";
29
+ import {
30
+ applyLinuxGenericWorkspaceSandbox,
31
+ isLinuxGenericWorkspaceContract,
32
+ } from "./linux-generic-bwrap-runtime.js";
28
33
 
29
34
  /**
30
35
  * Stable boundary identifiers shared by broker policies, platform plans, and
@@ -169,6 +174,15 @@ const LINUX_ELF64_PROGRAM_HEADER_BYTES = 56;
169
174
  const LINUX_ELF64_DYNAMIC_ENTRY_BYTES = 16;
170
175
  const LINUX_ELF_MAX_PROGRAM_HEADERS = 128;
171
176
  const LINUX_ELF_MAX_DYNAMIC_ENTRIES = 4096;
177
+ const LINUX_ELF_MAX_NEEDED_ENTRIES = 128;
178
+ const LINUX_ELF_MAX_INTERPRETER_BYTES = 4096;
179
+ const LINUX_ELF_MAX_STRING_TABLE_BYTES = 1024 * 1024;
180
+ const LINUX_AUXV_ENTRY_BYTES = 16;
181
+ const LINUX_AUXV_MAX_BYTES = 64 * 1024;
182
+ const LINUX_AUXV_PAGE_SIZE = 6n;
183
+ const LINUX_MIN_RUNTIME_PAGE_BYTES = 4096;
184
+ const LINUX_MAX_RUNTIME_PAGE_BYTES = 1024 * 1024;
185
+ const LINUX_ELF_MAX_UINT64 = (1n << 64n) - 1n;
172
186
  const LINUX_ELF_TYPE_EXEC = 2;
173
187
  const LINUX_ELF_TYPE_DYN = 3;
174
188
  const LINUX_ELF_PROGRAM_LOAD = 1;
@@ -177,8 +191,24 @@ const LINUX_ELF_PROGRAM_INTERP = 3;
177
191
  const LINUX_ELF_PROGRAM_GNU_STACK = 0x6474e551;
178
192
  const LINUX_ELF_DYNAMIC_NULL = 0n;
179
193
  const LINUX_ELF_DYNAMIC_NEEDED = 1n;
194
+ const LINUX_ELF_DYNAMIC_STRTAB = 5n;
195
+ const LINUX_ELF_DYNAMIC_STRSZ = 10n;
196
+ const LINUX_ELF_DYNAMIC_RPATH = 15n;
197
+ const LINUX_ELF_DYNAMIC_TEXTREL = 22n;
198
+ const LINUX_ELF_DYNAMIC_FLAGS = 30n;
199
+ const LINUX_ELF_DYNAMIC_RUNPATH = 29n;
200
+ const LINUX_ELF_DYNAMIC_CONFIG = 0x6ffffefan;
201
+ const LINUX_ELF_DYNAMIC_DEPAUDIT = 0x6ffffefbn;
202
+ const LINUX_ELF_DYNAMIC_AUDIT = 0x6ffffefcn;
180
203
  const LINUX_ELF_DYNAMIC_FLAGS_1 = 0x6ffffffbn;
204
+ const LINUX_ELF_DYNAMIC_AUXILIARY = 0x7ffffffdn;
205
+ const LINUX_ELF_DYNAMIC_FILTER = 0x7fffffffn;
206
+ const LINUX_ELF_DYNAMIC_FLAG_TEXTREL = 0x4n;
181
207
  const LINUX_ELF_DYNAMIC_FLAG_PIE = 0x08000000n;
208
+ const LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_SCOPE =
209
+ "initial-pt_interp-and-direct-dt_needed-attested-system-set";
210
+ const LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_MECHANISM =
211
+ "parsed-elf-direct-system-set-to-attested-node-runtime-fds-v1";
182
212
  const LINUX_ELF_MACHINES = Object.freeze({
183
213
  x64: 62,
184
214
  arm64: 183,
@@ -2600,8 +2630,11 @@ function linuxBubblewrapProbe(
2600
2630
  contentSnapshot = null,
2601
2631
  supervisorBinding = null,
2602
2632
  pluginTreeSnapshot = null,
2633
+ nativeDynamicClosure = null,
2603
2634
  ) {
2604
- const native = targetRuntime === "native-static-elf";
2635
+ const native =
2636
+ typeof targetRuntime === "string" && targetRuntime.startsWith("native-");
2637
+ const dynamicNative = targetRuntime === "native-dynamic-elf";
2605
2638
  const expectedSnapshotScope = native
2606
2639
  ? LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE
2607
2640
  : LINUX_NODE_ENTRY_SNAPSHOT_SCOPE;
@@ -2631,10 +2664,34 @@ function linuxBubblewrapProbe(
2631
2664
  LINUX_NODE_PLUGIN_TREE_SNAPSHOT_CONSISTENCY &&
2632
2665
  pluginTreeSnapshot?.contractBound === false &&
2633
2666
  pluginTreeSnapshot?.atomic === false;
2667
+ const initialDynamicLoadClosureDescriptorBound =
2668
+ dynamicNative &&
2669
+ attempted &&
2670
+ snapshot &&
2671
+ runnable &&
2672
+ reason === null &&
2673
+ supervisorDescriptorBound &&
2674
+ nativeDynamicClosure?.mechanism ===
2675
+ LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_MECHANISM &&
2676
+ nativeDynamicClosure?.scope ===
2677
+ LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_SCOPE &&
2678
+ typeof nativeDynamicClosure?.interpreter === "string" &&
2679
+ path.posix.isAbsolute(nativeDynamicClosure.interpreter) &&
2680
+ Number.isSafeInteger(nativeDynamicClosure?.dependencies) &&
2681
+ nativeDynamicClosure.dependencies >= 0 &&
2682
+ nativeDynamicClosure.dependencies <= LINUX_ELF_MAX_NEEDED_ENTRIES &&
2683
+ Number.isSafeInteger(nativeDynamicClosure?.files) &&
2684
+ nativeDynamicClosure.files >= 1 &&
2685
+ typeof nativeDynamicClosure?.digest === "string" &&
2686
+ /^[a-f0-9]{64}$/.test(nativeDynamicClosure.digest);
2634
2687
  return {
2635
- kind: native
2636
- ? "linux-bwrap-plugin-native-static-elf-policy-v1"
2637
- : "linux-bwrap-plugin-node-policy-v1",
2688
+ kind: dynamicNative
2689
+ ? "linux-bwrap-plugin-native-dynamic-elf-policy-v1"
2690
+ : targetRuntime === "native-static-elf"
2691
+ ? "linux-bwrap-plugin-native-static-elf-policy-v1"
2692
+ : native
2693
+ ? "linux-bwrap-plugin-native-elf-policy-v1"
2694
+ : "linux-bwrap-plugin-node-policy-v1",
2638
2695
  attempted,
2639
2696
  runnable,
2640
2697
  reason,
@@ -2660,6 +2717,17 @@ function linuxBubblewrapProbe(
2660
2717
  pluginTreeSnapshotAtomic: pluginTreeSnapshot.atomic,
2661
2718
  }
2662
2719
  : {}),
2720
+ ...(initialDynamicLoadClosureDescriptorBound
2721
+ ? {
2722
+ initialDynamicLoadClosureDescriptorBound: true,
2723
+ initialDynamicLoadClosureScope: nativeDynamicClosure.scope,
2724
+ initialDynamicLoadClosureMechanism: nativeDynamicClosure.mechanism,
2725
+ initialDynamicInterpreter: nativeDynamicClosure.interpreter,
2726
+ initialDynamicDependencyCount: nativeDynamicClosure.dependencies,
2727
+ initialDynamicRuntimeFileCount: nativeDynamicClosure.files,
2728
+ initialDynamicLoadClosureDigest: nativeDynamicClosure.digest,
2729
+ }
2730
+ : {}),
2663
2731
  supervisorDescriptorBound,
2664
2732
  ...(supervisorDescriptorBound
2665
2733
  ? {
@@ -2957,8 +3025,11 @@ function validateLinuxPluginContract(
2957
3025
  { sealedEntry = false } = {},
2958
3026
  ) {
2959
3027
  const nodeContract = contract?.kind === "strict-plugin-node-bin";
2960
- const nativeContract =
3028
+ const legacyStaticNativeContract =
2961
3029
  contract?.kind === "strict-plugin-native-static-elf-bin";
3030
+ const nativeContract =
3031
+ legacyStaticNativeContract ||
3032
+ contract?.kind === "strict-plugin-native-elf-bin";
2962
3033
  const entryIsSealed =
2963
3034
  (nodeContract || nativeContract) && sealedEntry === true;
2964
3035
  if (
@@ -3081,16 +3152,22 @@ function validateLinuxPluginContract(
3081
3152
  let entryFormat = null;
3082
3153
  if (nativeContract && !entryIsSealed) {
3083
3154
  try {
3084
- entryFormat = inspectLinuxStaticNativePath(
3085
- runtime,
3086
- contract.entryIdentity,
3087
- );
3155
+ entryFormat = inspectLinuxNativePath(runtime, contract.entryIdentity);
3088
3156
  } catch (error) {
3089
3157
  return {
3090
3158
  ok: false,
3091
3159
  reason: error.message || "native_entry_unattested",
3092
3160
  };
3093
3161
  }
3162
+ if (
3163
+ legacyStaticNativeContract &&
3164
+ entryFormat.runtime !== "native-static-elf"
3165
+ ) {
3166
+ return {
3167
+ ok: false,
3168
+ reason: "native_entry_dynamic_contract_required",
3169
+ };
3170
+ }
3094
3171
  }
3095
3172
  if (
3096
3173
  !validateLinuxPluginTree(runtime, contract.pluginRoot) ||
@@ -3101,7 +3178,12 @@ function validateLinuxPluginContract(
3101
3178
  return {
3102
3179
  ok: true,
3103
3180
  contract,
3104
- entryRuntime: nativeContract ? "native-static-elf" : "node",
3181
+ entryRuntime: nativeContract
3182
+ ? entryFormat?.runtime ||
3183
+ (legacyStaticNativeContract
3184
+ ? "native-static-elf"
3185
+ : "native-unclassified")
3186
+ : "node",
3105
3187
  entryFormat,
3106
3188
  };
3107
3189
  }
@@ -3170,7 +3252,7 @@ function hashLinuxOpenFile(runtime, fd, bytes) {
3170
3252
  }
3171
3253
 
3172
3254
  function linuxEntrySnapshotContract(targetRuntime) {
3173
- if (targetRuntime === "native-static-elf") {
3255
+ if (targetRuntime?.startsWith("native-")) {
3174
3256
  return {
3175
3257
  scope: LINUX_NATIVE_ENTRY_SNAPSHOT_SCOPE,
3176
3258
  targetMode: LINUX_NATIVE_ENTRY_SNAPSHOT_TARGET_MODE,
@@ -3663,18 +3745,169 @@ function attestLinuxNodePluginTreeSnapshot(
3663
3745
  }
3664
3746
  }
3665
3747
 
3748
+ function validLinuxRuntimePageSize(value) {
3749
+ if (
3750
+ !Number.isSafeInteger(value) ||
3751
+ value < LINUX_MIN_RUNTIME_PAGE_BYTES ||
3752
+ value > LINUX_MAX_RUNTIME_PAGE_BYTES
3753
+ ) {
3754
+ return false;
3755
+ }
3756
+ const size = BigInt(value);
3757
+ return (size & (size - 1n)) === 0n;
3758
+ }
3759
+
3760
+ function readLinuxPageSizeFromAuxv(runtimeFs) {
3761
+ // AT_PAGESZ is emitted by the kernel for this exact process and matches the
3762
+ // page granularity used by the ELF loader. Avoid filesystem block sizes or
3763
+ // an external getconf binary, neither of which is the loader contract.
3764
+ const raw = runtimeFs.readFileSync("/proc/self/auxv");
3765
+ const auxv = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
3766
+ if (
3767
+ auxv.length < LINUX_AUXV_ENTRY_BYTES ||
3768
+ auxv.length > LINUX_AUXV_MAX_BYTES ||
3769
+ auxv.length % LINUX_AUXV_ENTRY_BYTES !== 0
3770
+ ) {
3771
+ throw new Error("linux_runtime_auxv_invalid");
3772
+ }
3773
+ let pageSize = null;
3774
+ let terminated = false;
3775
+ for (let offset = 0; offset < auxv.length; offset += LINUX_AUXV_ENTRY_BYTES) {
3776
+ const tag = auxv.readBigUInt64LE(offset);
3777
+ const value = auxv.readBigUInt64LE(offset + 8);
3778
+ if (tag === 0n) {
3779
+ terminated = true;
3780
+ break;
3781
+ }
3782
+ if (tag !== LINUX_AUXV_PAGE_SIZE) continue;
3783
+ if (pageSize !== null || value > BigInt(Number.MAX_SAFE_INTEGER)) {
3784
+ throw new Error("linux_runtime_page_size_ambiguous");
3785
+ }
3786
+ pageSize = Number(value);
3787
+ }
3788
+ if (!terminated || !validLinuxRuntimePageSize(pageSize)) {
3789
+ throw new Error("linux_runtime_page_size_unattested");
3790
+ }
3791
+ return pageSize;
3792
+ }
3793
+
3794
+ function linuxElfRuntimePageSize(runtime) {
3795
+ const pageSize =
3796
+ typeof runtime.linuxPageSize === "function"
3797
+ ? runtime.linuxPageSize()
3798
+ : readLinuxPageSizeFromAuxv(runtime.fs);
3799
+ if (!validLinuxRuntimePageSize(pageSize)) {
3800
+ throw new Error("linux_runtime_page_size_unattested");
3801
+ }
3802
+ return BigInt(pageSize);
3803
+ }
3804
+
3805
+ function linuxElfAlignDown(value, alignment) {
3806
+ return value - (value % alignment);
3807
+ }
3808
+
3809
+ function linuxElfAlignUp(value, alignment) {
3810
+ const remainder = value % alignment;
3811
+ return remainder === 0n ? value : value + alignment - remainder;
3812
+ }
3813
+
3814
+ function attestLinuxElfLoadMappings(loadSegments, pageSize) {
3815
+ // Linux maps later PT_LOAD entries with MAP_FIXED. Even disjoint byte ranges
3816
+ // can therefore replace one another after the loader rounds them to pages.
3817
+ // A conservative no-overlap contract keeps our file-offset parser and the
3818
+ // in-memory dynamic-loader view identical.
3819
+ const ranges = [];
3820
+ for (const load of loadSegments) {
3821
+ if (
3822
+ load.fileOffset % pageSize !== load.virtualAddress % pageSize ||
3823
+ (load.alignment > 1n &&
3824
+ ((load.alignment & (load.alignment - 1n)) !== 0n ||
3825
+ load.fileOffset % load.alignment !==
3826
+ load.virtualAddress % load.alignment))
3827
+ ) {
3828
+ throw new Error("native_entry_load_segment_alignment_invalid");
3829
+ }
3830
+ if (
3831
+ load.virtualAddress > LINUX_ELF_MAX_UINT64 - load.memorySize ||
3832
+ load.virtualAddress + load.memorySize >
3833
+ LINUX_ELF_MAX_UINT64 - (pageSize - 1n)
3834
+ ) {
3835
+ throw new Error("native_entry_load_segment_virtual_range_overflow");
3836
+ }
3837
+ if (load.memorySize === 0n) continue;
3838
+ ranges.push({
3839
+ start: linuxElfAlignDown(load.virtualAddress, pageSize),
3840
+ end: linuxElfAlignUp(load.virtualAddress + load.memorySize, pageSize),
3841
+ });
3842
+ }
3843
+ ranges.sort((left, right) =>
3844
+ left.start < right.start ? -1 : left.start > right.start ? 1 : 0,
3845
+ );
3846
+ for (let index = 1; index < ranges.length; index += 1) {
3847
+ if (ranges[index].start < ranges[index - 1].end) {
3848
+ throw new Error("native_entry_load_segment_page_overlap");
3849
+ }
3850
+ }
3851
+ }
3852
+
3853
+ function linuxElfVirtualRangeFileMappings(loadSegments, address, bytes) {
3854
+ return loadSegments
3855
+ .filter((load) => {
3856
+ if (address < load.virtualAddress) return false;
3857
+ const delta = address - load.virtualAddress;
3858
+ return (
3859
+ delta <= load.fileSize &&
3860
+ bytes <= load.fileSize - delta &&
3861
+ delta <= load.memorySize &&
3862
+ bytes <= load.memorySize - delta
3863
+ );
3864
+ })
3865
+ .map((load) => ({
3866
+ load,
3867
+ fileOffset: load.fileOffset + (address - load.virtualAddress),
3868
+ }));
3869
+ }
3870
+
3871
+ function readLinuxElfString(table, offset, label) {
3872
+ if (!Number.isSafeInteger(offset) || offset < 0 || offset >= table.length) {
3873
+ throw new Error(`native_entry_${label}_string_out_of_bounds`);
3874
+ }
3875
+ const end = table.indexOf(0, offset);
3876
+ if (end < 0) {
3877
+ throw new Error(`native_entry_${label}_string_unterminated`);
3878
+ }
3879
+ const bytes = table.subarray(offset, end);
3880
+ if (bytes.length < 1 || bytes.some((value) => value < 0x21 || value > 0x7e)) {
3881
+ throw new Error(`native_entry_${label}_string_invalid`);
3882
+ }
3883
+ return bytes.toString("ascii");
3884
+ }
3885
+
3886
+ function linuxSystemDynamicPath(value) {
3887
+ return (
3888
+ typeof value === "string" &&
3889
+ path.posix.isAbsolute(value) &&
3890
+ path.posix.normalize(value) === value &&
3891
+ !value.includes("\0") &&
3892
+ ["/lib/", "/lib64/", "/usr/lib/", "/usr/lib64/"].some((prefix) =>
3893
+ value.startsWith(prefix),
3894
+ )
3895
+ );
3896
+ }
3897
+
3666
3898
  /**
3667
- * Classify one already-pinned plugin entry without invoking it or any host
3668
- * inspection utility. The native slice accepts conventional fully static
3669
- * ELF64 ET_EXEC images and a narrow static-PIE-shaped ELF:
3670
- * ET_DYN without PT_INTERP, with one bounded PT_DYNAMIC table that declares
3671
- * PIE but no external DT_NEEDED dependency.
3899
+ * Classify one already-pinned plugin entry without invoking it or passing it
3900
+ * to a host inspection utility. Besides conventional fully static ET_EXEC and
3901
+ * static-PIE-shaped ET_DYN images, the parser can describe a narrow dynamic
3902
+ * executable. The caller must separately prove that its PT_INTERP and every
3903
+ * direct DT_NEEDED member are present in a pinned, attested system-file set.
3672
3904
  */
3673
- function inspectLinuxStaticNativeElf(runtime, fd, identity) {
3905
+ function inspectLinuxNativeElf(runtime, fd, identity) {
3674
3906
  const expectedMachine = LINUX_ELF_MACHINES[runtime.arch];
3675
3907
  if (!expectedMachine) {
3676
3908
  throw new Error(`unsupported_elf_architecture:${String(runtime.arch)}`);
3677
3909
  }
3910
+ const runtimePageSize = linuxElfRuntimePageSize(runtime);
3678
3911
  const before = runtime.fs.fstatSync(fd);
3679
3912
  if (
3680
3913
  !before.isFile() ||
@@ -3741,6 +3974,7 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
3741
3974
  let nonExecutableStack = false;
3742
3975
  const loadSegments = [];
3743
3976
  const dynamicSegments = [];
3977
+ const interpreterSegments = [];
3744
3978
  for (let index = 0; index < programHeaderCount; index += 1) {
3745
3979
  const offset =
3746
3980
  Number(programHeaderOffset) + index * LINUX_ELF64_PROGRAM_HEADER_BYTES;
@@ -3756,6 +3990,7 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
3756
3990
  const virtualAddress = program.readBigUInt64LE(16);
3757
3991
  const fileSize = program.readBigUInt64LE(32);
3758
3992
  const memorySize = program.readBigUInt64LE(40);
3993
+ const alignment = program.readBigUInt64LE(48);
3759
3994
  if (fileOffset > fileBytes || fileSize > fileBytes - fileOffset) {
3760
3995
  throw new Error("native_entry_segment_out_of_bounds");
3761
3996
  }
@@ -3763,18 +3998,21 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
3763
3998
  throw new Error("native_entry_segment_out_of_bounds");
3764
3999
  }
3765
4000
  if (type === LINUX_ELF_PROGRAM_INTERP) {
3766
- throw new Error("native_entry_interpreter_unsupported");
4001
+ interpreterSegments.push({
4002
+ flags,
4003
+ fileOffset,
4004
+ fileSize,
4005
+ memorySize,
4006
+ });
3767
4007
  }
3768
4008
  if (type === LINUX_ELF_PROGRAM_DYNAMIC) {
3769
- if (elfType === LINUX_ELF_TYPE_EXEC) {
3770
- throw new Error("native_entry_dynamic_elf_unsupported");
3771
- }
3772
4009
  dynamicSegments.push({
3773
4010
  flags,
3774
4011
  fileOffset,
3775
4012
  virtualAddress,
3776
4013
  fileSize,
3777
4014
  memorySize,
4015
+ alignment,
3778
4016
  });
3779
4017
  }
3780
4018
  if (type === LINUX_ELF_PROGRAM_GNU_STACK) {
@@ -3790,6 +4028,7 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
3790
4028
  virtualAddress,
3791
4029
  fileSize,
3792
4030
  memorySize,
4031
+ alignment,
3793
4032
  });
3794
4033
  }
3795
4034
  if (type === LINUX_ELF_PROGRAM_LOAD && (flags & 0x1) !== 0) {
@@ -3805,6 +4044,7 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
3805
4044
  }
3806
4045
  }
3807
4046
  }
4047
+ attestLinuxElfLoadMappings(loadSegments, runtimePageSize);
3808
4048
  if (!executableLoad || !entryInExecutableLoad) {
3809
4049
  throw new Error("native_entry_has_no_executable_entry_segment");
3810
4050
  }
@@ -3812,23 +4052,65 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
3812
4052
  throw new Error("native_entry_nonexecutable_stack_unattested");
3813
4053
  }
3814
4054
 
3815
- if (elfType === LINUX_ELF_TYPE_DYN) {
4055
+ if (interpreterSegments.length > 1) {
4056
+ throw new Error("native_entry_interpreter_ambiguous");
4057
+ }
4058
+ let interpreter = null;
4059
+ if (interpreterSegments.length === 1) {
4060
+ const segment = interpreterSegments[0];
4061
+ if (
4062
+ (segment.flags & 0x3) !== 0 ||
4063
+ segment.fileSize < 2n ||
4064
+ segment.fileSize > BigInt(LINUX_ELF_MAX_INTERPRETER_BYTES) ||
4065
+ segment.fileSize > segment.memorySize
4066
+ ) {
4067
+ throw new Error("native_entry_interpreter_invalid");
4068
+ }
4069
+ const contents = readLinuxFdExactly(
4070
+ runtime,
4071
+ fd,
4072
+ Number(segment.fileSize),
4073
+ Number(segment.fileOffset),
4074
+ );
4075
+ if (
4076
+ contents[contents.length - 1] !== 0 ||
4077
+ contents.indexOf(0) !== contents.length - 1
4078
+ ) {
4079
+ throw new Error("native_entry_interpreter_unterminated");
4080
+ }
4081
+ interpreter = readLinuxElfString(contents, 0, "interpreter");
4082
+ if (!linuxSystemDynamicPath(interpreter)) {
4083
+ throw new Error("native_entry_interpreter_outside_system_roots");
4084
+ }
4085
+ }
4086
+
4087
+ const staticPie = elfType === LINUX_ELF_TYPE_DYN && interpreter === null;
4088
+ const dynamicExecutable = interpreter !== null;
4089
+ let dynamicMetadata = null;
4090
+ if (elfType === LINUX_ELF_TYPE_EXEC && !dynamicExecutable) {
4091
+ if (dynamicSegments.length > 0) {
4092
+ throw new Error("native_entry_dynamic_elf_unsupported");
4093
+ }
4094
+ } else {
4095
+ const prefix = staticPie
4096
+ ? "native_entry_static_pie"
4097
+ : "native_entry_dynamic";
3816
4098
  if (dynamicSegments.length === 0) {
3817
- throw new Error("native_entry_static_pie_dynamic_segment_missing");
4099
+ throw new Error(`${prefix}_dynamic_segment_missing`);
3818
4100
  }
3819
4101
  if (dynamicSegments.length !== 1) {
3820
- throw new Error("native_entry_static_pie_dynamic_segment_ambiguous");
4102
+ throw new Error(`${prefix}_dynamic_segment_ambiguous`);
3821
4103
  }
3822
4104
  const dynamic = dynamicSegments[0];
3823
4105
  if ((dynamic.flags & 0x1) !== 0) {
3824
- throw new Error("native_entry_static_pie_dynamic_segment_executable");
4106
+ throw new Error(`${prefix}_dynamic_segment_executable`);
3825
4107
  }
3826
4108
  if (
3827
4109
  dynamic.fileSize < BigInt(LINUX_ELF64_DYNAMIC_ENTRY_BYTES) ||
3828
4110
  dynamic.fileSize % BigInt(LINUX_ELF64_DYNAMIC_ENTRY_BYTES) !== 0n ||
3829
4111
  dynamic.fileSize > dynamic.memorySize
3830
4112
  ) {
3831
- throw new Error("native_entry_static_pie_dynamic_segment_invalid");
4113
+ throw new Error(`${prefix}_dynamic_segment_invalid`);
3832
4114
  }
3833
4115
  const dynamicEntries =
3834
4116
  dynamic.fileSize / BigInt(LINUX_ELF64_DYNAMIC_ENTRY_BYTES);
@@ -3836,9 +4118,9 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
3836
4118
  dynamicEntries < 1n ||
3837
4119
  dynamicEntries > BigInt(LINUX_ELF_MAX_DYNAMIC_ENTRIES)
3838
4120
  ) {
3839
- throw new Error("native_entry_static_pie_dynamic_table_too_large");
4121
+ throw new Error(`${prefix}_dynamic_table_too_large`);
3840
4122
  }
3841
- const containingLoad = loadSegments.find((load) => {
4123
+ const containingLoads = loadSegments.filter((load) => {
3842
4124
  if (
3843
4125
  dynamic.fileOffset < load.fileOffset ||
3844
4126
  dynamic.virtualAddress < load.virtualAddress
@@ -3855,8 +4137,22 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
3855
4137
  dynamic.memorySize <= load.memorySize - memoryDelta
3856
4138
  );
3857
4139
  });
3858
- if (!containingLoad) {
3859
- throw new Error("native_entry_static_pie_dynamic_segment_unmapped");
4140
+ if (containingLoads.length === 0) {
4141
+ throw new Error(`${prefix}_dynamic_segment_unmapped`);
4142
+ }
4143
+ if (containingLoads.length !== 1) {
4144
+ throw new Error(`${prefix}_dynamic_segment_mapping_ambiguous`);
4145
+ }
4146
+ const dynamicLoaderMappings = linuxElfVirtualRangeFileMappings(
4147
+ loadSegments,
4148
+ dynamic.virtualAddress,
4149
+ dynamic.fileSize,
4150
+ );
4151
+ if (
4152
+ dynamicLoaderMappings.length !== 1 ||
4153
+ dynamicLoaderMappings[0].fileOffset !== dynamic.fileOffset
4154
+ ) {
4155
+ throw new Error(`${prefix}_dynamic_segment_loader_view_mismatch`);
3860
4156
  }
3861
4157
 
3862
4158
  const dynamicTable = readLinuxFdExactly(
@@ -3865,8 +4161,21 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
3865
4161
  Number(dynamic.fileSize),
3866
4162
  Number(dynamic.fileOffset),
3867
4163
  );
4164
+ const neededOffsets = [];
4165
+ const forbiddenDependencyTags = new Set([
4166
+ LINUX_ELF_DYNAMIC_RPATH,
4167
+ LINUX_ELF_DYNAMIC_RUNPATH,
4168
+ LINUX_ELF_DYNAMIC_CONFIG,
4169
+ LINUX_ELF_DYNAMIC_DEPAUDIT,
4170
+ LINUX_ELF_DYNAMIC_AUDIT,
4171
+ LINUX_ELF_DYNAMIC_AUXILIARY,
4172
+ LINUX_ELF_DYNAMIC_FILTER,
4173
+ ]);
3868
4174
  let terminated = false;
4175
+ let flags = null;
3869
4176
  let flags1 = null;
4177
+ let stringTableAddress = null;
4178
+ let stringTableBytes = null;
3870
4179
  for (
3871
4180
  let offset = 0;
3872
4181
  offset < dynamicTable.length;
@@ -3878,21 +4187,113 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
3878
4187
  terminated = true;
3879
4188
  break;
3880
4189
  }
3881
- if (tag === LINUX_ELF_DYNAMIC_NEEDED) {
3882
- throw new Error("native_entry_static_pie_dependency_unsupported");
4190
+ if (
4191
+ tag === LINUX_ELF_DYNAMIC_TEXTREL ||
4192
+ forbiddenDependencyTags.has(tag)
4193
+ ) {
4194
+ throw new Error(`${prefix}_loader_directive_unsupported`);
3883
4195
  }
3884
- if (tag === LINUX_ELF_DYNAMIC_FLAGS_1) {
4196
+ if (tag === LINUX_ELF_DYNAMIC_NEEDED) {
4197
+ neededOffsets.push(value);
4198
+ if (neededOffsets.length > LINUX_ELF_MAX_NEEDED_ENTRIES) {
4199
+ throw new Error(`${prefix}_dependency_count_exceeded`);
4200
+ }
4201
+ } else if (tag === LINUX_ELF_DYNAMIC_STRTAB) {
4202
+ if (stringTableAddress !== null) {
4203
+ throw new Error(`${prefix}_string_table_ambiguous`);
4204
+ }
4205
+ stringTableAddress = value;
4206
+ } else if (tag === LINUX_ELF_DYNAMIC_STRSZ) {
4207
+ if (stringTableBytes !== null) {
4208
+ throw new Error(`${prefix}_string_table_ambiguous`);
4209
+ }
4210
+ stringTableBytes = value;
4211
+ } else if (tag === LINUX_ELF_DYNAMIC_FLAGS) {
4212
+ if (flags !== null) {
4213
+ throw new Error(`${prefix}_flags_ambiguous`);
4214
+ }
4215
+ flags = value;
4216
+ } else if (tag === LINUX_ELF_DYNAMIC_FLAGS_1) {
3885
4217
  if (flags1 !== null) {
3886
- throw new Error("native_entry_static_pie_flags_ambiguous");
4218
+ throw new Error(`${prefix}_flags_ambiguous`);
3887
4219
  }
3888
4220
  flags1 = value;
3889
4221
  }
3890
4222
  }
3891
4223
  if (!terminated) {
3892
- throw new Error("native_entry_static_pie_dynamic_table_unterminated");
4224
+ throw new Error(`${prefix}_dynamic_table_unterminated`);
4225
+ }
4226
+ if (flags !== null && (flags & LINUX_ELF_DYNAMIC_FLAG_TEXTREL) !== 0n) {
4227
+ throw new Error(`${prefix}_text_relocation_unsupported`);
4228
+ }
4229
+ if (
4230
+ elfType === LINUX_ELF_TYPE_DYN &&
4231
+ (flags1 === null || (flags1 & LINUX_ELF_DYNAMIC_FLAG_PIE) === 0n)
4232
+ ) {
4233
+ throw new Error(`${prefix}_flag_missing`);
3893
4234
  }
3894
- if (flags1 === null || (flags1 & LINUX_ELF_DYNAMIC_FLAG_PIE) === 0n) {
3895
- throw new Error("native_entry_static_pie_flag_missing");
4235
+ if (staticPie && neededOffsets.length > 0) {
4236
+ throw new Error("native_entry_static_pie_dependency_unsupported");
4237
+ }
4238
+
4239
+ let needed = [];
4240
+ if (neededOffsets.length > 0) {
4241
+ if (
4242
+ stringTableAddress === null ||
4243
+ stringTableBytes === null ||
4244
+ stringTableBytes < 1n ||
4245
+ stringTableBytes > BigInt(LINUX_ELF_MAX_STRING_TABLE_BYTES)
4246
+ ) {
4247
+ throw new Error(`${prefix}_string_table_invalid`);
4248
+ }
4249
+ const stringTableMappings = linuxElfVirtualRangeFileMappings(
4250
+ loadSegments,
4251
+ stringTableAddress,
4252
+ stringTableBytes,
4253
+ );
4254
+ if (stringTableMappings.length === 0) {
4255
+ throw new Error(`${prefix}_string_table_unmapped`);
4256
+ }
4257
+ if (stringTableMappings.length !== 1) {
4258
+ throw new Error(`${prefix}_string_table_mapping_ambiguous`);
4259
+ }
4260
+ const stringTableOffset = stringTableMappings[0].fileOffset;
4261
+ if (stringTableOffset > BigInt(Number.MAX_SAFE_INTEGER)) {
4262
+ throw new Error(`${prefix}_string_table_unmapped`);
4263
+ }
4264
+ const stringTable = readLinuxFdExactly(
4265
+ runtime,
4266
+ fd,
4267
+ Number(stringTableBytes),
4268
+ Number(stringTableOffset),
4269
+ );
4270
+ needed = neededOffsets.map((offset) => {
4271
+ if (offset > BigInt(Number.MAX_SAFE_INTEGER)) {
4272
+ throw new Error(`${prefix}_dependency_string_out_of_bounds`);
4273
+ }
4274
+ const name = readLinuxElfString(
4275
+ stringTable,
4276
+ Number(offset),
4277
+ "dynamic_dependency",
4278
+ );
4279
+ if (
4280
+ !/^[A-Za-z0-9][A-Za-z0-9._+-]{0,254}$/.test(name) ||
4281
+ path.posix.basename(name) !== name
4282
+ ) {
4283
+ throw new Error("native_entry_dynamic_dependency_name_invalid");
4284
+ }
4285
+ return name;
4286
+ });
4287
+ if (new Set(needed).size !== needed.length) {
4288
+ throw new Error("native_entry_dynamic_dependency_ambiguous");
4289
+ }
4290
+ }
4291
+
4292
+ if (dynamicExecutable) {
4293
+ dynamicMetadata = Object.freeze({
4294
+ interpreter,
4295
+ needed: Object.freeze(needed),
4296
+ });
3896
4297
  }
3897
4298
  }
3898
4299
 
@@ -3902,17 +4303,23 @@ function inspectLinuxStaticNativeElf(runtime, fd, identity) {
3902
4303
  throw new Error("native_entry_changed_during_elf_attestation");
3903
4304
  }
3904
4305
  return Object.freeze({
3905
- format:
3906
- elfType === LINUX_ELF_TYPE_DYN
4306
+ runtime: dynamicExecutable ? "native-dynamic-elf" : "native-static-elf",
4307
+ format: dynamicExecutable
4308
+ ? elfType === LINUX_ELF_TYPE_DYN
4309
+ ? "elf64-dynamic-pie-et-dyn"
4310
+ : "elf64-dynamic-et-exec"
4311
+ : elfType === LINUX_ELF_TYPE_DYN
3907
4312
  ? "elf64-static-pie-shaped-et-dyn"
3908
4313
  : "elf64-static-et-exec",
3909
4314
  architecture: runtime.arch,
3910
4315
  machine: expectedMachine,
4316
+ loaderPageBytes: Number(runtimePageSize),
3911
4317
  programHeaders: programHeaderCount,
4318
+ ...(dynamicMetadata || {}),
3912
4319
  });
3913
4320
  }
3914
4321
 
3915
- function inspectLinuxStaticNativePath(runtime, identity) {
4322
+ function inspectLinuxNativePath(runtime, identity) {
3916
4323
  let fd;
3917
4324
  try {
3918
4325
  const constants = runtime.fs.constants || fs.constants;
@@ -3921,7 +4328,7 @@ function inspectLinuxStaticNativePath(runtime, identity) {
3921
4328
  Number(constants.O_NOFOLLOW || 0) |
3922
4329
  Number(constants.O_NONBLOCK || 0);
3923
4330
  fd = runtime.fs.openSync(identity.realPath, flags);
3924
- return inspectLinuxStaticNativeElf(runtime, fd, identity);
4331
+ return inspectLinuxNativeElf(runtime, fd, identity);
3925
4332
  } finally {
3926
4333
  if (fd !== undefined) {
3927
4334
  try {
@@ -4656,6 +5063,87 @@ function pinLinuxRuntimeMounts(runtime, runtimeMounts, runtimeIdentity) {
4656
5063
  }
4657
5064
  }
4658
5065
 
5066
+ // Bind only the kernel-selected interpreter and the entry ELF's direct
5067
+ // DT_NEEDED names. This deliberately does not claim transitive or dlopen-time
5068
+ // closure; handleAtomic remains false for the wider launch chain.
5069
+ function bindLinuxNativeDynamicRuntime(entryFormat, pinnedRuntimeMounts) {
5070
+ if (entryFormat?.runtime !== "native-dynamic-elf") return null;
5071
+ if (
5072
+ !linuxSystemDynamicPath(entryFormat.interpreter) ||
5073
+ !Array.isArray(entryFormat.needed) ||
5074
+ entryFormat.needed.length > LINUX_ELF_MAX_NEEDED_ENTRIES
5075
+ ) {
5076
+ throw new Error("native_dynamic_metadata_invalid");
5077
+ }
5078
+ const systemMounts = (pinnedRuntimeMounts || []).filter(
5079
+ (mount) =>
5080
+ linuxSystemDynamicPath(mount?.destination) &&
5081
+ Number.isInteger(mount?.fd) &&
5082
+ typeof mount?.fileId?.dev === "string" &&
5083
+ typeof mount?.fileId?.ino === "string" &&
5084
+ Number.isSafeInteger(mount?.bytes) &&
5085
+ mount.bytes > 0 &&
5086
+ Number.isFinite(mount?.mtimeMs),
5087
+ );
5088
+ const interpreterMount = systemMounts.find(
5089
+ (mount) => mount.destination === entryFormat.interpreter,
5090
+ );
5091
+ if (!interpreterMount) {
5092
+ throw new Error("native_dynamic_interpreter_outside_direct_system_set");
5093
+ }
5094
+ const selected = new Map([[interpreterMount.destination, interpreterMount]]);
5095
+ for (const dependency of entryFormat.needed) {
5096
+ if (
5097
+ typeof dependency !== "string" ||
5098
+ !/^[A-Za-z0-9][A-Za-z0-9._+-]{0,254}$/.test(dependency)
5099
+ ) {
5100
+ throw new Error("native_dynamic_dependency_name_invalid");
5101
+ }
5102
+ const matches = systemMounts.filter(
5103
+ (mount) => path.posix.basename(mount.destination) === dependency,
5104
+ );
5105
+ if (matches.length === 0) {
5106
+ throw new Error("native_dynamic_dependency_outside_direct_system_set");
5107
+ }
5108
+ if (matches.length !== 1) {
5109
+ throw new Error("native_dynamic_dependency_runtime_ambiguous");
5110
+ }
5111
+ selected.set(matches[0].destination, matches[0]);
5112
+ }
5113
+ const members = [...selected.values()]
5114
+ .map((mount) => ({
5115
+ destination: mount.destination,
5116
+ fileId: {
5117
+ dev: mount.fileId.dev,
5118
+ ino: mount.fileId.ino,
5119
+ },
5120
+ bytes: mount.bytes,
5121
+ mtimeMs: mount.mtimeMs,
5122
+ }))
5123
+ .sort((left, right) => left.destination.localeCompare(right.destination));
5124
+ const digest = crypto
5125
+ .createHash("sha256")
5126
+ .update(
5127
+ JSON.stringify({
5128
+ version: 1,
5129
+ scope: LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_SCOPE,
5130
+ mechanism: LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_MECHANISM,
5131
+ interpreter: entryFormat.interpreter,
5132
+ needed: [...entryFormat.needed].sort(),
5133
+ members,
5134
+ }),
5135
+ )
5136
+ .digest("hex");
5137
+ return Object.freeze({
5138
+ scope: LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_SCOPE,
5139
+ mechanism: LINUX_NATIVE_DYNAMIC_DIRECT_SYSTEM_SET_MECHANISM,
5140
+ interpreter: entryFormat.interpreter,
5141
+ dependencies: entryFormat.needed.length,
5142
+ files: members.length,
5143
+ digest,
5144
+ });
5145
+ }
5146
+
4659
5147
  function linuxSandboxEnvironment() {
4660
5148
  return {
4661
5149
  HOME: "/home/sandbox",
@@ -4932,11 +5420,22 @@ export function applyLinuxSandbox(
4932
5420
  );
4933
5421
 
4934
5422
  if (requiresStrongLinuxBoundary) {
5423
+ if (isLinuxGenericWorkspaceContract(sandboxOpts.executionContract)) {
5424
+ return applyLinuxGenericWorkspaceSandbox(
5425
+ command,
5426
+ args,
5427
+ spawnOpts,
5428
+ sandboxOpts,
5429
+ runtime,
5430
+ );
5431
+ }
4935
5432
  const requestedEntryRuntime =
4936
5433
  sandboxOpts.executionContract?.kind ===
4937
5434
  "strict-plugin-native-static-elf-bin"
4938
5435
  ? "native-static-elf"
4939
- : "node";
5436
+ : sandboxOpts.executionContract?.kind === "strict-plugin-native-elf-bin"
5437
+ ? "native-unclassified"
5438
+ : "node";
4940
5439
  const validation = validateLinuxPluginContract(
4941
5440
  command,
4942
5441
  args,
@@ -5009,6 +5508,29 @@ export function applyLinuxSandbox(
5009
5508
  guarantees: [],
5010
5509
  });
5011
5510
  }
5511
+ let nativeDynamicClosure = null;
5512
+ try {
5513
+ nativeDynamicClosure = bindLinuxNativeDynamicRuntime(
5514
+ validation.entryFormat,
5515
+ pinnedRuntimeMounts,
5516
+ );
5517
+ } catch (error) {
5518
+ closeLinuxPinnedMounts(runtime, pinnedRuntimeMounts);
5519
+ return createSandboxPlan({
5520
+ ...base,
5521
+ backend: null,
5522
+ candidateBackend: LINUX_BWRAP_BACKEND,
5523
+ policyAttested: false,
5524
+ runtimeProbe: linuxBubblewrapProbe(
5525
+ false,
5526
+ false,
5527
+ error.message || "native_dynamic_direct_system_set_unattested",
5528
+ validation.entryRuntime,
5529
+ ),
5530
+ reason: "linux_bwrap_native_runtime_unattested",
5531
+ guarantees: [],
5532
+ });
5533
+ }
5012
5534
 
5013
5535
  let pinnedMounts = [];
5014
5536
  let pluginTree;
@@ -5046,8 +5568,8 @@ export function applyLinuxSandbox(
5046
5568
  ) {
5047
5569
  throw new Error("plugin_entry_pin_mismatch");
5048
5570
  }
5049
- if (validation.entryRuntime === "native-static-elf") {
5050
- entryFormat = inspectLinuxStaticNativeElf(
5571
+ if (validation.entryRuntime.startsWith("native-")) {
5572
+ entryFormat = inspectLinuxNativeElf(
5051
5573
  runtime,
5052
5574
  entryMount.fd,
5053
5575
  validation.contract.entryIdentity,
@@ -5325,10 +5847,9 @@ export function applyLinuxSandbox(
5325
5847
  environment,
5326
5848
  seccompFilter,
5327
5849
  );
5328
- const targetArgs =
5329
- validation.entryRuntime === "native-static-elf"
5330
- ? [sandboxEntry, ...args]
5331
- : ["/opt/chainless/runtime/node", sandboxEntry, ...args.slice(1)];
5850
+ const targetArgs = validation.entryRuntime.startsWith("native-")
5851
+ ? [sandboxEntry, ...args]
5852
+ : ["/opt/chainless/runtime/node", sandboxEntry, ...args.slice(1)];
5332
5853
  const snapshotPolicyBinding = entrySnapshot
5333
5854
  ? {
5334
5855
  scope: entrySnapshot.attestation.scope,
@@ -5389,6 +5910,9 @@ export function applyLinuxSandbox(
5389
5910
  },
5390
5911
  ),
5391
5912
  contentSnapshot: snapshotPolicyBinding,
5913
+ ...(nativeDynamicClosure
5914
+ ? { initialDynamicLoadClosure: nativeDynamicClosure }
5915
+ : {}),
5392
5916
  ...(validation.entryRuntime === "node"
5393
5917
  ? { pluginTreeContentSnapshot: pluginTreeSnapshot }
5394
5918
  : {}),
@@ -5398,10 +5922,9 @@ export function applyLinuxSandbox(
5398
5922
  sha256: seccompFilter.sha256,
5399
5923
  },
5400
5924
  policyArgs,
5401
- target:
5402
- validation.entryRuntime === "native-static-elf"
5403
- ? targetArgs.slice(0, 1)
5404
- : targetArgs.slice(0, 2),
5925
+ target: validation.entryRuntime.startsWith("native-")
5926
+ ? targetArgs.slice(0, 1)
5927
+ : targetArgs.slice(0, 2),
5405
5928
  }),
5406
5929
  )
5407
5930
  .digest("hex");
@@ -5556,6 +6079,7 @@ export function applyLinuxSandbox(
5556
6079
  entrySnapshot?.attestation,
5557
6080
  supervisorBinding,
5558
6081
  pluginTreeSnapshot,
6082
+ nativeDynamicClosure,
5559
6083
  ),
5560
6084
  );
5561
6085
  const options = {