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

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