@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.
- package/CHANGELOG.md +211 -0
- package/LICENSE +21 -0
- package/README.md +165 -2
- package/bin/darwin-arm64/smithers-jj-export +0 -0
- package/bin/darwin-x64/smithers-jj-export +0 -0
- package/bin/linux-arm64/smithers-jj-export +0 -0
- package/bin/linux-x64/smithers-jj-export +4 -0
- package/bin/win32-x64/smithers-jj-export.exe +0 -0
- package/dist/cjs/AtomicFileSystem.d.ts +147 -0
- package/dist/cjs/AtomicFileSystem.d.ts.map +1 -0
- package/dist/cjs/AtomicFileSystem.js +234 -0
- package/dist/cjs/AtomicFileSystem.js.map +7 -0
- package/dist/cjs/EgressHttpClient.d.ts +106 -0
- package/dist/cjs/EgressHttpClient.d.ts.map +1 -0
- package/dist/cjs/EgressHttpClient.js +202 -0
- package/dist/cjs/EgressHttpClient.js.map +7 -0
- package/dist/cjs/HostLiveness.d.ts +81 -0
- package/dist/cjs/HostLiveness.d.ts.map +1 -0
- package/dist/cjs/HostLiveness.js +49 -0
- package/dist/cjs/HostLiveness.js.map +7 -0
- package/dist/cjs/NodeHost.d.ts +145 -0
- package/dist/cjs/NodeHost.d.ts.map +1 -0
- package/dist/cjs/NodeHost.js +128 -0
- package/dist/cjs/NodeHost.js.map +7 -0
- package/dist/cjs/ProcessConfinement.d.ts +61 -0
- package/dist/cjs/ProcessConfinement.d.ts.map +1 -0
- package/dist/cjs/ProcessConfinement.js +292 -0
- package/dist/cjs/ProcessConfinement.js.map +7 -0
- package/dist/cjs/ProcessReaper.d.ts +457 -0
- package/dist/cjs/ProcessReaper.d.ts.map +1 -0
- package/dist/cjs/ProcessReaper.js +349 -0
- package/dist/cjs/ProcessReaper.js.map +7 -0
- package/dist/cjs/ProcessSandbox.d.ts +391 -0
- package/dist/cjs/ProcessSandbox.d.ts.map +1 -0
- package/dist/cjs/ProcessSandbox.js +774 -0
- package/dist/cjs/ProcessSandbox.js.map +7 -0
- package/dist/cjs/ScopedProcess.d.ts +66 -0
- package/dist/cjs/ScopedProcess.d.ts.map +1 -0
- package/dist/cjs/ScopedProcess.js +87 -0
- package/dist/cjs/ScopedProcess.js.map +7 -0
- package/dist/cjs/index.d.ts +38 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +47 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/AtomicFileSystemExecutable.d.ts +61 -0
- package/dist/cjs/internal/AtomicFileSystemExecutable.d.ts.map +1 -0
- package/dist/cjs/internal/AtomicFileSystemExecutable.js +250 -0
- package/dist/cjs/internal/AtomicFileSystemExecutable.js.map +7 -0
- package/dist/cjs/internal/AtomicFileSystemProtocol.d.ts +78 -0
- package/dist/cjs/internal/AtomicFileSystemProtocol.d.ts.map +1 -0
- package/dist/cjs/internal/AtomicFileSystemProtocol.js +382 -0
- package/dist/cjs/internal/AtomicFileSystemProtocol.js.map +7 -0
- package/dist/cjs/internal/AtomicFileSystemTransport.d.ts +44 -0
- package/dist/cjs/internal/AtomicFileSystemTransport.d.ts.map +1 -0
- package/dist/cjs/internal/AtomicFileSystemTransport.js +197 -0
- package/dist/cjs/internal/AtomicFileSystemTransport.js.map +7 -0
- package/dist/cjs/internal/AtomicHelperError.d.ts +35 -0
- package/dist/cjs/internal/AtomicHelperError.d.ts.map +1 -0
- package/dist/cjs/internal/AtomicHelperError.js +45 -0
- package/dist/cjs/internal/AtomicHelperError.js.map +7 -0
- package/dist/cjs/internal/AtomicHelperFault.d.ts +59 -0
- package/dist/cjs/internal/AtomicHelperFault.d.ts.map +1 -0
- package/dist/cjs/internal/AtomicHelperFault.js +66 -0
- package/dist/cjs/internal/AtomicHelperFault.js.map +7 -0
- package/dist/cjs/internal/PipedProcess.d.ts +35 -0
- package/dist/cjs/internal/PipedProcess.d.ts.map +1 -0
- package/dist/cjs/internal/PipedProcess.js +309 -0
- package/dist/cjs/internal/PipedProcess.js.map +7 -0
- package/dist/cjs/internal/ProcSnapshot.d.ts +85 -0
- package/dist/cjs/internal/ProcSnapshot.d.ts.map +1 -0
- package/dist/cjs/internal/ProcSnapshot.js +123 -0
- package/dist/cjs/internal/ProcSnapshot.js.map +7 -0
- package/dist/cjs/internal/ProcessCleanup.d.ts +64 -0
- package/dist/cjs/internal/ProcessCleanup.d.ts.map +1 -0
- package/dist/cjs/internal/ProcessCleanup.js +62 -0
- package/dist/cjs/internal/ProcessCleanup.js.map +7 -0
- package/dist/cjs/internal/ProcessCleanupPolicyError.d.ts +30 -0
- package/dist/cjs/internal/ProcessCleanupPolicyError.d.ts.map +1 -0
- package/dist/cjs/internal/ProcessCleanupPolicyError.js +33 -0
- package/dist/cjs/internal/ProcessCleanupPolicyError.js.map +7 -0
- package/dist/cjs/internal/ProcessFault.d.ts +63 -0
- package/dist/cjs/internal/ProcessFault.d.ts.map +1 -0
- package/dist/cjs/internal/ProcessFault.js +63 -0
- package/dist/cjs/internal/ProcessFault.js.map +7 -0
- package/dist/cjs/internal/ProcessSupervisor.d.ts +143 -0
- package/dist/cjs/internal/ProcessSupervisor.d.ts.map +1 -0
- package/dist/cjs/internal/ProcessSupervisor.js +685 -0
- package/dist/cjs/internal/ProcessSupervisor.js.map +7 -0
- package/dist/cjs/internal/SupervisorProgram.d.ts +9 -0
- package/dist/cjs/internal/SupervisorProgram.d.ts.map +1 -0
- package/dist/cjs/internal/SupervisorProgram.js +326 -0
- package/dist/cjs/internal/SupervisorProgram.js.map +7 -0
- package/dist/cjs/internal/WindowsProcessJob.d.ts +39 -0
- package/dist/cjs/internal/WindowsProcessJob.d.ts.map +1 -0
- package/dist/cjs/internal/WindowsProcessJob.js +113 -0
- package/dist/cjs/internal/WindowsProcessJob.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/AtomicFileSystem.d.ts +147 -0
- package/dist/esm/AtomicFileSystem.d.ts.map +1 -0
- package/dist/esm/AtomicFileSystem.js +290 -0
- package/dist/esm/AtomicFileSystem.js.map +1 -0
- package/dist/esm/EgressHttpClient.d.ts +106 -0
- package/dist/esm/EgressHttpClient.d.ts.map +1 -0
- package/dist/esm/EgressHttpClient.js +259 -0
- package/dist/esm/EgressHttpClient.js.map +1 -0
- package/dist/esm/HostLiveness.d.ts +81 -0
- package/dist/esm/HostLiveness.d.ts.map +1 -0
- package/dist/esm/HostLiveness.js +68 -0
- package/dist/esm/HostLiveness.js.map +1 -0
- package/dist/esm/NodeHost.d.ts +145 -0
- package/dist/esm/NodeHost.d.ts.map +1 -0
- package/dist/esm/NodeHost.js +162 -0
- package/dist/esm/NodeHost.js.map +1 -0
- package/dist/esm/ProcessConfinement.d.ts +61 -0
- package/dist/esm/ProcessConfinement.d.ts.map +1 -0
- package/dist/esm/ProcessConfinement.js +311 -0
- package/dist/esm/ProcessConfinement.js.map +1 -0
- package/dist/esm/ProcessReaper.d.ts +457 -0
- package/dist/esm/ProcessReaper.d.ts.map +1 -0
- package/dist/esm/ProcessReaper.js +711 -0
- package/dist/esm/ProcessReaper.js.map +1 -0
- package/dist/esm/ProcessSandbox.d.ts +391 -0
- package/dist/esm/ProcessSandbox.d.ts.map +1 -0
- package/dist/esm/ProcessSandbox.js +1043 -0
- package/dist/esm/ProcessSandbox.js.map +1 -0
- package/dist/esm/ScopedProcess.d.ts +66 -0
- package/dist/esm/ScopedProcess.d.ts.map +1 -0
- package/dist/esm/ScopedProcess.js +67 -0
- package/dist/esm/ScopedProcess.js.map +1 -0
- package/dist/esm/index.d.ts +38 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +38 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/AtomicFileSystemExecutable.d.ts +61 -0
- package/dist/esm/internal/AtomicFileSystemExecutable.d.ts.map +1 -0
- package/dist/esm/internal/AtomicFileSystemExecutable.js +350 -0
- package/dist/esm/internal/AtomicFileSystemExecutable.js.map +1 -0
- package/dist/esm/internal/AtomicFileSystemProtocol.d.ts +78 -0
- package/dist/esm/internal/AtomicFileSystemProtocol.d.ts.map +1 -0
- package/dist/esm/internal/AtomicFileSystemProtocol.js +412 -0
- package/dist/esm/internal/AtomicFileSystemProtocol.js.map +1 -0
- package/dist/esm/internal/AtomicFileSystemTransport.d.ts +44 -0
- package/dist/esm/internal/AtomicFileSystemTransport.d.ts.map +1 -0
- package/dist/esm/internal/AtomicFileSystemTransport.js +242 -0
- package/dist/esm/internal/AtomicFileSystemTransport.js.map +1 -0
- package/dist/esm/internal/AtomicHelperError.d.ts +35 -0
- package/dist/esm/internal/AtomicHelperError.d.ts.map +1 -0
- package/dist/esm/internal/AtomicHelperError.js +39 -0
- package/dist/esm/internal/AtomicHelperError.js.map +1 -0
- package/dist/esm/internal/AtomicHelperFault.d.ts +59 -0
- package/dist/esm/internal/AtomicHelperFault.d.ts.map +1 -0
- package/dist/esm/internal/AtomicHelperFault.js +66 -0
- package/dist/esm/internal/AtomicHelperFault.js.map +1 -0
- package/dist/esm/internal/PipedProcess.d.ts +35 -0
- package/dist/esm/internal/PipedProcess.d.ts.map +1 -0
- package/dist/esm/internal/PipedProcess.js +321 -0
- package/dist/esm/internal/PipedProcess.js.map +1 -0
- package/dist/esm/internal/ProcSnapshot.d.ts +85 -0
- package/dist/esm/internal/ProcSnapshot.d.ts.map +1 -0
- package/dist/esm/internal/ProcSnapshot.js +188 -0
- package/dist/esm/internal/ProcSnapshot.js.map +1 -0
- package/dist/esm/internal/ProcessCleanup.d.ts +64 -0
- package/dist/esm/internal/ProcessCleanup.d.ts.map +1 -0
- package/dist/esm/internal/ProcessCleanup.js +45 -0
- package/dist/esm/internal/ProcessCleanup.js.map +1 -0
- package/dist/esm/internal/ProcessCleanupPolicyError.d.ts +30 -0
- package/dist/esm/internal/ProcessCleanupPolicyError.d.ts.map +1 -0
- package/dist/esm/internal/ProcessCleanupPolicyError.js +25 -0
- package/dist/esm/internal/ProcessCleanupPolicyError.js.map +1 -0
- package/dist/esm/internal/ProcessFault.d.ts +63 -0
- package/dist/esm/internal/ProcessFault.d.ts.map +1 -0
- package/dist/esm/internal/ProcessFault.js +64 -0
- package/dist/esm/internal/ProcessFault.js.map +1 -0
- package/dist/esm/internal/ProcessSupervisor.d.ts +143 -0
- package/dist/esm/internal/ProcessSupervisor.d.ts.map +1 -0
- package/dist/esm/internal/ProcessSupervisor.js +689 -0
- package/dist/esm/internal/ProcessSupervisor.js.map +1 -0
- package/dist/esm/internal/SupervisorProgram.d.ts +9 -0
- package/dist/esm/internal/SupervisorProgram.d.ts.map +1 -0
- package/dist/esm/internal/SupervisorProgram.js +310 -0
- package/dist/esm/internal/SupervisorProgram.js.map +1 -0
- package/dist/esm/internal/WindowsProcessJob.d.ts +39 -0
- package/dist/esm/internal/WindowsProcessJob.d.ts.map +1 -0
- package/dist/esm/internal/WindowsProcessJob.js +115 -0
- package/dist/esm/internal/WindowsProcessJob.js.map +1 -0
- package/dist/vendor/adapters.json +1757 -0
- package/dist/vendor/chunk-2Y7WC4KU.js +109 -0
- package/dist/vendor/chunk-3GR5VZ2M.js +118 -0
- package/dist/vendor/chunk-42QKY6T6.js +40 -0
- package/dist/vendor/chunk-54RLBMTZ.js +61 -0
- package/dist/vendor/chunk-5ZMHBXIK.js +55 -0
- package/dist/vendor/chunk-6ME2W7TF.js +28 -0
- package/dist/vendor/chunk-AMQKOQNT.js +34 -0
- package/dist/vendor/chunk-B3FZ67QU.js +59 -0
- package/dist/vendor/chunk-BKTBUBQY.js +437 -0
- package/dist/vendor/chunk-DS7GF46S.js +34 -0
- package/dist/vendor/chunk-DVOAHXPG.js +104 -0
- package/dist/vendor/chunk-DWSHTSVX.js +229 -0
- package/dist/vendor/chunk-EGBNVEEW.js +14 -0
- package/dist/vendor/chunk-EZWY45HP.js +18 -0
- package/dist/vendor/chunk-FNZOJHSV.js +71 -0
- package/dist/vendor/chunk-GXCHI7BM.js +131 -0
- package/dist/vendor/chunk-IIJ52ZU6.js +20 -0
- package/dist/vendor/chunk-J7A4CZDO.js +92 -0
- package/dist/vendor/chunk-JG5TTWPT.js +85 -0
- package/dist/vendor/chunk-JJ24ARBQ.js +394 -0
- package/dist/vendor/chunk-KOZMM5PZ.js +426 -0
- package/dist/vendor/chunk-KUUXSID7.js +18 -0
- package/dist/vendor/chunk-LEUPYT3Q.js +36 -0
- package/dist/vendor/chunk-M32LDTW7.js +18 -0
- package/dist/vendor/chunk-M7CLUILA.js +0 -0
- package/dist/vendor/chunk-MBSKBQ5D.js +21 -0
- package/dist/vendor/chunk-MLKGABMK.js +9 -0
- package/dist/vendor/chunk-NBVRTDTV.js +129 -0
- package/dist/vendor/chunk-OXNGN5H7.js +81 -0
- package/dist/vendor/chunk-QPLDT6A6.js +564 -0
- package/dist/vendor/chunk-QYBKAIA6.js +18 -0
- package/dist/vendor/chunk-RO2R4MJH.js +62 -0
- package/dist/vendor/chunk-SIR62W3Y.js +398 -0
- package/dist/vendor/chunk-TFIKFFSC.js +26 -0
- package/dist/vendor/chunk-TXKLEBBL.js +143 -0
- package/dist/vendor/chunk-UFH5UUZK.js +48 -0
- package/dist/vendor/chunk-VANKHF46.js +96 -0
- package/dist/vendor/chunk-VZBDV3TV.js +18 -0
- package/dist/vendor/chunk-XDKNKFBW.js +22 -0
- package/dist/vendor/chunk-XH4U3JSV.js +249 -0
- package/dist/vendor/chunk-Y35ZAO5U.js +53 -0
- package/dist/vendor/chunk-ZJMRMY57.js +0 -0
- package/dist/vendor/cjs/node/NodeChildProcessSpawner.d.ts +7 -0
- package/dist/vendor/cjs/node/NodeChildProcessSpawner.js +1 -0
- package/dist/vendor/cjs/node/NodeClusterHttp.d.ts +63 -0
- package/dist/vendor/cjs/node/NodeClusterHttp.js +1 -0
- package/dist/vendor/cjs/node/NodeClusterSocket.d.ts +79 -0
- package/dist/vendor/cjs/node/NodeClusterSocket.js +1 -0
- package/dist/vendor/cjs/node/NodeCrypto.d.ts +10 -0
- package/dist/vendor/cjs/node/NodeCrypto.js +1 -0
- package/dist/vendor/cjs/node/NodeFileSystem.d.ts +10 -0
- package/dist/vendor/cjs/node/NodeFileSystem.js +1 -0
- package/dist/vendor/cjs/node/NodeHttpClient.d.ts +179 -0
- package/dist/vendor/cjs/node/NodeHttpClient.js +1 -0
- package/dist/vendor/cjs/node/NodeHttpIncomingMessage.d.ts +62 -0
- package/dist/vendor/cjs/node/NodeHttpIncomingMessage.js +1 -0
- package/dist/vendor/cjs/node/NodeHttpPlatform.d.ts +36 -0
- package/dist/vendor/cjs/node/NodeHttpPlatform.js +1 -0
- package/dist/vendor/cjs/node/NodeHttpServer.d.ts +120 -0
- package/dist/vendor/cjs/node/NodeHttpServer.js +1 -0
- package/dist/vendor/cjs/node/NodeHttpServerRequest.d.ts +29 -0
- package/dist/vendor/cjs/node/NodeHttpServerRequest.js +1 -0
- package/dist/vendor/cjs/node/NodeMultipart.d.ts +46 -0
- package/dist/vendor/cjs/node/NodeMultipart.js +1 -0
- package/dist/vendor/cjs/node/NodeMultipartParser.d.ts +97 -0
- package/dist/vendor/cjs/node/NodeMultipartParser.js +1 -0
- package/dist/vendor/cjs/node/NodePath.d.ts +27 -0
- package/dist/vendor/cjs/node/NodePath.js +1 -0
- package/dist/vendor/cjs/node/NodeRedis.d.ts +74 -0
- package/dist/vendor/cjs/node/NodeRedis.js +1 -0
- package/dist/vendor/cjs/node/NodeRuntime.d.ts +85 -0
- package/dist/vendor/cjs/node/NodeRuntime.js +1 -0
- package/dist/vendor/cjs/node/NodeServices.d.ts +34 -0
- package/dist/vendor/cjs/node/NodeServices.js +1 -0
- package/dist/vendor/cjs/node/NodeSink.d.ts +8 -0
- package/dist/vendor/cjs/node/NodeSink.js +1 -0
- package/dist/vendor/cjs/node/NodeSocket.d.ts +38 -0
- package/dist/vendor/cjs/node/NodeSocket.js +1 -0
- package/dist/vendor/cjs/node/NodeSocketServer.d.ts +8 -0
- package/dist/vendor/cjs/node/NodeSocketServer.js +1 -0
- package/dist/vendor/cjs/node/NodeStdio.d.ts +11 -0
- package/dist/vendor/cjs/node/NodeStdio.js +1 -0
- package/dist/vendor/cjs/node/NodeStream.d.ts +8 -0
- package/dist/vendor/cjs/node/NodeStream.js +1 -0
- package/dist/vendor/cjs/node/NodeTerminal.d.ts +21 -0
- package/dist/vendor/cjs/node/NodeTerminal.js +1 -0
- package/dist/vendor/cjs/node/NodeWorker.d.ts +22 -0
- package/dist/vendor/cjs/node/NodeWorker.js +1 -0
- package/dist/vendor/cjs/node/NodeWorkerRunner.d.ts +12 -0
- package/dist/vendor/cjs/node/NodeWorkerRunner.js +1 -0
- package/dist/vendor/cjs/node/Undici.d.ts +28 -0
- package/dist/vendor/cjs/node/Undici.js +1 -0
- package/dist/vendor/cjs/node/index.d.ts +100 -0
- package/dist/vendor/cjs/node/index.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeChildProcessSpawner.d.ts +57 -0
- package/dist/vendor/cjs/node-shared/NodeChildProcessSpawner.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeClusterSocket.d.ts +22 -0
- package/dist/vendor/cjs/node-shared/NodeClusterSocket.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeCrypto.d.ts +27 -0
- package/dist/vendor/cjs/node-shared/NodeCrypto.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeFileSystem.d.ts +11 -0
- package/dist/vendor/cjs/node-shared/NodeFileSystem.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeHttpCompression.d.ts +36 -0
- package/dist/vendor/cjs/node-shared/NodeHttpCompression.js +1 -0
- package/dist/vendor/cjs/node-shared/NodePath.d.ts +27 -0
- package/dist/vendor/cjs/node-shared/NodePath.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeRuntime.d.ts +47 -0
- package/dist/vendor/cjs/node-shared/NodeRuntime.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeSink.d.ts +69 -0
- package/dist/vendor/cjs/node-shared/NodeSink.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeSocket.d.ts +134 -0
- package/dist/vendor/cjs/node-shared/NodeSocket.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeSocketServer.d.ts +80 -0
- package/dist/vendor/cjs/node-shared/NodeSocketServer.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeStdio.d.ts +12 -0
- package/dist/vendor/cjs/node-shared/NodeStdio.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeStream.d.ts +179 -0
- package/dist/vendor/cjs/node-shared/NodeStream.js +1 -0
- package/dist/vendor/cjs/node-shared/NodeTerminal.d.ts +22 -0
- package/dist/vendor/cjs/node-shared/NodeTerminal.js +1 -0
- package/dist/vendor/cjs/node-shared/index.d.ts +56 -0
- package/dist/vendor/cjs/node-shared/index.js +1 -0
- package/dist/vendor/cjs/node-shared/internal/nodeChildProcessSpawner.d.ts +4 -0
- package/dist/vendor/cjs/node-shared/internal/nodeChildProcessSpawner.js +1 -0
- package/dist/vendor/cjs/node-shared/internal/utils.d.ts +2 -0
- package/dist/vendor/cjs/node-shared/internal/utils.js +1 -0
- package/dist/vendor/cjs/package.json +1 -0
- package/dist/vendor/cjs/platform-node-shared.d.ts +56 -0
- package/dist/vendor/cjs/platform-node-shared.js +1 -0
- package/dist/vendor/cjs/platform-node.d.ts +100 -0
- package/dist/vendor/cjs/platform-node.js +1 -0
- package/dist/vendor/node/LICENSE +21 -0
- package/dist/vendor/node/NodeChildProcessSpawner.d.ts +7 -0
- package/dist/vendor/node/NodeChildProcessSpawner.js +14 -0
- package/dist/vendor/node/NodeClusterHttp.d.ts +63 -0
- package/dist/vendor/node/NodeClusterHttp.js +43 -0
- package/dist/vendor/node/NodeClusterSocket.d.ts +79 -0
- package/dist/vendor/node/NodeClusterSocket.js +30 -0
- package/dist/vendor/node/NodeCrypto.d.ts +10 -0
- package/dist/vendor/node/NodeCrypto.js +8 -0
- package/dist/vendor/node/NodeFileSystem.d.ts +10 -0
- package/dist/vendor/node/NodeFileSystem.js +9 -0
- package/dist/vendor/node/NodeHttpClient.d.ts +179 -0
- package/dist/vendor/node/NodeHttpClient.js +46 -0
- package/dist/vendor/node/NodeHttpIncomingMessage.d.ts +62 -0
- package/dist/vendor/node/NodeHttpIncomingMessage.js +10 -0
- package/dist/vendor/node/NodeHttpPlatform.d.ts +36 -0
- package/dist/vendor/node/NodeHttpPlatform.js +16 -0
- package/dist/vendor/node/NodeHttpServer.d.ts +120 -0
- package/dist/vendor/node/NodeHttpServer.js +46 -0
- package/dist/vendor/node/NodeHttpServerRequest.d.ts +29 -0
- package/dist/vendor/node/NodeHttpServerRequest.js +9 -0
- package/dist/vendor/node/NodeMultipart.d.ts +46 -0
- package/dist/vendor/node/NodeMultipart.js +15 -0
- package/dist/vendor/node/NodeMultipartParser.d.ts +97 -0
- package/dist/vendor/node/NodeMultipartParser.js +11 -0
- package/dist/vendor/node/NodePath.d.ts +27 -0
- package/dist/vendor/node/NodePath.js +12 -0
- package/dist/vendor/node/NodeRedis.d.ts +74 -0
- package/dist/vendor/node/NodeRedis.js +11 -0
- package/dist/vendor/node/NodeRuntime.d.ts +85 -0
- package/dist/vendor/node/NodeRuntime.js +8 -0
- package/dist/vendor/node/NodeServices.d.ts +34 -0
- package/dist/vendor/node/NodeServices.js +23 -0
- package/dist/vendor/node/NodeSink.d.ts +8 -0
- package/dist/vendor/node/NodeSink.js +12 -0
- package/dist/vendor/node/NodeSocket.d.ts +38 -0
- package/dist/vendor/node/NodeSocket.js +31 -0
- package/dist/vendor/node/NodeSocketServer.d.ts +8 -0
- package/dist/vendor/node/NodeSocketServer.js +21 -0
- package/dist/vendor/node/NodeStdio.d.ts +11 -0
- package/dist/vendor/node/NodeStdio.js +10 -0
- package/dist/vendor/node/NodeStream.d.ts +8 -0
- package/dist/vendor/node/NodeStream.js +27 -0
- package/dist/vendor/node/NodeTerminal.d.ts +21 -0
- package/dist/vendor/node/NodeTerminal.js +10 -0
- package/dist/vendor/node/NodeWorker.d.ts +22 -0
- package/dist/vendor/node/NodeWorker.js +9 -0
- package/dist/vendor/node/NodeWorkerRunner.d.ts +12 -0
- package/dist/vendor/node/NodeWorkerRunner.js +7 -0
- package/dist/vendor/node/Undici.d.ts +28 -0
- package/dist/vendor/node/Undici.js +9 -0
- package/dist/vendor/node/index.d.ts +100 -0
- package/dist/vendor/node/index.js +115 -0
- package/dist/vendor/node-shared/LICENSE +21 -0
- package/dist/vendor/node-shared/NodeChildProcessSpawner.d.ts +57 -0
- package/dist/vendor/node-shared/NodeChildProcessSpawner.js +13 -0
- package/dist/vendor/node-shared/NodeClusterSocket.d.ts +22 -0
- package/dist/vendor/node-shared/NodeClusterSocket.js +11 -0
- package/dist/vendor/node-shared/NodeCrypto.d.ts +27 -0
- package/dist/vendor/node-shared/NodeCrypto.js +9 -0
- package/dist/vendor/node-shared/NodeFileSystem.d.ts +11 -0
- package/dist/vendor/node-shared/NodeFileSystem.js +8 -0
- package/dist/vendor/node-shared/NodeHttpCompression.d.ts +36 -0
- package/dist/vendor/node-shared/NodeHttpCompression.js +13 -0
- package/dist/vendor/node-shared/NodePath.d.ts +27 -0
- package/dist/vendor/node-shared/NodePath.js +11 -0
- package/dist/vendor/node-shared/NodeRuntime.d.ts +47 -0
- package/dist/vendor/node-shared/NodeRuntime.js +7 -0
- package/dist/vendor/node-shared/NodeSink.d.ts +69 -0
- package/dist/vendor/node-shared/NodeSink.js +11 -0
- package/dist/vendor/node-shared/NodeSocket.d.ts +134 -0
- package/dist/vendor/node-shared/NodeSocket.js +23 -0
- package/dist/vendor/node-shared/NodeSocketServer.d.ts +80 -0
- package/dist/vendor/node-shared/NodeSocketServer.js +20 -0
- package/dist/vendor/node-shared/NodeStdio.d.ts +12 -0
- package/dist/vendor/node-shared/NodeStdio.js +9 -0
- package/dist/vendor/node-shared/NodeStream.d.ts +179 -0
- package/dist/vendor/node-shared/NodeStream.js +26 -0
- package/dist/vendor/node-shared/NodeTerminal.d.ts +22 -0
- package/dist/vendor/node-shared/NodeTerminal.js +9 -0
- package/dist/vendor/node-shared/index.d.ts +56 -0
- package/dist/vendor/node-shared/index.js +58 -0
- package/dist/vendor/node-shared/internal/nodeChildProcessSpawner.d.ts +4 -0
- package/dist/vendor/node-shared/internal/nodeChildProcessSpawner.js +7 -0
- package/dist/vendor/node-shared/internal/utils.d.ts +2 -0
- package/dist/vendor/node-shared/internal/utils.js +7 -0
- package/dist/vendor/platform-node-shared.d.ts +56 -0
- package/dist/vendor/platform-node-shared.js +58 -0
- package/dist/vendor/platform-node.d.ts +100 -0
- package/dist/vendor/platform-node.js +115 -0
- package/docs/README.md +129 -0
- package/docs/api.md +553 -0
- package/docs/concepts/descriptor-relative-filesystem.md +181 -0
- package/docs/concepts/host-bundle.md +125 -0
- package/docs/concepts/process-containment.md +190 -0
- package/docs/guides/answer-run-ownership.md +87 -0
- package/docs/guides/configure-the-filesystem-helper.md +152 -0
- package/docs/guides/contain-child-processes.md +139 -0
- package/docs/guides/match-files-with-glob.md +151 -0
- package/docs/installation.md +204 -0
- package/docs/quickstart.md +147 -0
- package/docs/troubleshooting.md +130 -0
- package/package.json +202 -3
- package/src/AtomicFileSystem.ts +415 -0
- package/src/EgressHttpClient.ts +327 -0
- package/src/HostLiveness.ts +103 -0
- package/src/NodeHost.ts +253 -0
- package/src/ProcessConfinement.ts +363 -0
- package/src/ProcessReaper.ts +951 -0
- package/src/ProcessSandbox.ts +1307 -0
- package/src/ScopedProcess.ts +124 -0
- package/src/index.ts +44 -0
- package/src/internal/AtomicFileSystemExecutable.ts +379 -0
- package/src/internal/AtomicFileSystemProtocol.ts +502 -0
- package/src/internal/AtomicFileSystemTransport.ts +272 -0
- package/src/internal/AtomicHelperError.ts +50 -0
- package/src/internal/AtomicHelperFault.ts +84 -0
- package/src/internal/PipedProcess.ts +357 -0
- package/src/internal/ProcSnapshot.ts +193 -0
- package/src/internal/ProcessCleanup.ts +99 -0
- package/src/internal/ProcessCleanupPolicyError.ts +30 -0
- package/src/internal/ProcessFault.ts +82 -0
- package/src/internal/ProcessSupervisor.ts +807 -0
- package/src/internal/SupervisorProgram.ts +309 -0
- 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
|