@smthrs/platform-node 0.0.0-stage → 1.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/CHANGELOG.md +211 -0
  2. package/LICENSE +21 -0
  3. package/README.md +165 -2
  4. package/bin/darwin-arm64/smithers-jj-export +0 -0
  5. package/bin/darwin-x64/smithers-jj-export +0 -0
  6. package/bin/linux-arm64/smithers-jj-export +0 -0
  7. package/bin/linux-x64/smithers-jj-export +4 -0
  8. package/bin/win32-x64/smithers-jj-export.exe +0 -0
  9. package/dist/cjs/AtomicFileSystem.d.ts +147 -0
  10. package/dist/cjs/AtomicFileSystem.d.ts.map +1 -0
  11. package/dist/cjs/AtomicFileSystem.js +234 -0
  12. package/dist/cjs/AtomicFileSystem.js.map +7 -0
  13. package/dist/cjs/EgressHttpClient.d.ts +106 -0
  14. package/dist/cjs/EgressHttpClient.d.ts.map +1 -0
  15. package/dist/cjs/EgressHttpClient.js +202 -0
  16. package/dist/cjs/EgressHttpClient.js.map +7 -0
  17. package/dist/cjs/HostLiveness.d.ts +81 -0
  18. package/dist/cjs/HostLiveness.d.ts.map +1 -0
  19. package/dist/cjs/HostLiveness.js +49 -0
  20. package/dist/cjs/HostLiveness.js.map +7 -0
  21. package/dist/cjs/NodeHost.d.ts +145 -0
  22. package/dist/cjs/NodeHost.d.ts.map +1 -0
  23. package/dist/cjs/NodeHost.js +128 -0
  24. package/dist/cjs/NodeHost.js.map +7 -0
  25. package/dist/cjs/ProcessConfinement.d.ts +61 -0
  26. package/dist/cjs/ProcessConfinement.d.ts.map +1 -0
  27. package/dist/cjs/ProcessConfinement.js +287 -0
  28. package/dist/cjs/ProcessConfinement.js.map +7 -0
  29. package/dist/cjs/ProcessReaper.d.ts +457 -0
  30. package/dist/cjs/ProcessReaper.d.ts.map +1 -0
  31. package/dist/cjs/ProcessReaper.js +349 -0
  32. package/dist/cjs/ProcessReaper.js.map +7 -0
  33. package/dist/cjs/ProcessSandbox.d.ts +377 -0
  34. package/dist/cjs/ProcessSandbox.d.ts.map +1 -0
  35. package/dist/cjs/ProcessSandbox.js +759 -0
  36. package/dist/cjs/ProcessSandbox.js.map +7 -0
  37. package/dist/cjs/ScopedProcess.d.ts +65 -0
  38. package/dist/cjs/ScopedProcess.d.ts.map +1 -0
  39. package/dist/cjs/ScopedProcess.js +87 -0
  40. package/dist/cjs/ScopedProcess.js.map +7 -0
  41. package/dist/cjs/index.d.ts +38 -0
  42. package/dist/cjs/index.d.ts.map +1 -0
  43. package/dist/cjs/index.js +47 -0
  44. package/dist/cjs/index.js.map +7 -0
  45. package/dist/cjs/internal/AtomicFileSystemExecutable.d.ts +61 -0
  46. package/dist/cjs/internal/AtomicFileSystemExecutable.d.ts.map +1 -0
  47. package/dist/cjs/internal/AtomicFileSystemExecutable.js +250 -0
  48. package/dist/cjs/internal/AtomicFileSystemExecutable.js.map +7 -0
  49. package/dist/cjs/internal/AtomicFileSystemProtocol.d.ts +78 -0
  50. package/dist/cjs/internal/AtomicFileSystemProtocol.d.ts.map +1 -0
  51. package/dist/cjs/internal/AtomicFileSystemProtocol.js +382 -0
  52. package/dist/cjs/internal/AtomicFileSystemProtocol.js.map +7 -0
  53. package/dist/cjs/internal/AtomicFileSystemTransport.d.ts +44 -0
  54. package/dist/cjs/internal/AtomicFileSystemTransport.d.ts.map +1 -0
  55. package/dist/cjs/internal/AtomicFileSystemTransport.js +197 -0
  56. package/dist/cjs/internal/AtomicFileSystemTransport.js.map +7 -0
  57. package/dist/cjs/internal/AtomicHelperError.d.ts +35 -0
  58. package/dist/cjs/internal/AtomicHelperError.d.ts.map +1 -0
  59. package/dist/cjs/internal/AtomicHelperError.js +45 -0
  60. package/dist/cjs/internal/AtomicHelperError.js.map +7 -0
  61. package/dist/cjs/internal/AtomicHelperFault.d.ts +59 -0
  62. package/dist/cjs/internal/AtomicHelperFault.d.ts.map +1 -0
  63. package/dist/cjs/internal/AtomicHelperFault.js +66 -0
  64. package/dist/cjs/internal/AtomicHelperFault.js.map +7 -0
  65. package/dist/cjs/internal/PipedProcess.d.ts +35 -0
  66. package/dist/cjs/internal/PipedProcess.d.ts.map +1 -0
  67. package/dist/cjs/internal/PipedProcess.js +309 -0
  68. package/dist/cjs/internal/PipedProcess.js.map +7 -0
  69. package/dist/cjs/internal/ProcSnapshot.d.ts +85 -0
  70. package/dist/cjs/internal/ProcSnapshot.d.ts.map +1 -0
  71. package/dist/cjs/internal/ProcSnapshot.js +123 -0
  72. package/dist/cjs/internal/ProcSnapshot.js.map +7 -0
  73. package/dist/cjs/internal/ProcessCleanup.d.ts +64 -0
  74. package/dist/cjs/internal/ProcessCleanup.d.ts.map +1 -0
  75. package/dist/cjs/internal/ProcessCleanup.js +62 -0
  76. package/dist/cjs/internal/ProcessCleanup.js.map +7 -0
  77. package/dist/cjs/internal/ProcessCleanupPolicyError.d.ts +30 -0
  78. package/dist/cjs/internal/ProcessCleanupPolicyError.d.ts.map +1 -0
  79. package/dist/cjs/internal/ProcessCleanupPolicyError.js +33 -0
  80. package/dist/cjs/internal/ProcessCleanupPolicyError.js.map +7 -0
  81. package/dist/cjs/internal/ProcessFault.d.ts +63 -0
  82. package/dist/cjs/internal/ProcessFault.d.ts.map +1 -0
  83. package/dist/cjs/internal/ProcessFault.js +63 -0
  84. package/dist/cjs/internal/ProcessFault.js.map +7 -0
  85. package/dist/cjs/internal/ProcessSupervisor.d.ts +141 -0
  86. package/dist/cjs/internal/ProcessSupervisor.d.ts.map +1 -0
  87. package/dist/cjs/internal/ProcessSupervisor.js +682 -0
  88. package/dist/cjs/internal/ProcessSupervisor.js.map +7 -0
  89. package/dist/cjs/internal/SupervisorProgram.d.ts +9 -0
  90. package/dist/cjs/internal/SupervisorProgram.d.ts.map +1 -0
  91. package/dist/cjs/internal/SupervisorProgram.js +306 -0
  92. package/dist/cjs/internal/SupervisorProgram.js.map +7 -0
  93. package/dist/cjs/internal/WindowsProcessJob.d.ts +39 -0
  94. package/dist/cjs/internal/WindowsProcessJob.d.ts.map +1 -0
  95. package/dist/cjs/internal/WindowsProcessJob.js +113 -0
  96. package/dist/cjs/internal/WindowsProcessJob.js.map +7 -0
  97. package/dist/cjs/package.json +1 -0
  98. package/dist/esm/AtomicFileSystem.d.ts +147 -0
  99. package/dist/esm/AtomicFileSystem.d.ts.map +1 -0
  100. package/dist/esm/AtomicFileSystem.js +290 -0
  101. package/dist/esm/AtomicFileSystem.js.map +1 -0
  102. package/dist/esm/EgressHttpClient.d.ts +106 -0
  103. package/dist/esm/EgressHttpClient.d.ts.map +1 -0
  104. package/dist/esm/EgressHttpClient.js +259 -0
  105. package/dist/esm/EgressHttpClient.js.map +1 -0
  106. package/dist/esm/HostLiveness.d.ts +81 -0
  107. package/dist/esm/HostLiveness.d.ts.map +1 -0
  108. package/dist/esm/HostLiveness.js +68 -0
  109. package/dist/esm/HostLiveness.js.map +1 -0
  110. package/dist/esm/NodeHost.d.ts +145 -0
  111. package/dist/esm/NodeHost.d.ts.map +1 -0
  112. package/dist/esm/NodeHost.js +162 -0
  113. package/dist/esm/NodeHost.js.map +1 -0
  114. package/dist/esm/ProcessConfinement.d.ts +61 -0
  115. package/dist/esm/ProcessConfinement.d.ts.map +1 -0
  116. package/dist/esm/ProcessConfinement.js +311 -0
  117. package/dist/esm/ProcessConfinement.js.map +1 -0
  118. package/dist/esm/ProcessReaper.d.ts +457 -0
  119. package/dist/esm/ProcessReaper.d.ts.map +1 -0
  120. package/dist/esm/ProcessReaper.js +711 -0
  121. package/dist/esm/ProcessReaper.js.map +1 -0
  122. package/dist/esm/ProcessSandbox.d.ts +377 -0
  123. package/dist/esm/ProcessSandbox.d.ts.map +1 -0
  124. package/dist/esm/ProcessSandbox.js +1014 -0
  125. package/dist/esm/ProcessSandbox.js.map +1 -0
  126. package/dist/esm/ScopedProcess.d.ts +65 -0
  127. package/dist/esm/ScopedProcess.d.ts.map +1 -0
  128. package/dist/esm/ScopedProcess.js +66 -0
  129. package/dist/esm/ScopedProcess.js.map +1 -0
  130. package/dist/esm/index.d.ts +38 -0
  131. package/dist/esm/index.d.ts.map +1 -0
  132. package/dist/esm/index.js +38 -0
  133. package/dist/esm/index.js.map +1 -0
  134. package/dist/esm/internal/AtomicFileSystemExecutable.d.ts +61 -0
  135. package/dist/esm/internal/AtomicFileSystemExecutable.d.ts.map +1 -0
  136. package/dist/esm/internal/AtomicFileSystemExecutable.js +350 -0
  137. package/dist/esm/internal/AtomicFileSystemExecutable.js.map +1 -0
  138. package/dist/esm/internal/AtomicFileSystemProtocol.d.ts +78 -0
  139. package/dist/esm/internal/AtomicFileSystemProtocol.d.ts.map +1 -0
  140. package/dist/esm/internal/AtomicFileSystemProtocol.js +412 -0
  141. package/dist/esm/internal/AtomicFileSystemProtocol.js.map +1 -0
  142. package/dist/esm/internal/AtomicFileSystemTransport.d.ts +44 -0
  143. package/dist/esm/internal/AtomicFileSystemTransport.d.ts.map +1 -0
  144. package/dist/esm/internal/AtomicFileSystemTransport.js +242 -0
  145. package/dist/esm/internal/AtomicFileSystemTransport.js.map +1 -0
  146. package/dist/esm/internal/AtomicHelperError.d.ts +35 -0
  147. package/dist/esm/internal/AtomicHelperError.d.ts.map +1 -0
  148. package/dist/esm/internal/AtomicHelperError.js +39 -0
  149. package/dist/esm/internal/AtomicHelperError.js.map +1 -0
  150. package/dist/esm/internal/AtomicHelperFault.d.ts +59 -0
  151. package/dist/esm/internal/AtomicHelperFault.d.ts.map +1 -0
  152. package/dist/esm/internal/AtomicHelperFault.js +66 -0
  153. package/dist/esm/internal/AtomicHelperFault.js.map +1 -0
  154. package/dist/esm/internal/PipedProcess.d.ts +35 -0
  155. package/dist/esm/internal/PipedProcess.d.ts.map +1 -0
  156. package/dist/esm/internal/PipedProcess.js +321 -0
  157. package/dist/esm/internal/PipedProcess.js.map +1 -0
  158. package/dist/esm/internal/ProcSnapshot.d.ts +85 -0
  159. package/dist/esm/internal/ProcSnapshot.d.ts.map +1 -0
  160. package/dist/esm/internal/ProcSnapshot.js +188 -0
  161. package/dist/esm/internal/ProcSnapshot.js.map +1 -0
  162. package/dist/esm/internal/ProcessCleanup.d.ts +64 -0
  163. package/dist/esm/internal/ProcessCleanup.d.ts.map +1 -0
  164. package/dist/esm/internal/ProcessCleanup.js +45 -0
  165. package/dist/esm/internal/ProcessCleanup.js.map +1 -0
  166. package/dist/esm/internal/ProcessCleanupPolicyError.d.ts +30 -0
  167. package/dist/esm/internal/ProcessCleanupPolicyError.d.ts.map +1 -0
  168. package/dist/esm/internal/ProcessCleanupPolicyError.js +25 -0
  169. package/dist/esm/internal/ProcessCleanupPolicyError.js.map +1 -0
  170. package/dist/esm/internal/ProcessFault.d.ts +63 -0
  171. package/dist/esm/internal/ProcessFault.d.ts.map +1 -0
  172. package/dist/esm/internal/ProcessFault.js +64 -0
  173. package/dist/esm/internal/ProcessFault.js.map +1 -0
  174. package/dist/esm/internal/ProcessSupervisor.d.ts +141 -0
  175. package/dist/esm/internal/ProcessSupervisor.d.ts.map +1 -0
  176. package/dist/esm/internal/ProcessSupervisor.js +682 -0
  177. package/dist/esm/internal/ProcessSupervisor.js.map +1 -0
  178. package/dist/esm/internal/SupervisorProgram.d.ts +9 -0
  179. package/dist/esm/internal/SupervisorProgram.d.ts.map +1 -0
  180. package/dist/esm/internal/SupervisorProgram.js +290 -0
  181. package/dist/esm/internal/SupervisorProgram.js.map +1 -0
  182. package/dist/esm/internal/WindowsProcessJob.d.ts +39 -0
  183. package/dist/esm/internal/WindowsProcessJob.d.ts.map +1 -0
  184. package/dist/esm/internal/WindowsProcessJob.js +115 -0
  185. package/dist/esm/internal/WindowsProcessJob.js.map +1 -0
  186. package/dist/vendor/adapters.json +1757 -0
  187. package/dist/vendor/chunk-2Y7WC4KU.js +109 -0
  188. package/dist/vendor/chunk-3GR5VZ2M.js +118 -0
  189. package/dist/vendor/chunk-42QKY6T6.js +40 -0
  190. package/dist/vendor/chunk-54RLBMTZ.js +61 -0
  191. package/dist/vendor/chunk-5ZMHBXIK.js +55 -0
  192. package/dist/vendor/chunk-6ME2W7TF.js +28 -0
  193. package/dist/vendor/chunk-AMQKOQNT.js +34 -0
  194. package/dist/vendor/chunk-B3FZ67QU.js +59 -0
  195. package/dist/vendor/chunk-BKTBUBQY.js +437 -0
  196. package/dist/vendor/chunk-DS7GF46S.js +34 -0
  197. package/dist/vendor/chunk-DVOAHXPG.js +104 -0
  198. package/dist/vendor/chunk-DWSHTSVX.js +229 -0
  199. package/dist/vendor/chunk-EGBNVEEW.js +14 -0
  200. package/dist/vendor/chunk-EZWY45HP.js +18 -0
  201. package/dist/vendor/chunk-FNZOJHSV.js +71 -0
  202. package/dist/vendor/chunk-GXCHI7BM.js +131 -0
  203. package/dist/vendor/chunk-IIJ52ZU6.js +20 -0
  204. package/dist/vendor/chunk-J7A4CZDO.js +92 -0
  205. package/dist/vendor/chunk-JG5TTWPT.js +85 -0
  206. package/dist/vendor/chunk-JJ24ARBQ.js +394 -0
  207. package/dist/vendor/chunk-KOZMM5PZ.js +426 -0
  208. package/dist/vendor/chunk-KUUXSID7.js +18 -0
  209. package/dist/vendor/chunk-LEUPYT3Q.js +36 -0
  210. package/dist/vendor/chunk-M32LDTW7.js +18 -0
  211. package/dist/vendor/chunk-M7CLUILA.js +0 -0
  212. package/dist/vendor/chunk-MBSKBQ5D.js +21 -0
  213. package/dist/vendor/chunk-MLKGABMK.js +9 -0
  214. package/dist/vendor/chunk-NBVRTDTV.js +129 -0
  215. package/dist/vendor/chunk-OXNGN5H7.js +81 -0
  216. package/dist/vendor/chunk-QPLDT6A6.js +564 -0
  217. package/dist/vendor/chunk-QYBKAIA6.js +18 -0
  218. package/dist/vendor/chunk-RO2R4MJH.js +62 -0
  219. package/dist/vendor/chunk-SIR62W3Y.js +398 -0
  220. package/dist/vendor/chunk-TFIKFFSC.js +26 -0
  221. package/dist/vendor/chunk-TXKLEBBL.js +143 -0
  222. package/dist/vendor/chunk-UFH5UUZK.js +48 -0
  223. package/dist/vendor/chunk-VANKHF46.js +96 -0
  224. package/dist/vendor/chunk-VZBDV3TV.js +18 -0
  225. package/dist/vendor/chunk-XDKNKFBW.js +22 -0
  226. package/dist/vendor/chunk-XH4U3JSV.js +249 -0
  227. package/dist/vendor/chunk-Y35ZAO5U.js +53 -0
  228. package/dist/vendor/chunk-ZJMRMY57.js +0 -0
  229. package/dist/vendor/cjs/node/NodeChildProcessSpawner.d.ts +7 -0
  230. package/dist/vendor/cjs/node/NodeChildProcessSpawner.js +1 -0
  231. package/dist/vendor/cjs/node/NodeClusterHttp.d.ts +63 -0
  232. package/dist/vendor/cjs/node/NodeClusterHttp.js +1 -0
  233. package/dist/vendor/cjs/node/NodeClusterSocket.d.ts +79 -0
  234. package/dist/vendor/cjs/node/NodeClusterSocket.js +1 -0
  235. package/dist/vendor/cjs/node/NodeCrypto.d.ts +10 -0
  236. package/dist/vendor/cjs/node/NodeCrypto.js +1 -0
  237. package/dist/vendor/cjs/node/NodeFileSystem.d.ts +10 -0
  238. package/dist/vendor/cjs/node/NodeFileSystem.js +1 -0
  239. package/dist/vendor/cjs/node/NodeHttpClient.d.ts +179 -0
  240. package/dist/vendor/cjs/node/NodeHttpClient.js +1 -0
  241. package/dist/vendor/cjs/node/NodeHttpIncomingMessage.d.ts +62 -0
  242. package/dist/vendor/cjs/node/NodeHttpIncomingMessage.js +1 -0
  243. package/dist/vendor/cjs/node/NodeHttpPlatform.d.ts +36 -0
  244. package/dist/vendor/cjs/node/NodeHttpPlatform.js +1 -0
  245. package/dist/vendor/cjs/node/NodeHttpServer.d.ts +120 -0
  246. package/dist/vendor/cjs/node/NodeHttpServer.js +1 -0
  247. package/dist/vendor/cjs/node/NodeHttpServerRequest.d.ts +29 -0
  248. package/dist/vendor/cjs/node/NodeHttpServerRequest.js +1 -0
  249. package/dist/vendor/cjs/node/NodeMultipart.d.ts +46 -0
  250. package/dist/vendor/cjs/node/NodeMultipart.js +1 -0
  251. package/dist/vendor/cjs/node/NodeMultipartParser.d.ts +97 -0
  252. package/dist/vendor/cjs/node/NodeMultipartParser.js +1 -0
  253. package/dist/vendor/cjs/node/NodePath.d.ts +27 -0
  254. package/dist/vendor/cjs/node/NodePath.js +1 -0
  255. package/dist/vendor/cjs/node/NodeRedis.d.ts +74 -0
  256. package/dist/vendor/cjs/node/NodeRedis.js +1 -0
  257. package/dist/vendor/cjs/node/NodeRuntime.d.ts +85 -0
  258. package/dist/vendor/cjs/node/NodeRuntime.js +1 -0
  259. package/dist/vendor/cjs/node/NodeServices.d.ts +34 -0
  260. package/dist/vendor/cjs/node/NodeServices.js +1 -0
  261. package/dist/vendor/cjs/node/NodeSink.d.ts +8 -0
  262. package/dist/vendor/cjs/node/NodeSink.js +1 -0
  263. package/dist/vendor/cjs/node/NodeSocket.d.ts +38 -0
  264. package/dist/vendor/cjs/node/NodeSocket.js +1 -0
  265. package/dist/vendor/cjs/node/NodeSocketServer.d.ts +8 -0
  266. package/dist/vendor/cjs/node/NodeSocketServer.js +1 -0
  267. package/dist/vendor/cjs/node/NodeStdio.d.ts +11 -0
  268. package/dist/vendor/cjs/node/NodeStdio.js +1 -0
  269. package/dist/vendor/cjs/node/NodeStream.d.ts +8 -0
  270. package/dist/vendor/cjs/node/NodeStream.js +1 -0
  271. package/dist/vendor/cjs/node/NodeTerminal.d.ts +21 -0
  272. package/dist/vendor/cjs/node/NodeTerminal.js +1 -0
  273. package/dist/vendor/cjs/node/NodeWorker.d.ts +22 -0
  274. package/dist/vendor/cjs/node/NodeWorker.js +1 -0
  275. package/dist/vendor/cjs/node/NodeWorkerRunner.d.ts +12 -0
  276. package/dist/vendor/cjs/node/NodeWorkerRunner.js +1 -0
  277. package/dist/vendor/cjs/node/Undici.d.ts +28 -0
  278. package/dist/vendor/cjs/node/Undici.js +1 -0
  279. package/dist/vendor/cjs/node/index.d.ts +100 -0
  280. package/dist/vendor/cjs/node/index.js +1 -0
  281. package/dist/vendor/cjs/node-shared/NodeChildProcessSpawner.d.ts +57 -0
  282. package/dist/vendor/cjs/node-shared/NodeChildProcessSpawner.js +1 -0
  283. package/dist/vendor/cjs/node-shared/NodeClusterSocket.d.ts +22 -0
  284. package/dist/vendor/cjs/node-shared/NodeClusterSocket.js +1 -0
  285. package/dist/vendor/cjs/node-shared/NodeCrypto.d.ts +27 -0
  286. package/dist/vendor/cjs/node-shared/NodeCrypto.js +1 -0
  287. package/dist/vendor/cjs/node-shared/NodeFileSystem.d.ts +11 -0
  288. package/dist/vendor/cjs/node-shared/NodeFileSystem.js +1 -0
  289. package/dist/vendor/cjs/node-shared/NodeHttpCompression.d.ts +36 -0
  290. package/dist/vendor/cjs/node-shared/NodeHttpCompression.js +1 -0
  291. package/dist/vendor/cjs/node-shared/NodePath.d.ts +27 -0
  292. package/dist/vendor/cjs/node-shared/NodePath.js +1 -0
  293. package/dist/vendor/cjs/node-shared/NodeRuntime.d.ts +47 -0
  294. package/dist/vendor/cjs/node-shared/NodeRuntime.js +1 -0
  295. package/dist/vendor/cjs/node-shared/NodeSink.d.ts +69 -0
  296. package/dist/vendor/cjs/node-shared/NodeSink.js +1 -0
  297. package/dist/vendor/cjs/node-shared/NodeSocket.d.ts +134 -0
  298. package/dist/vendor/cjs/node-shared/NodeSocket.js +1 -0
  299. package/dist/vendor/cjs/node-shared/NodeSocketServer.d.ts +80 -0
  300. package/dist/vendor/cjs/node-shared/NodeSocketServer.js +1 -0
  301. package/dist/vendor/cjs/node-shared/NodeStdio.d.ts +12 -0
  302. package/dist/vendor/cjs/node-shared/NodeStdio.js +1 -0
  303. package/dist/vendor/cjs/node-shared/NodeStream.d.ts +179 -0
  304. package/dist/vendor/cjs/node-shared/NodeStream.js +1 -0
  305. package/dist/vendor/cjs/node-shared/NodeTerminal.d.ts +22 -0
  306. package/dist/vendor/cjs/node-shared/NodeTerminal.js +1 -0
  307. package/dist/vendor/cjs/node-shared/index.d.ts +56 -0
  308. package/dist/vendor/cjs/node-shared/index.js +1 -0
  309. package/dist/vendor/cjs/node-shared/internal/nodeChildProcessSpawner.d.ts +4 -0
  310. package/dist/vendor/cjs/node-shared/internal/nodeChildProcessSpawner.js +1 -0
  311. package/dist/vendor/cjs/node-shared/internal/utils.d.ts +2 -0
  312. package/dist/vendor/cjs/node-shared/internal/utils.js +1 -0
  313. package/dist/vendor/cjs/package.json +1 -0
  314. package/dist/vendor/cjs/platform-node-shared.d.ts +56 -0
  315. package/dist/vendor/cjs/platform-node-shared.js +1 -0
  316. package/dist/vendor/cjs/platform-node.d.ts +100 -0
  317. package/dist/vendor/cjs/platform-node.js +1 -0
  318. package/dist/vendor/node/LICENSE +21 -0
  319. package/dist/vendor/node/NodeChildProcessSpawner.d.ts +7 -0
  320. package/dist/vendor/node/NodeChildProcessSpawner.js +14 -0
  321. package/dist/vendor/node/NodeClusterHttp.d.ts +63 -0
  322. package/dist/vendor/node/NodeClusterHttp.js +43 -0
  323. package/dist/vendor/node/NodeClusterSocket.d.ts +79 -0
  324. package/dist/vendor/node/NodeClusterSocket.js +30 -0
  325. package/dist/vendor/node/NodeCrypto.d.ts +10 -0
  326. package/dist/vendor/node/NodeCrypto.js +8 -0
  327. package/dist/vendor/node/NodeFileSystem.d.ts +10 -0
  328. package/dist/vendor/node/NodeFileSystem.js +9 -0
  329. package/dist/vendor/node/NodeHttpClient.d.ts +179 -0
  330. package/dist/vendor/node/NodeHttpClient.js +46 -0
  331. package/dist/vendor/node/NodeHttpIncomingMessage.d.ts +62 -0
  332. package/dist/vendor/node/NodeHttpIncomingMessage.js +10 -0
  333. package/dist/vendor/node/NodeHttpPlatform.d.ts +36 -0
  334. package/dist/vendor/node/NodeHttpPlatform.js +16 -0
  335. package/dist/vendor/node/NodeHttpServer.d.ts +120 -0
  336. package/dist/vendor/node/NodeHttpServer.js +46 -0
  337. package/dist/vendor/node/NodeHttpServerRequest.d.ts +29 -0
  338. package/dist/vendor/node/NodeHttpServerRequest.js +9 -0
  339. package/dist/vendor/node/NodeMultipart.d.ts +46 -0
  340. package/dist/vendor/node/NodeMultipart.js +15 -0
  341. package/dist/vendor/node/NodeMultipartParser.d.ts +97 -0
  342. package/dist/vendor/node/NodeMultipartParser.js +11 -0
  343. package/dist/vendor/node/NodePath.d.ts +27 -0
  344. package/dist/vendor/node/NodePath.js +12 -0
  345. package/dist/vendor/node/NodeRedis.d.ts +74 -0
  346. package/dist/vendor/node/NodeRedis.js +11 -0
  347. package/dist/vendor/node/NodeRuntime.d.ts +85 -0
  348. package/dist/vendor/node/NodeRuntime.js +8 -0
  349. package/dist/vendor/node/NodeServices.d.ts +34 -0
  350. package/dist/vendor/node/NodeServices.js +23 -0
  351. package/dist/vendor/node/NodeSink.d.ts +8 -0
  352. package/dist/vendor/node/NodeSink.js +12 -0
  353. package/dist/vendor/node/NodeSocket.d.ts +38 -0
  354. package/dist/vendor/node/NodeSocket.js +31 -0
  355. package/dist/vendor/node/NodeSocketServer.d.ts +8 -0
  356. package/dist/vendor/node/NodeSocketServer.js +21 -0
  357. package/dist/vendor/node/NodeStdio.d.ts +11 -0
  358. package/dist/vendor/node/NodeStdio.js +10 -0
  359. package/dist/vendor/node/NodeStream.d.ts +8 -0
  360. package/dist/vendor/node/NodeStream.js +27 -0
  361. package/dist/vendor/node/NodeTerminal.d.ts +21 -0
  362. package/dist/vendor/node/NodeTerminal.js +10 -0
  363. package/dist/vendor/node/NodeWorker.d.ts +22 -0
  364. package/dist/vendor/node/NodeWorker.js +9 -0
  365. package/dist/vendor/node/NodeWorkerRunner.d.ts +12 -0
  366. package/dist/vendor/node/NodeWorkerRunner.js +7 -0
  367. package/dist/vendor/node/Undici.d.ts +28 -0
  368. package/dist/vendor/node/Undici.js +9 -0
  369. package/dist/vendor/node/index.d.ts +100 -0
  370. package/dist/vendor/node/index.js +115 -0
  371. package/dist/vendor/node-shared/LICENSE +21 -0
  372. package/dist/vendor/node-shared/NodeChildProcessSpawner.d.ts +57 -0
  373. package/dist/vendor/node-shared/NodeChildProcessSpawner.js +13 -0
  374. package/dist/vendor/node-shared/NodeClusterSocket.d.ts +22 -0
  375. package/dist/vendor/node-shared/NodeClusterSocket.js +11 -0
  376. package/dist/vendor/node-shared/NodeCrypto.d.ts +27 -0
  377. package/dist/vendor/node-shared/NodeCrypto.js +9 -0
  378. package/dist/vendor/node-shared/NodeFileSystem.d.ts +11 -0
  379. package/dist/vendor/node-shared/NodeFileSystem.js +8 -0
  380. package/dist/vendor/node-shared/NodeHttpCompression.d.ts +36 -0
  381. package/dist/vendor/node-shared/NodeHttpCompression.js +13 -0
  382. package/dist/vendor/node-shared/NodePath.d.ts +27 -0
  383. package/dist/vendor/node-shared/NodePath.js +11 -0
  384. package/dist/vendor/node-shared/NodeRuntime.d.ts +47 -0
  385. package/dist/vendor/node-shared/NodeRuntime.js +7 -0
  386. package/dist/vendor/node-shared/NodeSink.d.ts +69 -0
  387. package/dist/vendor/node-shared/NodeSink.js +11 -0
  388. package/dist/vendor/node-shared/NodeSocket.d.ts +134 -0
  389. package/dist/vendor/node-shared/NodeSocket.js +23 -0
  390. package/dist/vendor/node-shared/NodeSocketServer.d.ts +80 -0
  391. package/dist/vendor/node-shared/NodeSocketServer.js +20 -0
  392. package/dist/vendor/node-shared/NodeStdio.d.ts +12 -0
  393. package/dist/vendor/node-shared/NodeStdio.js +9 -0
  394. package/dist/vendor/node-shared/NodeStream.d.ts +179 -0
  395. package/dist/vendor/node-shared/NodeStream.js +26 -0
  396. package/dist/vendor/node-shared/NodeTerminal.d.ts +22 -0
  397. package/dist/vendor/node-shared/NodeTerminal.js +9 -0
  398. package/dist/vendor/node-shared/index.d.ts +56 -0
  399. package/dist/vendor/node-shared/index.js +58 -0
  400. package/dist/vendor/node-shared/internal/nodeChildProcessSpawner.d.ts +4 -0
  401. package/dist/vendor/node-shared/internal/nodeChildProcessSpawner.js +7 -0
  402. package/dist/vendor/node-shared/internal/utils.d.ts +2 -0
  403. package/dist/vendor/node-shared/internal/utils.js +7 -0
  404. package/dist/vendor/platform-node-shared.d.ts +56 -0
  405. package/dist/vendor/platform-node-shared.js +58 -0
  406. package/dist/vendor/platform-node.d.ts +100 -0
  407. package/dist/vendor/platform-node.js +115 -0
  408. package/docs/README.md +129 -0
  409. package/docs/api.md +552 -0
  410. package/docs/concepts/descriptor-relative-filesystem.md +181 -0
  411. package/docs/concepts/host-bundle.md +125 -0
  412. package/docs/concepts/process-containment.md +182 -0
  413. package/docs/guides/answer-run-ownership.md +87 -0
  414. package/docs/guides/configure-the-filesystem-helper.md +152 -0
  415. package/docs/guides/contain-child-processes.md +139 -0
  416. package/docs/guides/match-files-with-glob.md +151 -0
  417. package/docs/installation.md +204 -0
  418. package/docs/quickstart.md +147 -0
  419. package/docs/troubleshooting.md +130 -0
  420. package/package.json +202 -3
  421. package/src/AtomicFileSystem.ts +415 -0
  422. package/src/EgressHttpClient.ts +327 -0
  423. package/src/HostLiveness.ts +103 -0
  424. package/src/NodeHost.ts +253 -0
  425. package/src/ProcessConfinement.ts +356 -0
  426. package/src/ProcessReaper.ts +951 -0
  427. package/src/ProcessSandbox.ts +1273 -0
  428. package/src/ScopedProcess.ts +123 -0
  429. package/src/index.ts +44 -0
  430. package/src/internal/AtomicFileSystemExecutable.ts +379 -0
  431. package/src/internal/AtomicFileSystemProtocol.ts +502 -0
  432. package/src/internal/AtomicFileSystemTransport.ts +272 -0
  433. package/src/internal/AtomicHelperError.ts +50 -0
  434. package/src/internal/AtomicHelperFault.ts +84 -0
  435. package/src/internal/PipedProcess.ts +357 -0
  436. package/src/internal/ProcSnapshot.ts +193 -0
  437. package/src/internal/ProcessCleanup.ts +99 -0
  438. package/src/internal/ProcessCleanupPolicyError.ts +30 -0
  439. package/src/internal/ProcessFault.ts +82 -0
  440. package/src/internal/ProcessSupervisor.ts +801 -0
  441. package/src/internal/SupervisorProgram.ts +289 -0
  442. package/src/internal/WindowsProcessJob.ts +114 -0
@@ -0,0 +1,1014 @@
1
+ /**
2
+ * The one operating-system sandbox every confined process goes through.
3
+ *
4
+ * A caller resolves what a run may touch, a {@link Request}: the network
5
+ * posture, the read and write sets, and optionally which mechanism it wants.
6
+ * This module turns the request into an argv the host enforces, or into an
7
+ * {@link Unenforceable} refusal naming what the host lacks. Two callers share
8
+ * it: the build graph confines every target's tool run under its declared
9
+ * inputs and outputs (`@smthrs/targets/ExecSandbox`), and the Node
10
+ * `ProcessConfinement` confines every approved agent command under the
11
+ * grants in force.
12
+ *
13
+ * Three mechanisms, one contract:
14
+ *
15
+ * - Linux: bubblewrap. An empty root contains only enumerated system/runtime
16
+ * paths and declared reads and writes. The workspace and root are remounted
17
+ * read-only after mounting grants. A private tmp supplies HOME. The network
18
+ * namespace is unshared unless explicitly opened; loopback-only is refused.
19
+ * - macOS: seatbelt (`sandbox-exec`). Host reads are denied except enumerated
20
+ * system/runtime paths, declared inputs, external reads, writes and private
21
+ * tmp. Host credential locations are re-closed under broad grants. Writes
22
+ * and network access are denied except what the request opens.
23
+ * - Docker: `docker run` with a read-only root, declared reads mounted
24
+ * read-only at their host paths, declared writes read-write, `--network
25
+ * none` unless the request opens it. The image supplies the toolchain. It is
26
+ * the path on Windows and on any host without the native mechanism.
27
+ *
28
+ * Every declared confinement is enforced or the caller fails closed. Nothing
29
+ * here logs "unenforced" and carries on; the one caller that may run
30
+ * unconfined when no mechanism exists, `ProcessConfinement`, says so in its
31
+ * own options and never on this module's behalf.
32
+ *
33
+ * @since 1.0.0-rc.1
34
+ */
35
+ import { execFileSync } from "node:child_process";
36
+ import { randomUUID } from "node:crypto";
37
+ import * as NodeFs from "node:fs";
38
+ import * as NodeOs from "node:os";
39
+ import * as NodePath from "node:path";
40
+ /**
41
+ * Checks for an {@link Unenforceable} refusal.
42
+ *
43
+ * @category guards
44
+ * @since 1.0.0-rc.1
45
+ */
46
+ export const isUnenforceable = (value) => typeof value === "object" && value !== null &&
47
+ value._tag === "@smthrs/platform-node/SandboxUnenforceable";
48
+ /**
49
+ * Builds an {@link Unenforceable} refusal.
50
+ *
51
+ * @category constructors
52
+ * @since 1.0.0-rc.1
53
+ */
54
+ export const unenforceable = (platform, mechanism, missing, detail) => ({
55
+ _tag: "@smthrs/platform-node/SandboxUnenforceable",
56
+ platform,
57
+ mechanism,
58
+ missing,
59
+ message: `sandbox: the declared confinement cannot be enforced on this ${platform} host: ${detail}.`
60
+ });
61
+ const pathSeparator = (platform) => platform === "win32" ? ";" : ":";
62
+ /** Ambient host environment for runtime lookup and cache locations. */
63
+ const ambientEnvironment = () => process.env;
64
+ /**
65
+ * The real host: platform, `PATH` lookup, and filesystem probes.
66
+ *
67
+ * @category resolution
68
+ * @since 0.1.0
69
+ */
70
+ export const host = (env = ambientEnvironment()) => {
71
+ const platform = process.platform;
72
+ let developerDirectory = env["DEVELOPER_DIR"];
73
+ if (platform === "darwin" && developerDirectory === undefined) {
74
+ try {
75
+ developerDirectory = execFileSync("/usr/bin/xcode-select", ["-p"], { encoding: "utf8", timeout: 2_000 }).trim();
76
+ }
77
+ catch {
78
+ // A host without Xcode needs no developer-tree grant.
79
+ }
80
+ }
81
+ const entries = (env["PATH"] ?? "").split(pathSeparator(platform)).filter((entry) => entry !== "");
82
+ const extensions = platform === "win32" ? (env["PATHEXT"] ?? ".EXE;.CMD;.BAT").split(";") : [""];
83
+ return {
84
+ platform,
85
+ developerDirectory,
86
+ home: NodeOs.homedir(),
87
+ executable: (name) => {
88
+ if (NodePath.isAbsolute(name))
89
+ return isExecutable(name) ? name : undefined;
90
+ for (const entry of entries) {
91
+ for (const extension of extensions) {
92
+ const candidate = NodePath.join(entry, name + extension);
93
+ if (isExecutable(candidate))
94
+ return candidate;
95
+ }
96
+ }
97
+ return undefined;
98
+ },
99
+ exists: (path) => NodeFs.existsSync(path),
100
+ isSymbolicLink,
101
+ isDirectory: (path) => {
102
+ try {
103
+ return NodeFs.statSync(path).isDirectory();
104
+ }
105
+ catch {
106
+ return false;
107
+ }
108
+ },
109
+ entries: (directory) => {
110
+ try {
111
+ return NodeFs.readdirSync(directory);
112
+ }
113
+ catch {
114
+ return undefined;
115
+ }
116
+ },
117
+ realpath: (path) => {
118
+ try {
119
+ return NodeFs.realpathSync(path);
120
+ }
121
+ catch {
122
+ return undefined;
123
+ }
124
+ },
125
+ uid: typeof process.getuid === "function" ? process.getuid() : undefined,
126
+ gid: typeof process.getgid === "function" ? process.getgid() : undefined
127
+ };
128
+ };
129
+ const isExecutable = (path) => {
130
+ try {
131
+ NodeFs.accessSync(path, NodeFs.constants.X_OK);
132
+ return NodeFs.statSync(path).isFile();
133
+ }
134
+ catch {
135
+ return false;
136
+ }
137
+ };
138
+ /**
139
+ * Selects the mechanism a request runs under on a host.
140
+ *
141
+ * A docker selection is honored when `docker` is on `PATH` and refused
142
+ * otherwise; a bubblewrap selection is refused off Linux. With no selection
143
+ * the platform decides: bubblewrap on Linux, seatbelt on macOS, and a refusal
144
+ * elsewhere, because Windows has no user-level process sandbox and Docker
145
+ * needs an image only the caller can name.
146
+ *
147
+ * @category resolution
148
+ * @since 1.0.0-rc.1
149
+ */
150
+ export const select = (request, hostFacts) => {
151
+ const declared = request.mechanism;
152
+ const platform = hostFacts.platform;
153
+ if (declared !== undefined && declared._tag === "docker") {
154
+ const executable = hostFacts.executable("docker");
155
+ if (executable === undefined) {
156
+ return unenforceable(platform, "docker", "docker", "a docker mechanism is selected but docker is not on PATH");
157
+ }
158
+ return { _tag: "docker", executable, image: declared.image };
159
+ }
160
+ if (declared !== undefined && platform !== "linux") {
161
+ return unenforceable(platform, "bubblewrap", "linux", "bubblewrap is selected but runs only on Linux; on macOS the seatbelt mechanism is selected when no mechanism is named");
162
+ }
163
+ if (platform === "linux") {
164
+ const executable = hostFacts.executable("bwrap");
165
+ if (executable === undefined) {
166
+ return unenforceable(platform, "bubblewrap", "bwrap", "bwrap is not on PATH (install the bubblewrap package)");
167
+ }
168
+ if (request.network === "loopback") {
169
+ return unenforceable(platform, "bubblewrap", "network: true", "bubblewrap cannot expose only the host loopback interface: sharing that interface also grants full host networking; " +
170
+ "open the whole network to opt into that access");
171
+ }
172
+ return { _tag: "bubblewrap", executable };
173
+ }
174
+ if (platform === "darwin") {
175
+ const executable = hostFacts.executable("/usr/bin/sandbox-exec");
176
+ if (executable === undefined) {
177
+ return unenforceable(platform, "seatbelt", "/usr/bin/sandbox-exec", "/usr/bin/sandbox-exec is missing");
178
+ }
179
+ return { _tag: "seatbelt", executable };
180
+ }
181
+ return unenforceable(platform, "docker", "docker image", `${platform} has no process sandbox of its own; select a docker mechanism with an image`);
182
+ };
183
+ const toPosix = (path) => path.split(NodePath.sep).join("/");
184
+ /**
185
+ * The path with the symlinks of its existing ancestors resolved. A path that
186
+ * does not exist yet keeps its unresolved tail below the deepest ancestor
187
+ * that does; a path with no existing ancestor is returned as given.
188
+ */
189
+ const realized = (path) => {
190
+ let head = path;
191
+ const tail = [];
192
+ while (head !== NodePath.dirname(head)) {
193
+ try {
194
+ const real = NodeFs.realpathSync(head);
195
+ return tail.length === 0 ? real : NodePath.join(real, ...tail);
196
+ }
197
+ catch {
198
+ tail.unshift(NodePath.basename(head));
199
+ head = NodePath.dirname(head);
200
+ }
201
+ }
202
+ return path;
203
+ };
204
+ const insideRoot = (root, candidate) => {
205
+ const relative = NodePath.relative(root, candidate);
206
+ return relative === "" ||
207
+ (relative !== ".." && !relative.startsWith(`..${NodePath.sep}`) && !NodePath.isAbsolute(relative));
208
+ };
209
+ /** Unlike exists/stat, lstat sees dangling links and does not follow them. */
210
+ const isSymbolicLink = (path) => {
211
+ try {
212
+ return NodeFs.lstatSync(path).isSymbolicLink();
213
+ }
214
+ catch (cause) {
215
+ if (cause.code === "ENOENT")
216
+ return false;
217
+ throw cause;
218
+ }
219
+ };
220
+ /** Check each component, including existing ancestors of a missing output. */
221
+ const safeWrite = (root, path, hostFacts) => {
222
+ if (!insideRoot(root, path))
223
+ return false;
224
+ try {
225
+ const realpath = hostFacts.realpath ?? NodeFs.realpathSync;
226
+ const link = hostFacts.isSymbolicLink ?? isSymbolicLink;
227
+ if (realpath(root) !== root)
228
+ return false;
229
+ let current = root;
230
+ for (const component of NodePath.relative(root, path).split(NodePath.sep).filter(Boolean)) {
231
+ current = NodePath.join(current, component);
232
+ if (link(current))
233
+ return false;
234
+ if (hostFacts.exists(current) && realpath(current) !== current)
235
+ return false;
236
+ }
237
+ return true;
238
+ }
239
+ catch {
240
+ return false;
241
+ }
242
+ };
243
+ const unsafeWrite = (platform, mechanism, path) => unenforceable(platform, mechanism, "canonical workspace write path", `write path ${path} has a symbolic link, an unresolved ancestor, or leaves the canonical workspace`);
244
+ /**
245
+ * Rechecks write grants before directory creation and before rendering. Throws
246
+ * an {@link Unenforceable} refusal if a path changed since planning. Callers
247
+ * must keep the workspace stable until the operating system consumes mounts.
248
+ *
249
+ * @category resolution
250
+ * @since 0.1.0
251
+ */
252
+ export const validateWrites = (confinement, hostFacts = host()) => {
253
+ for (const write of confinement.writes) {
254
+ if (!safeWrite(confinement.workspaceRoot, write, hostFacts)) {
255
+ throw unsafeWrite(hostFacts.platform, confinement.mechanism._tag, write);
256
+ }
257
+ }
258
+ };
259
+ /**
260
+ * Folds a declared file set into directories plus the entries to re-close.
261
+ *
262
+ * The seatbelt compiler caps one rule's data at 64 KiB, so a workspace with a
263
+ * few thousand declared sources cannot be granted file by file. Deepest
264
+ * first, a directory in which at least as many entries are covered as not is
265
+ * granted whole, and its uncovered entries are listed for a deny rule that
266
+ * follows the grant. The result admits exactly the declared set plus the
267
+ * granted directories' future entries. A directory whose uncovered entry
268
+ * still holds a covered path deeper down is never folded over that entry,
269
+ * the workspace root is never folded, and a host that cannot list a
270
+ * directory leaves its files as they were. Covered means a declared read, a
271
+ * declared write, or the private tmp.
272
+ */
273
+ const fold = (root, declared, alsoCovered, hostFacts) => {
274
+ const entries = hostFacts.entries;
275
+ if (entries === undefined)
276
+ return { reads: declared, denies: [] };
277
+ const covered = new Set([...declared, ...alsoCovered]);
278
+ const candidates = new Set();
279
+ for (const path of declared) {
280
+ let current = NodePath.dirname(path);
281
+ while (insideRoot(root, current) && current !== root && !candidates.has(current)) {
282
+ candidates.add(current);
283
+ current = NodePath.dirname(current);
284
+ }
285
+ }
286
+ // An undeclared directory on the way down to a covered write or read-only
287
+ // path is not a candidate, so nothing else would stop its parent being
288
+ // folded over it. The deny that follows the grant names the directory, and
289
+ // because later rules win in SBPL it closes reads on the very subtree the
290
+ // plan grants a write to.
291
+ const holdsCovered = new Set();
292
+ for (const path of alsoCovered) {
293
+ let current = NodePath.dirname(path);
294
+ while (insideRoot(root, current) && current !== root && !holdsCovered.has(current)) {
295
+ holdsCovered.add(current);
296
+ current = NodePath.dirname(current);
297
+ }
298
+ }
299
+ const deepestFirst = [...candidates].sort((left, right) => right.length - left.length || (left < right ? -1 : 1));
300
+ const promoted = [];
301
+ const denies = [];
302
+ for (const directory of deepestFirst) {
303
+ if (covered.has(directory))
304
+ continue;
305
+ const children = entries(directory);
306
+ if (children === undefined || children.length === 0)
307
+ continue;
308
+ const uncovered = [];
309
+ let coveredCount = 0;
310
+ let partial = false;
311
+ for (const child of children) {
312
+ const path = NodePath.join(directory, child);
313
+ if (covered.has(path))
314
+ coveredCount += 1;
315
+ else if (candidates.has(path) || holdsCovered.has(path))
316
+ partial = true;
317
+ else
318
+ uncovered.push(path);
319
+ }
320
+ if (partial || coveredCount === 0 || coveredCount < uncovered.length)
321
+ continue;
322
+ covered.add(directory);
323
+ promoted.push(directory);
324
+ denies.push(...uncovered);
325
+ }
326
+ return { reads: [...declared, ...promoted], denies };
327
+ };
328
+ /**
329
+ * Orders paths so that every subtree is contiguous and its root comes first.
330
+ *
331
+ * Comparing on the path plus its separator is what makes that hold: `a/b` and
332
+ * `a/b/c` sort around `a/b.ts`, because `.` precedes `/`, and a plain
333
+ * comparison would then put a sibling between an ancestor and its child.
334
+ */
335
+ const bySubtree = (left, right) => {
336
+ const leftKey = left + NodePath.sep;
337
+ const rightKey = right + NodePath.sep;
338
+ return leftKey < rightKey ? -1 : 1;
339
+ };
340
+ /**
341
+ * Drops a path covered by another entry of the same set, keeping the set ordered.
342
+ *
343
+ * One sweep in subtree order carries the shallowest kept ancestor forward,
344
+ * because searching the kept set per path is quadratic in the sibling files a
345
+ * bubblewrap or Docker plan declares one by one.
346
+ */
347
+ const collapse = (paths) => {
348
+ const unique = [...new Set(paths)];
349
+ const kept = new Set();
350
+ let ancestor;
351
+ for (const path of [...unique].sort(bySubtree)) {
352
+ if (ancestor !== undefined && path.startsWith(ancestor + NodePath.sep))
353
+ continue;
354
+ kept.add(path);
355
+ ancestor = path;
356
+ }
357
+ return unique.filter((path) => kept.has(path)).sort((left, right) => left.length - right.length || (left < right ? -1 : 1));
358
+ };
359
+ /**
360
+ * Resolves a request against a workspace into a {@link Plan}, or refuses.
361
+ *
362
+ * Every relative path is anchored at the canonical workspace root; one that
363
+ * escapes the workspace is dropped, because the sandbox never grants anything
364
+ * outside the tree the key covers. Reads that do not exist are dropped too:
365
+ * bubblewrap cannot bind a missing path, and a declared input that vanished is
366
+ * the executor's measurement failure, not this module's. A write containing
367
+ * any symbolic link below the canonical root is refused, including a missing
368
+ * output below a linked ancestor.
369
+ *
370
+ * @category resolution
371
+ * @since 0.1.0
372
+ */
373
+ export const plan = (request, location, hostFacts) => {
374
+ const selected = select(request, hostFacts);
375
+ if (isUnenforceable(selected))
376
+ return selected;
377
+ let root;
378
+ try {
379
+ const canonical = (hostFacts.realpath ?? NodeFs.realpathSync)(location.workspaceRoot);
380
+ if (canonical === undefined)
381
+ return unsafeWrite(hostFacts.platform, selected._tag, location.workspaceRoot);
382
+ root = canonical;
383
+ }
384
+ catch {
385
+ return unsafeWrite(hostFacts.platform, selected._tag, location.workspaceRoot);
386
+ }
387
+ const anchor = (relative) => {
388
+ const absolute = NodePath.resolve(root, ...relative.split("/"));
389
+ return insideRoot(root, absolute) ? absolute : undefined;
390
+ };
391
+ const declared = [];
392
+ // A declared link grants its target as well: native read allowlists and
393
+ // Docker's image filesystem otherwise hide bytes outside the workspace.
394
+ const externalReads = [];
395
+ for (const relative of request.reads) {
396
+ const absolute = anchor(relative);
397
+ if (absolute === undefined || !hostFacts.exists(absolute))
398
+ continue;
399
+ declared.push(absolute);
400
+ const real = hostFacts.realpath?.(absolute);
401
+ if (real !== undefined && real !== absolute && !insideRoot(root, real))
402
+ externalReads.push(real);
403
+ }
404
+ for (const path of request.externalReads ?? []) {
405
+ if (!NodePath.isAbsolute(path) || insideRoot(root, path) || !hostFacts.exists(path))
406
+ continue;
407
+ externalReads.push(hostFacts.realpath?.(path) ?? path);
408
+ }
409
+ // A write names a directory the tool may fill, and is bound as declared
410
+ // whether or not it exists yet: a name is never read as a file, because a
411
+ // dot in `.cargo-home` or `dist.new` would otherwise widen the bind to the
412
+ // parent, and a parent that is the workspace root opens every file in it.
413
+ // A path that is already a file on the host is the one exception, since a
414
+ // file cannot be bound as a writable directory.
415
+ const writes = [];
416
+ const add = (absolute) => {
417
+ if (!safeWrite(root, absolute, hostFacts))
418
+ return unsafeWrite(hostFacts.platform, selected._tag, absolute);
419
+ writes.push(absolute);
420
+ return undefined;
421
+ };
422
+ for (const relative of request.writes) {
423
+ const absolute = anchor(relative);
424
+ if (absolute === undefined)
425
+ continue;
426
+ if (!safeWrite(root, absolute, hostFacts))
427
+ return unsafeWrite(hostFacts.platform, selected._tag, absolute);
428
+ const refusal = add(hostFacts.exists(absolute) && !hostFacts.isDirectory(absolute) ? NodePath.dirname(absolute) : absolute);
429
+ if (refusal !== undefined)
430
+ return refusal;
431
+ }
432
+ // A declared output file opens its parent: a file cannot be bound before it
433
+ // exists, and a tool that writes by rename needs the directory anyway.
434
+ for (const relative of request.writeFiles ?? []) {
435
+ const absolute = anchor(relative);
436
+ if (absolute === undefined)
437
+ continue;
438
+ if (!safeWrite(root, absolute, hostFacts))
439
+ return unsafeWrite(hostFacts.platform, selected._tag, absolute);
440
+ const refusal = add(hostFacts.exists(absolute) && hostFacts.isDirectory(absolute) ? absolute : NodePath.dirname(absolute));
441
+ if (refusal !== undefined)
442
+ return refusal;
443
+ }
444
+ const readOnly = [];
445
+ for (const relative of request.readOnly ?? []) {
446
+ const absolute = anchor(relative);
447
+ if (absolute !== undefined)
448
+ readOnly.push(absolute);
449
+ }
450
+ const folded = selected._tag === "seatbelt"
451
+ ? fold(root, declared, [...writes, ...readOnly, location.tmp], hostFacts)
452
+ : { reads: declared, denies: [] };
453
+ return {
454
+ mechanism: selected,
455
+ network: request.network,
456
+ unixSockets: request.unixSockets,
457
+ strict: request.strict,
458
+ workspaceRoot: root,
459
+ cwd: location.cwd,
460
+ reads: collapse(folded.reads),
461
+ readDenies: collapse(folded.denies),
462
+ writes: collapse(writes),
463
+ readOnly: collapse(readOnly),
464
+ externalReads: collapse(externalReads),
465
+ tmp: location.tmp,
466
+ uid: hostFacts.uid,
467
+ gid: hostFacts.gid
468
+ };
469
+ };
470
+ /**
471
+ * Where corepack keeps the package-manager binaries it manages, resolved the
472
+ * way corepack itself resolves it from a host environment.
473
+ *
474
+ * A `pnpm`, `yarn`, or `npm` on `PATH` is often corepack's shim, and the
475
+ * program it execs lives in this directory, not on `PATH`. Redirecting `HOME`
476
+ * and `XDG_CACHE_HOME` to the private tmp moves that directory out from under
477
+ * the shim, which then tries to re-download the package manager from the
478
+ * registry — and a confined run has no network, so every rule that drives the
479
+ * package manager fails with a connect error that names the registry rather
480
+ * than the missing cache.
481
+ */
482
+ const corepackHome = (env, home) => {
483
+ const declared = env["COREPACK_HOME"];
484
+ if (declared !== undefined && declared !== "")
485
+ return declared;
486
+ const base = env["XDG_CACHE_HOME"] ?? env["LOCALAPPDATA"] ??
487
+ NodePath.join(home, process.platform === "win32" ? "AppData/Local" : ".cache");
488
+ return NodePath.join(base, "node", "corepack");
489
+ };
490
+ /**
491
+ * Environment the confined process receives on top of the tool environment:
492
+ * a private temporary directory and home, so nothing a tool caches lands in
493
+ * the real home or the shared temp directory.
494
+ *
495
+ * `COREPACK_HOME` is the one host cache kept: it holds the package-manager
496
+ * program a corepack shim execs, the run may only read it (the mechanisms
497
+ * deny writes outside the workspace), and without it a confined `pnpm` is a
498
+ * shim with nothing to run. A non-default cache location requires an explicit
499
+ * `externalReads` grant; preserving the variable does not grant access.
500
+ *
501
+ * @category rendering
502
+ * @since 0.1.0
503
+ */
504
+ export const environment = (confinement, hostEnv = ambientEnvironment(), hostHome = NodeOs.homedir()) => {
505
+ const tmp = confinement.mechanism._tag === "seatbelt" ? confinement.tmp : "/tmp";
506
+ const home = NodePath.posix.join(toPosix(tmp), "home");
507
+ return {
508
+ COREPACK_HOME: corepackHome(hostEnv, hostHome),
509
+ HOME: home,
510
+ TMPDIR: tmp,
511
+ TMP: tmp,
512
+ TEMP: tmp,
513
+ XDG_CACHE_HOME: NodePath.posix.join(toPosix(tmp), "cache"),
514
+ XDG_CONFIG_HOME: NodePath.posix.join(toPosix(tmp), "config"),
515
+ XDG_DATA_HOME: NodePath.posix.join(toPosix(tmp), "data"),
516
+ XDG_STATE_HOME: NodePath.posix.join(toPosix(tmp), "state")
517
+ };
518
+ };
519
+ /** The fixed runtime surface, never the host root, home, PATH, or arbitrary env paths. */
520
+ const runtimeReads = (hostFacts) => {
521
+ const paths = [
522
+ "/usr",
523
+ "/bin",
524
+ "/sbin",
525
+ "/lib",
526
+ "/lib64",
527
+ "/nix/store",
528
+ "/opt/homebrew",
529
+ // A Smithers machine's toolchain layer: the repository's Node, pnpm, Go,
530
+ // Rust and Python, which the machine's environment puts first on PATH
531
+ // (packages/backend/microsandbox layers.go toolchainRoot). Never all of
532
+ // /opt/smithers, which holds the machine's environment and helpers.
533
+ "/opt/smithers/toolchain",
534
+ "/opt/smithers/rust",
535
+ "/System/Library",
536
+ "/System/Volumes/Preboot/Cryptexes/OS",
537
+ "/private/var/db/dyld",
538
+ // macOS shell and developer-tool dispatch read these system selectors.
539
+ "/var/select/sh",
540
+ "/private/var/select/sh",
541
+ "/var/select/developer_dir",
542
+ "/private/var/select/developer_dir",
543
+ "/Library/Apple",
544
+ "/Library/Developer",
545
+ "/Applications/Xcode.app/Contents/Developer",
546
+ "/Applications/Xcode.app/Contents/Info.plist",
547
+ "/Applications/Xcode.app/Contents/Frameworks",
548
+ "/Applications/Xcode.app/Contents/SharedFrameworks",
549
+ "/Applications/Xcode.app/Contents/PlugIns",
550
+ "/etc/ld.so.cache",
551
+ "/etc/ld.so.conf",
552
+ "/etc/ld.so.conf.d",
553
+ "/etc/localtime",
554
+ "/etc/ssl",
555
+ "/etc/hosts",
556
+ "/etc/resolv.conf",
557
+ "/etc/nsswitch.conf"
558
+ ];
559
+ const developerDirectory = hostFacts.developerDirectory;
560
+ if (hostFacts.platform === "darwin" && developerDirectory !== undefined &&
561
+ /^\/Applications\/Xcode[^/]*\.app\/Contents\/Developer$/.test(developerDirectory)) {
562
+ paths.push(developerDirectory);
563
+ }
564
+ if (hostFacts.home !== undefined)
565
+ paths.push(NodePath.join(hostFacts.home, ".cache/node/corepack"));
566
+ for (const name of ["node", "bun"]) {
567
+ const executable = hostFacts.executable(name);
568
+ if (executable !== undefined) {
569
+ paths.push(executable);
570
+ const real = hostFacts.realpath?.(executable) ?? executable;
571
+ paths.push(real);
572
+ // Node distributions keep npm/corepack here; never grant the install
573
+ // prefix itself, which can be the real home for a custom installation.
574
+ paths.push(NodePath.resolve(NodePath.dirname(real), "../lib/node_modules"));
575
+ }
576
+ }
577
+ const present = paths.filter((path) => hostFacts.exists(path));
578
+ return collapse(present.flatMap((path) => [path, hostFacts.realpath?.(path) ?? path]));
579
+ };
580
+ /** Credentials remain closed even when a workspace or runtime grant contains the home. */
581
+ const credentialPaths = (hostFacts) => {
582
+ const home = hostFacts.home;
583
+ if (home === undefined)
584
+ return [];
585
+ return collapse([
586
+ ".aws",
587
+ ".ssh",
588
+ ".gnupg",
589
+ ".azure",
590
+ ".kube",
591
+ ".docker",
592
+ ".config/gcloud",
593
+ ".config/gh",
594
+ ".npmrc",
595
+ ".netrc",
596
+ ".git-credentials"
597
+ ].flatMap((relative) => {
598
+ const path = NodePath.join(home, relative);
599
+ return [path, hostFacts.realpath?.(path) ?? path];
600
+ }));
601
+ };
602
+ /**
603
+ * The program a confined run execs, made reachable inside its empty root.
604
+ *
605
+ * @internal
606
+ *
607
+ * Bubblewrap resolves a bare program name with `execvp` against `PATH` inside
608
+ * the new root, and that root holds only the runtime paths and the declared
609
+ * sets. A package manager installed outside them — pnpm under the
610
+ * `pnpm/action-setup` prefix on a CI runner, a `.bin` link into a
611
+ * `node_modules` tree — is on the host `PATH` and absent from the sandbox, so
612
+ * the run died with `bwrap: execvp pnpm` before the tool started (#3140).
613
+ *
614
+ * A bare name is resolved on the tool's `PATH` (the host's when none is
615
+ * given). A program already inside a granted path is left exactly as spelled,
616
+ * so a multi-call binary keeps the name it dispatches on, and so is one inside
617
+ * the workspace, whose reads only the declaration may open. Otherwise the run
618
+ * execs the program's real file, and the nearest package directory holding it
619
+ * (its `package.json`, at most three levels up, never the home or the root)
620
+ * is bound read-only, because a script like `pnpm.cjs` loads its siblings.
621
+ * With no package directory only the real file is bound. A name that resolves
622
+ * nowhere is left for `execvp` to report.
623
+ */
624
+ export const launcher = (program, searchPath, granted, workspaceRoot, hostFacts) => {
625
+ const unchanged = { program, reads: [] };
626
+ if (program.includes("/") && !NodePath.isAbsolute(program))
627
+ return unchanged;
628
+ const found = NodePath.isAbsolute(program)
629
+ ? hostFacts.executable(program)
630
+ : searchPath === undefined
631
+ ? hostFacts.executable(program)
632
+ : searchPath.split(":").filter((entry) => NodePath.isAbsolute(entry))
633
+ .map((entry) => hostFacts.executable(NodePath.join(entry, program)))
634
+ .find((candidate) => candidate !== undefined);
635
+ if (found === undefined || granted.some((parent) => insideRoot(parent, found)))
636
+ return unchanged;
637
+ const real = hostFacts.realpath?.(found) ?? found;
638
+ if (insideRoot(workspaceRoot, found) || insideRoot(workspaceRoot, real))
639
+ return unchanged;
640
+ if (granted.some((parent) => insideRoot(parent, real)))
641
+ return { program: real, reads: [] };
642
+ let directory = NodePath.dirname(real);
643
+ for (let level = 0; level < 3; level++) {
644
+ if (directory === NodePath.dirname(directory) || directory === hostFacts.home)
645
+ break;
646
+ if (hostFacts.exists(NodePath.join(directory, "package.json")))
647
+ return { program: real, reads: [directory] };
648
+ directory = NodePath.dirname(directory);
649
+ }
650
+ return { program: real, reads: [real] };
651
+ };
652
+ /**
653
+ * The bubblewrap argv for a plan.
654
+ *
655
+ * Order matters: bubblewrap applies operations in argument order, so the
656
+ * empty root goes first, runtime paths are bound, the workspace tmpfs shadows
657
+ * any broad runtime grant, declared paths are bound on top, and re-closed
658
+ * subtrees are bound read-only over their writable parents. The tmpfs is
659
+ * remounted read-only last so an undeclared write
660
+ * under the workspace fails with `EROFS` instead of vanishing into the tmpfs.
661
+ *
662
+ * @category rendering
663
+ * @since 0.1.0
664
+ */
665
+ export const bubblewrap = (confinement, argv, hostFacts = host(), searchPath) => {
666
+ if (confinement.mechanism._tag !== "bubblewrap")
667
+ throw new Error("bubblewrap argv needs a bubblewrap plan");
668
+ validateWrites(confinement, hostFacts);
669
+ const home = "/tmp/home";
670
+ const out = [
671
+ confinement.mechanism.executable,
672
+ "--tmpfs",
673
+ "/"
674
+ ];
675
+ const runtime = runtimeReads(hostFacts);
676
+ for (const read of runtime)
677
+ out.push("--ro-bind", read, read);
678
+ out.push("--dev", "/dev", "--proc", "/proc", "--tmpfs", "/tmp", "--dir", home, "--dir", "/tmp/cache", "--tmpfs", confinement.workspaceRoot, "--dir", confinement.cwd);
679
+ // The target of a link that leaves the workspace is bound first, so the
680
+ // link a declared read then binds resolves to the same bytes the host sees
681
+ // even when `/tmp` above the target has just become a private tmpfs.
682
+ for (const real of confinement.externalReads)
683
+ out.push("--ro-bind", real, real);
684
+ const [program, ...args] = argv;
685
+ const launch = program === undefined ? undefined : launcher(program, searchPath, [...runtime, ...confinement.reads, ...confinement.writes, ...confinement.externalReads], confinement.workspaceRoot, hostFacts);
686
+ for (const read of launch?.reads ?? [])
687
+ out.push("--ro-bind", read, read);
688
+ for (const read of confinement.reads)
689
+ out.push("--ro-bind", read, read);
690
+ for (const write of confinement.writes)
691
+ out.push("--bind", write, write);
692
+ for (const closed of confinement.readOnly) {
693
+ if (confinement.writes.some((write) => closed === write || closed.startsWith(write + NodePath.sep))) {
694
+ out.push("--ro-bind-try", closed, closed);
695
+ }
696
+ }
697
+ const granted = [
698
+ ...runtime,
699
+ ...(launch?.reads ?? []),
700
+ ...confinement.reads,
701
+ ...confinement.writes,
702
+ ...confinement.externalReads
703
+ ];
704
+ for (const path of credentialPaths(hostFacts)) {
705
+ if (!hostFacts.exists(path) || !granted.some((parent) => insideRoot(parent, path)))
706
+ continue;
707
+ // An explicit external read can name a credential or a file inside it.
708
+ // Mask first, then restore only those explicit paths below the mask.
709
+ if (confinement.externalReads.some((parent) => insideRoot(parent, path)))
710
+ continue;
711
+ const directory = hostFacts.isDirectory(path);
712
+ if (directory)
713
+ out.push("--tmpfs", path);
714
+ else
715
+ out.push("--ro-bind", "/dev/null", path);
716
+ for (const read of confinement.externalReads) {
717
+ if (insideRoot(path, read))
718
+ out.push("--ro-bind", read, read);
719
+ }
720
+ if (directory)
721
+ out.push("--remount-ro", path);
722
+ }
723
+ // `--remount-ro` acts on the mount at that exact path. When the root itself
724
+ // is a declared write (a declared output file at the top level opens its
725
+ // parent), that mount is the writable bind, and remounting it would deny
726
+ // every write the declaration admitted; the tmpfs it replaced needs no
727
+ // re-closing.
728
+ if (!confinement.writes.includes(confinement.workspaceRoot))
729
+ out.push("--remount-ro", confinement.workspaceRoot);
730
+ out.push("--remount-ro", "/");
731
+ out.push("--chdir", confinement.cwd, "--unshare-all", "--new-session", "--die-with-parent");
732
+ // Bubblewrap cannot expose only the host loopback interface. Planning
733
+ // refuses that posture on Linux, so only an explicit full-network opening
734
+ // reaches `--share-net` here.
735
+ if (confinement.network === "loopback") {
736
+ throw new Error("bubblewrap cannot render loopback-only networking");
737
+ }
738
+ if (confinement.network === "open")
739
+ out.push("--share-net");
740
+ out.push("--", ...(launch === undefined ? argv : [launch.program, ...args]));
741
+ return out;
742
+ };
743
+ /** Escapes one string for a seatbelt profile literal. */
744
+ const sbpl = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
745
+ /** Every directory from the workspace root down to the parent of each path. */
746
+ const ancestors = (root, paths) => {
747
+ const found = new Set([root]);
748
+ for (const path of paths) {
749
+ let current = NodePath.dirname(path);
750
+ while (current.length >= root.length && current !== NodePath.dirname(current)) {
751
+ found.add(current);
752
+ if (current === root)
753
+ break;
754
+ current = NodePath.dirname(current);
755
+ }
756
+ }
757
+ return [...found].sort();
758
+ };
759
+ /**
760
+ * The seatbelt profile for a plan.
761
+ *
762
+ * File reads start closed, then enumerated runtime paths and declared sets
763
+ * are opened. The workspace is re-closed before its grants so a workspace
764
+ * under a runtime root does not inherit the broad runtime grant. Credentials
765
+ * are re-closed before explicit external reads, which can opt into them.
766
+ *
767
+ * @category rendering
768
+ * @since 0.1.0
769
+ */
770
+ export const seatbelt = (confinement, hostFacts = host()) => {
771
+ if (confinement.mechanism._tag !== "seatbelt")
772
+ throw new Error("a seatbelt profile needs a seatbelt plan");
773
+ validateWrites(confinement, hostFacts);
774
+ const lines = ["(version 1)", confinement.strict === true ? "(deny default)" : "(allow default)"];
775
+ if (confinement.strict === true) {
776
+ // No Mach lookup, Apple Events or LaunchServices grants: work must stay
777
+ // in descendants inheriting this sandbox, never delegated to a host daemon.
778
+ lines.push("(allow process-exec)", "(allow process-fork)", "(allow signal (target same-sandbox))", "(allow process-info* (target same-sandbox))", "(allow sysctl-read (sysctl-name-prefix \"hw.\") (sysctl-name-prefix \"machdep.cpu.\") (sysctl-name \"kern.argmax\") (sysctl-name \"kern.hostname\") (sysctl-name \"kern.maxfilesperproc\") (sysctl-name \"kern.maxproc\") (sysctl-name \"kern.osproductversion\") (sysctl-name \"kern.osrelease\") (sysctl-name \"kern.ostype\") (sysctl-name \"kern.osversion\") (sysctl-name \"kern.secure_kernel\") (sysctl-name \"kern.usrstack64\") (sysctl-name \"kern.version\") (sysctl-name \"sysctl.proc_cputype\") (sysctl-name \"vm.loadavg\"))", "(allow mach-host*)");
779
+ if (confinement.network === "open")
780
+ lines.push("(allow network*)");
781
+ }
782
+ if (confinement.network !== "open") {
783
+ lines.push("(deny network*)");
784
+ if (confinement.unixSockets !== false)
785
+ lines.push("(allow network* (local unix-socket))");
786
+ if (confinement.network === "loopback") {
787
+ lines.push("(allow network-bind (local ip \"localhost:*\"))", "(allow network-inbound (local ip \"localhost:*\"))", "(allow network-outbound (remote ip \"localhost:*\"))");
788
+ }
789
+ }
790
+ if (confinement.network === "open" && confinement.unixSockets === false) {
791
+ lines.push("(deny network* (local unix-socket))");
792
+ if (confinement.strict === true) {
793
+ // Darwin resolves DNS through this trusted endpoint. Full IP authority
794
+ // permits DNS only; other host daemons remain closed, including Mach IPC.
795
+ lines.push("(allow network-outbound (literal \"/private/var/run/mDNSResponder\"))");
796
+ }
797
+ }
798
+ const writable = [...confinement.writes, confinement.tmp];
799
+ lines.push("(deny file-write*)", `(allow file-write* ${confinement.strict === true ? "(literal \"/dev/null\")" : "(subpath \"/dev\")"} ${writable.map((path) => `(subpath ${sbpl(path)})`).join(" ")})`);
800
+ if (confinement.readOnly.length > 0) {
801
+ lines.push(`(deny file-write* ${confinement.readOnly.map((path) => `(subpath ${sbpl(path)})`).join(" ")})`);
802
+ }
803
+ lines.push("(deny file-read*)", confinement.strict === true
804
+ ? "(allow file-read* (literal \"/\") (literal \"/dev/null\") (literal \"/dev/zero\") (literal \"/dev/random\") (literal \"/dev/urandom\"))"
805
+ : "(allow file-read* (literal \"/\") (subpath \"/dev\"))",
806
+ // macOS's system aliases need readlink access before a shell can execute
807
+ // an admitted file through them. Literal grants expose the links only;
808
+ // their target directories still cannot be listed or read wholesale.
809
+ "(allow file-read* (literal \"/tmp\") (literal \"/var\") (literal \"/etc\"))");
810
+ const runtime = runtimeReads(hostFacts);
811
+ if (runtime.length > 0)
812
+ lines.push(`(allow file-read* ${runtime.map((path) => `(subpath ${sbpl(path)})`).join(" ")})`);
813
+ const readable = [...confinement.reads, ...confinement.writes, confinement.tmp];
814
+ // Node realpath walks every ancestor, including parents outside the
815
+ // workspace. Grant only their metadata, without listing or reading bytes.
816
+ const traversable = ancestors("/", [...runtime, ...readable, ...confinement.externalReads])
817
+ .filter((path) => !insideRoot(confinement.workspaceRoot, path));
818
+ lines.push(`(allow file-read-metadata ${traversable.map((path) => `(literal ${sbpl(path)})`).join(" ")})`);
819
+ const listable = ancestors(confinement.workspaceRoot, readable.filter((path) => insideRoot(confinement.workspaceRoot, path)));
820
+ lines.push(`(deny file-read* (subpath ${sbpl(confinement.workspaceRoot)}))`);
821
+ if (confinement.strict === true) {
822
+ const metadata = [
823
+ ...new Set([
824
+ ...listable,
825
+ ...ancestors(confinement.workspaceRoot, [confinement.cwd]),
826
+ confinement.cwd
827
+ ])
828
+ ].sort();
829
+ lines.push(`(allow file-read-metadata ${metadata.map((path) => `(literal ${sbpl(path)})`).join(" ")})`);
830
+ }
831
+ else {
832
+ lines.push(`(allow file-read-metadata (subpath ${sbpl(confinement.workspaceRoot)}))`);
833
+ }
834
+ lines.push(`(allow file-read* ${[
835
+ ...(confinement.strict === true ? [] : listable.map((path) => `(literal ${sbpl(path)})`)),
836
+ ...readable.map((path) => `(subpath ${sbpl(path)})`)
837
+ ].join(" ")})`);
838
+ if (confinement.readDenies.length > 0) {
839
+ lines.push(`(deny file-read* ${confinement.readDenies.map((path) => `(subpath ${sbpl(path)})`).join(" ")})`);
840
+ }
841
+ const credentials = credentialPaths(hostFacts);
842
+ if (credentials.length > 0) {
843
+ lines.push(`(deny file-read* ${credentials.map((path) => `(subpath ${sbpl(path)})`).join(" ")})`);
844
+ if (confinement.strict === true) {
845
+ lines.push(`(deny file-write* ${credentials.map((path) => `(subpath ${sbpl(path)})`).join(" ")})`);
846
+ }
847
+ }
848
+ if (confinement.externalReads.length > 0) {
849
+ lines.push(`(allow file-read* ${confinement.externalReads.map((path) => `(subpath ${sbpl(path)})`).join(" ")})`);
850
+ }
851
+ return lines.join("");
852
+ };
853
+ /**
854
+ * One `key=value` field of a `docker run --mount` value. Docker parses the
855
+ * value as one CSV record, so a field containing a comma, a quote, or a line
856
+ * break is wrapped in quotes with inner quotes doubled; any other field keeps
857
+ * its plain spelling.
858
+ */
859
+ const mountField = (key, value) => {
860
+ const field = `${key}=${value}`;
861
+ return /[",\r\n]/.test(field) ? `"${field.replaceAll("\"", "\"\"")}"` : field;
862
+ };
863
+ const bindMount = (path, readonly) => ["type=bind", mountField("src", path), mountField("dst", toPosix(path)), ...(readonly ? ["readonly"] : [])].join(",");
864
+ /**
865
+ * The `docker run` argv for a plan. Paths keep their host spelling inside the
866
+ * container so an argv that names workspace paths works unchanged; the image
867
+ * supplies everything outside the workspace.
868
+ *
869
+ * @category rendering
870
+ * @since 0.1.0
871
+ */
872
+ export const docker = (confinement, argv, env, hostFacts = host(), containerName = `smithers-${randomUUID()}`) => {
873
+ if (confinement.mechanism._tag !== "docker")
874
+ throw new Error("docker argv needs a docker plan");
875
+ validateWrites(confinement, hostFacts);
876
+ const out = [
877
+ confinement.mechanism.executable,
878
+ "run",
879
+ "--rm",
880
+ "--init",
881
+ "--name",
882
+ containerName,
883
+ "--read-only",
884
+ "--tmpfs",
885
+ "/tmp:rw,exec",
886
+ "--network",
887
+ confinement.network === "open" ? "bridge" : "none",
888
+ "--workdir",
889
+ toPosix(confinement.cwd)
890
+ ];
891
+ if (confinement.uid !== undefined && confinement.gid !== undefined) {
892
+ out.push("--user", `${confinement.uid}:${confinement.gid}`);
893
+ }
894
+ for (const real of confinement.externalReads)
895
+ out.push("--mount", bindMount(real, true));
896
+ for (const read of confinement.reads)
897
+ out.push("--mount", bindMount(read, true));
898
+ for (const write of confinement.writes)
899
+ out.push("--mount", bindMount(write, false));
900
+ for (const closed of confinement.readOnly) {
901
+ if (confinement.writes.some((write) => closed === write || closed.startsWith(write + NodePath.sep))) {
902
+ out.push("--mount", bindMount(closed, true));
903
+ }
904
+ }
905
+ for (const [name, value] of Object.entries(env).sort(([left], [right]) => (left < right ? -1 : 1))) {
906
+ if (name === "PATH" || name === "HOME")
907
+ continue;
908
+ out.push("--env", `${name}=${value}`);
909
+ }
910
+ out.push("--env", "HOME=/tmp/home", confinement.mechanism.image, ...argv);
911
+ return out;
912
+ };
913
+ /**
914
+ * Wraps a tool argv in the plan's mechanism and reports the environment the
915
+ * wrapper needs the child to carry. Docker runs also return the unique
916
+ * container name the caller must forcibly remove when the client scope closes.
917
+ *
918
+ * @category rendering
919
+ * @since 0.1.0
920
+ */
921
+ export const wrap = (confinement, argv, env, hostFacts = host()) => {
922
+ const extra = environment(confinement);
923
+ switch (confinement.mechanism._tag) {
924
+ case "bubblewrap":
925
+ return { argv: bubblewrap(confinement, argv, hostFacts, env["PATH"]), env: extra };
926
+ case "seatbelt": {
927
+ const profile = seatbelt(confinement, hostFacts);
928
+ return { argv: [confinement.mechanism.executable, "-p", profile, ...argv], env: extra };
929
+ }
930
+ case "docker": {
931
+ const containerName = `smithers-${randomUUID()}`;
932
+ return { argv: docker(confinement, argv, { ...env, ...extra }, hostFacts, containerName), env: {}, containerName };
933
+ }
934
+ }
935
+ };
936
+ /**
937
+ * Each pattern names the path a tool complained about and whether the text
938
+ * proves the denied operation was a write. A confined process never sees an
939
+ * undeclared path: seatbelt answers EPERM, bubblewrap answers ENOENT because
940
+ * the path was never mounted, and dash spells that "Directory nonexistent".
941
+ */
942
+ const denialPatterns = [
943
+ { pattern: /\b(?:EACCES|EPERM|ENOENT)\b[^'"\n]*['"]([^'"\n]+)['"]/g, write: false },
944
+ { pattern: /\bEROFS\b[^'"\n]*['"]([^'"\n]+)['"]/g, write: true },
945
+ {
946
+ pattern: /(?:^|[\s:])((?:\/|\.{0,2}\/?)[^\s:'"]+): (?:Operation not permitted|Permission denied|No such file or directory|Directory nonexistent|Not a directory)/gm,
947
+ write: false
948
+ },
949
+ { pattern: /(?:^|[\s:])((?:\/|\.{0,2}\/?)[^\s:'"]+): Read-only file system/gm, write: true },
950
+ {
951
+ pattern: /(?:Operation not permitted|Permission denied|No such file or directory|Directory nonexistent)[^\n]*?[:\s]['"]?((?:\/|[A-Za-z]:\\)[^'"\s:]+)/g,
952
+ write: false
953
+ },
954
+ { pattern: /Read-only file system[^\n]*?[:\s]['"]?((?:\/|[A-Za-z]:\\)[^'"\s:]+)/g, write: true },
955
+ {
956
+ pattern: /cannot (?:create|write|touch|mkdir|remove) (?:directory )?['"]?((?:\/|[A-Za-z]:\\|\.{0,2}\/?)[^'":\s]+)/g,
957
+ write: true
958
+ },
959
+ { pattern: /cannot (?:open|access|stat|read) ['"]?((?:\/|[A-Za-z]:\\|\.{0,2}\/?)[^'":\s]+)/g, write: false }
960
+ ];
961
+ /**
962
+ * Names, from a failed tool's own output, the paths it touched outside the
963
+ * declared set. The sandbox denies at the kernel, so the only witness is the
964
+ * tool's error text; this reads it back and says which side of the boundary
965
+ * each path fell on.
966
+ *
967
+ * @category diagnostics
968
+ * @since 0.1.0
969
+ */
970
+ export const diagnose = (confinement, text) => {
971
+ // Path → whether the text proved the denied operation was a write.
972
+ const named = new Map();
973
+ for (const { pattern, write } of denialPatterns) {
974
+ for (const match of text.matchAll(pattern)) {
975
+ const raw = match[1];
976
+ const absolute = NodePath.isAbsolute(raw) ? raw : NodePath.resolve(confinement.cwd, raw);
977
+ if (insideRoot(confinement.workspaceRoot, absolute))
978
+ named.set(absolute, (named.get(absolute) ?? false) || write);
979
+ if (named.size >= 5)
980
+ break;
981
+ }
982
+ if (named.size >= 5)
983
+ break;
984
+ }
985
+ if (named.size === 0)
986
+ return undefined;
987
+ const covered = (path, set) => set.some((entry) => entry === path || path.startsWith(entry + NodePath.sep));
988
+ const lines = [...named.entries()].sort(([left], [right]) => (left < right ? -1 : 1)).map(([path, write]) => {
989
+ const relative = toPosix(NodePath.relative(confinement.workspaceRoot, path));
990
+ // A path that lexically sits in the workspace can still leave it through
991
+ // a symlink in an existing ancestor; the declaration covers the tree, not
992
+ // wherever a link points.
993
+ const real = realized(path);
994
+ const escapes = !insideRoot(confinement.workspaceRoot, real);
995
+ const readable = !escapes && (covered(path, confinement.reads) || covered(path, confinement.writes));
996
+ const writable = !escapes && covered(path, confinement.writes) && !covered(path, confinement.readOnly);
997
+ if (escapes) {
998
+ return `sandbox: ${relative} resolves to ${real}, outside the declared ${write ? "write" : "read"} set`;
999
+ }
1000
+ if (write) {
1001
+ return writable
1002
+ ? `sandbox: ${relative} was denied inside the declared set`
1003
+ : `sandbox: ${relative} is outside the declared write set`;
1004
+ }
1005
+ if (!readable)
1006
+ return `sandbox: ${relative} is outside the declared read set`;
1007
+ if (!writable)
1008
+ return `sandbox: ${relative} is outside the declared write set`;
1009
+ return `sandbox: ${relative} was denied inside the declared set`;
1010
+ });
1011
+ return `${lines.join("\n")}\nsandbox: ${confinement.mechanism._tag}, network ${confinement.network}, ` +
1012
+ `${confinement.reads.length} read path(s), ${confinement.writes.length} write path(s)`;
1013
+ };
1014
+ //# sourceMappingURL=ProcessSandbox.js.map