@secure-exec/core 0.2.1 → 0.3.0-rc.2

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 (248) hide show
  1. package/README.md +5 -5
  2. package/dist/binary.d.ts +4 -0
  3. package/dist/binary.js +25 -0
  4. package/dist/bytes.d.ts +2 -0
  5. package/dist/bytes.js +6 -0
  6. package/dist/callbacks.d.ts +41 -0
  7. package/dist/callbacks.js +94 -0
  8. package/dist/cargo.d.ts +2 -0
  9. package/dist/cargo.js +142 -0
  10. package/dist/correlation.d.ts +10 -0
  11. package/dist/correlation.js +49 -0
  12. package/dist/descriptors.d.ts +34 -0
  13. package/dist/descriptors.js +37 -0
  14. package/dist/event-buffer.d.ts +90 -0
  15. package/dist/event-buffer.js +313 -0
  16. package/dist/ext.d.ts +7 -0
  17. package/dist/ext.js +13 -0
  18. package/dist/filesystem.d.ts +41 -0
  19. package/dist/filesystem.js +70 -0
  20. package/dist/frame-payload-codec.d.ts +8 -0
  21. package/dist/frame-payload-codec.js +14 -0
  22. package/dist/frame-rpc.d.ts +38 -0
  23. package/dist/frame-rpc.js +73 -0
  24. package/dist/frame-stream.d.ts +27 -0
  25. package/dist/frame-stream.js +99 -0
  26. package/dist/framing.d.ts +7 -0
  27. package/dist/framing.js +22 -0
  28. package/dist/generated/AcpLimitsConfig.d.ts +4 -0
  29. package/dist/generated/AcpLimitsConfig.js +2 -0
  30. package/dist/generated/CreateVmConfig.d.ts +19 -0
  31. package/dist/generated/FsPermissionRule.d.ts +6 -0
  32. package/dist/generated/FsPermissionRuleSet.d.ts +6 -0
  33. package/dist/generated/FsPermissionRuleSet.js +1 -0
  34. package/dist/generated/FsPermissionScope.d.ts +3 -0
  35. package/dist/generated/FsPermissionScope.js +1 -0
  36. package/dist/generated/HttpLimitsConfig.d.ts +3 -0
  37. package/dist/generated/HttpLimitsConfig.js +2 -0
  38. package/dist/generated/JsModuleResolution.d.ts +1 -0
  39. package/dist/generated/JsModuleResolution.js +2 -0
  40. package/dist/generated/JsRuntimeConfig.d.ts +26 -0
  41. package/dist/generated/JsRuntimeConfig.js +1 -0
  42. package/dist/generated/JsRuntimeLimitsConfig.d.ts +7 -0
  43. package/dist/generated/JsRuntimeLimitsConfig.js +2 -0
  44. package/dist/generated/JsRuntimePlatform.d.ts +1 -0
  45. package/dist/generated/JsRuntimePlatform.js +2 -0
  46. package/dist/generated/MountPluginDescriptor.d.ts +4 -0
  47. package/dist/generated/MountPluginDescriptor.js +2 -0
  48. package/dist/generated/NativeRootFilesystemConfig.d.ts +5 -0
  49. package/dist/generated/NativeRootFilesystemConfig.js +1 -0
  50. package/dist/generated/PatternPermissionRule.d.ts +6 -0
  51. package/dist/generated/PatternPermissionRule.js +1 -0
  52. package/dist/generated/PatternPermissionRuleSet.d.ts +6 -0
  53. package/dist/generated/PatternPermissionRuleSet.js +1 -0
  54. package/dist/generated/PatternPermissionScope.d.ts +3 -0
  55. package/dist/generated/PatternPermissionScope.js +1 -0
  56. package/dist/generated/PermissionMode.d.ts +1 -0
  57. package/dist/generated/PermissionMode.js +2 -0
  58. package/dist/generated/PermissionsPolicy.d.ts +10 -0
  59. package/dist/generated/PermissionsPolicy.js +1 -0
  60. package/dist/generated/PluginLimitsConfig.d.ts +4 -0
  61. package/dist/generated/PluginLimitsConfig.js +2 -0
  62. package/dist/generated/PythonLimitsConfig.d.ts +5 -0
  63. package/dist/generated/PythonLimitsConfig.js +2 -0
  64. package/dist/generated/ResourceLimitsConfig.d.ts +22 -0
  65. package/dist/generated/ResourceLimitsConfig.js +2 -0
  66. package/dist/generated/RootFilesystemConfig.d.ts +9 -0
  67. package/dist/generated/RootFilesystemConfig.js +1 -0
  68. package/dist/generated/RootFilesystemEntry.d.ts +13 -0
  69. package/dist/generated/RootFilesystemEntry.js +1 -0
  70. package/dist/generated/RootFilesystemEntryEncoding.d.ts +1 -0
  71. package/dist/generated/RootFilesystemEntryEncoding.js +2 -0
  72. package/dist/generated/RootFilesystemEntryKind.d.ts +1 -0
  73. package/dist/generated/RootFilesystemEntryKind.js +2 -0
  74. package/dist/generated/RootFilesystemLowerDescriptor.d.ts +7 -0
  75. package/dist/generated/RootFilesystemLowerDescriptor.js +1 -0
  76. package/dist/generated/RootFilesystemMode.d.ts +1 -0
  77. package/dist/generated/RootFilesystemMode.js +2 -0
  78. package/dist/generated/ToolLimitsConfig.d.ts +10 -0
  79. package/dist/generated/ToolLimitsConfig.js +2 -0
  80. package/dist/generated/VmDnsConfig.d.ts +6 -0
  81. package/dist/generated/VmDnsConfig.js +2 -0
  82. package/dist/generated/VmLimitsConfig.d.ts +18 -0
  83. package/dist/generated/VmLimitsConfig.js +1 -0
  84. package/dist/generated/VmListenPolicyConfig.d.ts +5 -0
  85. package/dist/generated/VmListenPolicyConfig.js +2 -0
  86. package/dist/generated/WasmLimitsConfig.d.ts +5 -0
  87. package/dist/generated/WasmLimitsConfig.js +2 -0
  88. package/dist/generated-protocol.d.ts +1037 -0
  89. package/dist/generated-protocol.js +2887 -0
  90. package/dist/index.d.ts +24 -62
  91. package/dist/index.js +24 -53
  92. package/dist/json.d.ts +2 -0
  93. package/dist/json.js +20 -0
  94. package/dist/kernel-proxy.d.ts +149 -0
  95. package/dist/kernel-proxy.js +1733 -0
  96. package/dist/native-client.d.ts +41 -0
  97. package/dist/native-client.js +124 -0
  98. package/dist/node-runtime.d.ts +490 -0
  99. package/dist/node-runtime.js +585 -0
  100. package/dist/numbers.d.ts +1 -0
  101. package/dist/numbers.js +8 -0
  102. package/dist/ownership.d.ts +18 -0
  103. package/dist/ownership.js +77 -0
  104. package/dist/permissions.d.ts +29 -0
  105. package/dist/permissions.js +68 -0
  106. package/dist/process.d.ts +35 -0
  107. package/dist/process.js +125 -0
  108. package/dist/protocol-client.d.ts +46 -0
  109. package/dist/protocol-client.js +180 -0
  110. package/dist/protocol-frames.d.ts +68 -0
  111. package/dist/protocol-frames.js +139 -0
  112. package/dist/protocol-maps.d.ts +28 -0
  113. package/dist/protocol-maps.js +217 -0
  114. package/dist/protocol-schema.d.ts +10 -0
  115. package/dist/protocol-schema.js +11 -0
  116. package/dist/request-payloads.d.ts +137 -0
  117. package/dist/request-payloads.js +210 -0
  118. package/dist/response-payloads.d.ts +107 -0
  119. package/dist/response-payloads.js +161 -0
  120. package/dist/sidecar-client.d.ts +242 -0
  121. package/dist/sidecar-client.js +797 -0
  122. package/dist/state.d.ts +40 -0
  123. package/dist/state.js +44 -0
  124. package/dist/test-runtime.d.ts +526 -0
  125. package/dist/test-runtime.js +2119 -0
  126. package/dist/vm-config.d.ts +31 -0
  127. package/dist/vm-config.js +1 -0
  128. package/fixtures/alpine-defaults.json +520 -0
  129. package/fixtures/base-filesystem.json +528 -0
  130. package/package.json +193 -115
  131. package/LICENSE +0 -191
  132. package/dist/bridge-setup.d.ts +0 -6
  133. package/dist/bridge-setup.js +0 -9
  134. package/dist/esm-compiler.d.ts +0 -18
  135. package/dist/esm-compiler.js +0 -72
  136. package/dist/fs-helpers.d.ts +0 -23
  137. package/dist/fs-helpers.js +0 -41
  138. package/dist/generated/isolate-runtime.d.ts +0 -19
  139. package/dist/generated/isolate-runtime.js +0 -21
  140. package/dist/generated/polyfills.d.ts +0 -82
  141. package/dist/generated/polyfills.js +0 -82
  142. package/dist/isolate-runtime/apply-custom-global-policy.js +0 -53
  143. package/dist/isolate-runtime/apply-timing-mitigation-freeze.js +0 -130
  144. package/dist/isolate-runtime/apply-timing-mitigation-off.js +0 -14
  145. package/dist/isolate-runtime/bridge-attach.js +0 -29
  146. package/dist/isolate-runtime/bridge-initial-globals.js +0 -385
  147. package/dist/isolate-runtime/eval-script-result.js +0 -8
  148. package/dist/isolate-runtime/global-exposure-helpers.js +0 -36
  149. package/dist/isolate-runtime/init-commonjs-module-globals.js +0 -28
  150. package/dist/isolate-runtime/override-process-cwd.js +0 -8
  151. package/dist/isolate-runtime/override-process-env.js +0 -8
  152. package/dist/isolate-runtime/require-setup.js +0 -4153
  153. package/dist/isolate-runtime/set-commonjs-file-globals.js +0 -36
  154. package/dist/isolate-runtime/set-stdin-data.js +0 -10
  155. package/dist/isolate-runtime/setup-dynamic-import.js +0 -123
  156. package/dist/isolate-runtime/setup-fs-facade.js +0 -87
  157. package/dist/kernel/command-registry.d.ts +0 -44
  158. package/dist/kernel/command-registry.js +0 -114
  159. package/dist/kernel/device-backend.d.ts +0 -14
  160. package/dist/kernel/device-backend.js +0 -251
  161. package/dist/kernel/device-layer.d.ts +0 -12
  162. package/dist/kernel/device-layer.js +0 -271
  163. package/dist/kernel/dns-cache.d.ts +0 -29
  164. package/dist/kernel/dns-cache.js +0 -52
  165. package/dist/kernel/fd-table.d.ts +0 -84
  166. package/dist/kernel/fd-table.js +0 -278
  167. package/dist/kernel/file-lock.d.ts +0 -34
  168. package/dist/kernel/file-lock.js +0 -122
  169. package/dist/kernel/host-adapter.d.ts +0 -50
  170. package/dist/kernel/host-adapter.js +0 -8
  171. package/dist/kernel/index.d.ts +0 -36
  172. package/dist/kernel/index.js +0 -34
  173. package/dist/kernel/kernel.d.ts +0 -9
  174. package/dist/kernel/kernel.js +0 -1415
  175. package/dist/kernel/mount-table.d.ts +0 -75
  176. package/dist/kernel/mount-table.js +0 -353
  177. package/dist/kernel/permissions.d.ts +0 -36
  178. package/dist/kernel/permissions.js +0 -150
  179. package/dist/kernel/pipe-manager.d.ts +0 -64
  180. package/dist/kernel/pipe-manager.js +0 -267
  181. package/dist/kernel/proc-backend.d.ts +0 -30
  182. package/dist/kernel/proc-backend.js +0 -428
  183. package/dist/kernel/proc-layer.d.ts +0 -11
  184. package/dist/kernel/proc-layer.js +0 -507
  185. package/dist/kernel/process-table.d.ts +0 -126
  186. package/dist/kernel/process-table.js +0 -651
  187. package/dist/kernel/pty.d.ts +0 -109
  188. package/dist/kernel/pty.js +0 -552
  189. package/dist/kernel/socket-table.d.ts +0 -312
  190. package/dist/kernel/socket-table.js +0 -1188
  191. package/dist/kernel/timer-table.d.ts +0 -54
  192. package/dist/kernel/timer-table.js +0 -108
  193. package/dist/kernel/types.d.ts +0 -541
  194. package/dist/kernel/types.js +0 -98
  195. package/dist/kernel/user.d.ts +0 -29
  196. package/dist/kernel/user.js +0 -35
  197. package/dist/kernel/vfs.d.ts +0 -82
  198. package/dist/kernel/vfs.js +0 -25
  199. package/dist/kernel/wait.d.ts +0 -45
  200. package/dist/kernel/wait.js +0 -112
  201. package/dist/kernel/wstatus.d.ts +0 -21
  202. package/dist/kernel/wstatus.js +0 -33
  203. package/dist/module-resolver.d.ts +0 -29
  204. package/dist/module-resolver.js +0 -314
  205. package/dist/package-bundler.d.ts +0 -41
  206. package/dist/package-bundler.js +0 -497
  207. package/dist/runtime-driver.d.ts +0 -66
  208. package/dist/shared/api-types.d.ts +0 -83
  209. package/dist/shared/bridge-contract.d.ts +0 -772
  210. package/dist/shared/bridge-contract.js +0 -169
  211. package/dist/shared/console-formatter.d.ts +0 -22
  212. package/dist/shared/console-formatter.js +0 -161
  213. package/dist/shared/constants.d.ts +0 -3
  214. package/dist/shared/constants.js +0 -3
  215. package/dist/shared/errors.d.ts +0 -16
  216. package/dist/shared/errors.js +0 -21
  217. package/dist/shared/esm-utils.d.ts +0 -28
  218. package/dist/shared/esm-utils.js +0 -97
  219. package/dist/shared/global-exposure.d.ts +0 -38
  220. package/dist/shared/global-exposure.js +0 -876
  221. package/dist/shared/in-memory-fs.d.ts +0 -16
  222. package/dist/shared/in-memory-fs.js +0 -115
  223. package/dist/shared/permissions.d.ts +0 -36
  224. package/dist/shared/permissions.js +0 -314
  225. package/dist/shared/require-setup.d.ts +0 -6
  226. package/dist/shared/require-setup.js +0 -9
  227. package/dist/test/block-store-conformance.d.ts +0 -34
  228. package/dist/test/block-store-conformance.js +0 -251
  229. package/dist/test/metadata-store-conformance.d.ts +0 -37
  230. package/dist/test/metadata-store-conformance.js +0 -646
  231. package/dist/test/vfs-conformance.d.ts +0 -65
  232. package/dist/test/vfs-conformance.js +0 -842
  233. package/dist/types.d.ts +0 -98
  234. package/dist/types.js +0 -6
  235. package/dist/vfs/chunked-vfs.d.ts +0 -66
  236. package/dist/vfs/chunked-vfs.js +0 -1290
  237. package/dist/vfs/host-block-store.d.ts +0 -19
  238. package/dist/vfs/host-block-store.js +0 -97
  239. package/dist/vfs/memory-block-store.d.ts +0 -16
  240. package/dist/vfs/memory-block-store.js +0 -45
  241. package/dist/vfs/memory-metadata.d.ts +0 -75
  242. package/dist/vfs/memory-metadata.js +0 -528
  243. package/dist/vfs/sqlite-metadata.d.ts +0 -91
  244. package/dist/vfs/sqlite-metadata.js +0 -582
  245. package/dist/vfs/types.d.ts +0 -210
  246. package/dist/vfs/types.js +0 -8
  247. /package/dist/{runtime-driver.js → generated/CreateVmConfig.js} +0 -0
  248. /package/dist/{shared/api-types.js → generated/FsPermissionRule.js} +0 -0
@@ -0,0 +1,41 @@
1
+ import { StdioSidecarProcess } from "./process.js";
2
+ import type { LiveEventFrame, LiveResponseFrame, LiveSidecarRequestHandler, ProtocolFramePayloadCodec } from "./protocol-frames.js";
3
+ import type { LiveOwnershipScope } from "./ownership.js";
4
+ import type { LiveRequestPayload } from "./request-payloads.js";
5
+ import type { LiveSidecarEventSelector } from "./event-buffer.js";
6
+ export declare const DEFAULT_SIDECAR_FRAME_TIMEOUT_MS = 120000;
7
+ export declare const DEFAULT_SIDECAR_EVENT_BUFFER_CAPACITY = 4096;
8
+ export declare const DEFAULT_SIDECAR_GRACEFUL_EXIT_MS = 5000;
9
+ export declare const DEFAULT_SIDECAR_FORCE_EXIT_MS = 2000;
10
+ export interface StdioSidecarProtocolClientSpawnOptions {
11
+ cwd?: string;
12
+ command?: string;
13
+ args?: string[];
14
+ frameTimeoutMs?: number;
15
+ eventBufferCapacity?: number;
16
+ gracefulExitMs?: number;
17
+ forceExitMs?: number;
18
+ disposedErrorMessage?: string;
19
+ payloadCodec?: ProtocolFramePayloadCodec;
20
+ }
21
+ export declare class StdioSidecarProtocolClient {
22
+ readonly child: StdioSidecarProcess["child"];
23
+ private readonly sidecarProcess;
24
+ private readonly protocolClient;
25
+ private readonly gracefulExitMs;
26
+ private readonly forceExitMs;
27
+ private readonly disposedErrorMessage;
28
+ private constructor();
29
+ static spawn(options?: StdioSidecarProtocolClientSpawnOptions): StdioSidecarProtocolClient;
30
+ setSidecarRequestHandler(handler: LiveSidecarRequestHandler | null): void;
31
+ onEvent(handler: (event: LiveEventFrame) => void): () => void;
32
+ sendRequest(input: {
33
+ ownership: LiveOwnershipScope;
34
+ payload: LiveRequestPayload;
35
+ }): Promise<LiveResponseFrame>;
36
+ waitForEvent(matcher: LiveSidecarEventSelector | ((event: LiveEventFrame) => boolean), timeoutMs?: number, options?: {
37
+ signal?: AbortSignal;
38
+ }): Promise<LiveEventFrame>;
39
+ dispose(): Promise<void>;
40
+ failPermanently(error: Error): void;
41
+ }
@@ -0,0 +1,124 @@
1
+ import { resolvePublishedSidecarBinary } from "./binary.js";
2
+ import { SidecarProcessExited, StdioSidecarProcess, } from "./process.js";
3
+ import { SidecarProtocolClient } from "./protocol-client.js";
4
+ export const DEFAULT_SIDECAR_FRAME_TIMEOUT_MS = 120_000;
5
+ export const DEFAULT_SIDECAR_EVENT_BUFFER_CAPACITY = 4_096;
6
+ export const DEFAULT_SIDECAR_GRACEFUL_EXIT_MS = 5_000;
7
+ export const DEFAULT_SIDECAR_FORCE_EXIT_MS = 2_000;
8
+ export class StdioSidecarProtocolClient {
9
+ child;
10
+ sidecarProcess;
11
+ protocolClient;
12
+ gracefulExitMs;
13
+ forceExitMs;
14
+ disposedErrorMessage;
15
+ constructor(sidecarProcess, options) {
16
+ this.sidecarProcess = sidecarProcess;
17
+ this.child = sidecarProcess.child;
18
+ this.gracefulExitMs = options.gracefulExitMs;
19
+ this.forceExitMs = options.forceExitMs;
20
+ this.disposedErrorMessage = options.disposedErrorMessage;
21
+ this.protocolClient = new SidecarProtocolClient({
22
+ stdin: this.child.stdin,
23
+ stdout: this.child.stdout,
24
+ frameTimeoutMs: options.frameTimeoutMs,
25
+ eventBufferCapacity: options.eventBufferCapacity,
26
+ payloadCodec: options.payloadCodec,
27
+ stderrText: () => this.sidecarProcess.stderrText(),
28
+ streamEndedError: () => this.sidecarProcess.currentExitError() ??
29
+ new SidecarProcessExited({
30
+ exitCode: this.child.exitCode,
31
+ signal: this.child.signalCode,
32
+ stderr: this.sidecarProcess.stderrText(),
33
+ }),
34
+ frameError: (error) => this.sidecarProcess.currentExitError() ?? error,
35
+ });
36
+ this.sidecarProcess.onExit((error) => {
37
+ this.failPermanently(error);
38
+ });
39
+ this.sidecarProcess.onError((error) => {
40
+ this.failPermanently(error);
41
+ });
42
+ }
43
+ static spawn(options = {}) {
44
+ return new StdioSidecarProtocolClient(StdioSidecarProcess.spawn({
45
+ command: options.command ?? resolvePublishedSidecarBinary(),
46
+ args: options.args ?? [],
47
+ cwd: options.cwd,
48
+ }), {
49
+ frameTimeoutMs: options.frameTimeoutMs ?? DEFAULT_SIDECAR_FRAME_TIMEOUT_MS,
50
+ eventBufferCapacity: options.eventBufferCapacity ??
51
+ DEFAULT_SIDECAR_EVENT_BUFFER_CAPACITY,
52
+ gracefulExitMs: options.gracefulExitMs ?? DEFAULT_SIDECAR_GRACEFUL_EXIT_MS,
53
+ forceExitMs: options.forceExitMs ?? DEFAULT_SIDECAR_FORCE_EXIT_MS,
54
+ disposedErrorMessage: options.disposedErrorMessage ?? "sidecar client disposed",
55
+ payloadCodec: options.payloadCodec ?? "bare",
56
+ });
57
+ }
58
+ setSidecarRequestHandler(handler) {
59
+ this.protocolClient.setSidecarRequestHandler(handler);
60
+ }
61
+ onEvent(handler) {
62
+ return this.protocolClient.onEvent(handler);
63
+ }
64
+ async sendRequest(input) {
65
+ return await this.protocolClient.sendRequest(input);
66
+ }
67
+ async waitForEvent(matcher, timeoutMs, options) {
68
+ return await this.protocolClient.waitForEvent(matcher, timeoutMs, options);
69
+ }
70
+ async dispose() {
71
+ this.protocolClient.failPermanently(new Error(this.disposedErrorMessage));
72
+ if (!this.child.stdin.destroyed) {
73
+ try {
74
+ this.child.stdin.end();
75
+ }
76
+ catch {
77
+ // Stdin may already be closing. The child exit watcher will catch up.
78
+ }
79
+ }
80
+ const exitCode = await this.sidecarProcess.waitForExit(this.gracefulExitMs);
81
+ if (exitCode === null) {
82
+ try {
83
+ this.child.kill("SIGKILL");
84
+ }
85
+ catch {
86
+ // The child may have exited between the timeout and the kill attempt.
87
+ }
88
+ await this.sidecarProcess.waitForExit(this.forceExitMs);
89
+ }
90
+ this.protocolClient.dispose();
91
+ try {
92
+ this.child.stdin.destroy();
93
+ }
94
+ catch {
95
+ // Best effort. The child is gone so the descriptor will close on its own.
96
+ }
97
+ try {
98
+ this.child.stdout.destroy();
99
+ }
100
+ catch {
101
+ // Best effort. The child is gone so the descriptor will close on its own.
102
+ }
103
+ try {
104
+ this.child.stderr.destroy();
105
+ }
106
+ catch {
107
+ // Best effort. The child is gone so the descriptor will close on its own.
108
+ }
109
+ if (exitCode !== null &&
110
+ exitCode !== 0 &&
111
+ this.child.signalCode === null) {
112
+ throw new Error(`sidecar exited with code ${exitCode}\nstderr:\n${this.sidecarProcess.stderrText()}`);
113
+ }
114
+ }
115
+ failPermanently(error) {
116
+ this.protocolClient.failPermanently(error, {
117
+ replaceExisting: (current, next) => current instanceof SidecarProcessExited &&
118
+ current.exitCode === null &&
119
+ current.signal === null &&
120
+ next instanceof SidecarProcessExited &&
121
+ (next.exitCode !== null || next.signal !== null),
122
+ });
123
+ }
124
+ }
@@ -0,0 +1,490 @@
1
+ /**
2
+ * NodeRuntime — ergonomic façade for running guest JavaScript end-to-end.
3
+ *
4
+ * Boots a fully virtualized VM (via the native sidecar) and runs guest Node
5
+ * programs with minimal boilerplate. All of the sidecar spawn, session
6
+ * handshake, VM creation, root filesystem bootstrap, runtime-driver mounting,
7
+ * and lifecycle waiting are hidden behind `NodeRuntime.create()`.
8
+ *
9
+ * ```ts
10
+ * const rt = await NodeRuntime.create();
11
+ * const { stdout, exitCode } = await rt.exec("console.log('hi', 1 + 1)");
12
+ * await rt.dispose();
13
+ * ```
14
+ *
15
+ * Guest code is written to an ESM module inside the VM and executed as
16
+ * `node <file>` through the kernel, so all execution stays inside the kernel
17
+ * isolation boundary — no host escapes, no real Node.js builtins for guest
18
+ * work.
19
+ */
20
+ import type { HostToolDefinition, Permissions } from "./test-runtime.js";
21
+ export type { HostToolDefinition, HostToolExample } from "./test-runtime.js";
22
+ /** Options for {@link NodeRuntime.create}. */
23
+ export interface NodeRuntimeCreateOptions {
24
+ /** Environment variables visible to guest processes. */
25
+ env?: Record<string, string>;
26
+ /** Initial working directory for guest processes. Defaults to `/home/user`. */
27
+ cwd?: string;
28
+ /**
29
+ * Permission policy for the VM. Merged over a secure default that **denies
30
+ * network access** (guest code cannot reach the network until you opt in);
31
+ * the virtualized filesystem and processes stay enabled so programs run.
32
+ * Because it merges, a partial policy works: `{ network: "allow" }` grants
33
+ * the network while keeping the execution essentials. Pass a fuller policy
34
+ * (rule sets) to further sandbox individual scopes.
35
+ */
36
+ permissions?: Permissions;
37
+ /**
38
+ * Override the directory containing the WASM command binaries (the source of
39
+ * the guest `sh`). Defaults to the in-repo build output, or the
40
+ * `SECURE_EXEC_WASM_COMMANDS_DIR` environment variable when set.
41
+ */
42
+ commandsDir?: string;
43
+ /**
44
+ * Files to seed into the VM's virtual filesystem before the guest runs,
45
+ * keyed by absolute guest path. Parent directories are created as needed.
46
+ * Use this to project host assets, npm packages, or fixtures into the
47
+ * sandbox so guest code can `import`/`require`/read them. The bytes are
48
+ * copied into the VM's in-memory filesystem; the host filesystem is never
49
+ * exposed to the guest.
50
+ *
51
+ * ```ts
52
+ * const rt = await NodeRuntime.create({
53
+ * files: { "/root/data.json": '{"ok":true}' },
54
+ * });
55
+ * ```
56
+ */
57
+ files?: Record<string, string | Uint8Array>;
58
+ /**
59
+ * Host directories to project into the VM's virtual filesystem, Docker-style.
60
+ * Each mount makes a host directory readable at a guest path. Files are read
61
+ * lazily from the host as the guest accesses them, so large trees (for
62
+ * example a `node_modules` package such as the TypeScript compiler) are
63
+ * projected without copying their bytes up front. The guest sees only the
64
+ * mounted subtree, never the wider host filesystem.
65
+ *
66
+ * ```ts
67
+ * const rt = await NodeRuntime.create({
68
+ * mounts: [
69
+ * {
70
+ * guestPath: "/root/node_modules/typescript",
71
+ * hostPath: "/abs/path/to/node_modules/typescript",
72
+ * readOnly: true,
73
+ * },
74
+ * ],
75
+ * });
76
+ * ```
77
+ */
78
+ mounts?: HostDirectoryMount[];
79
+ /**
80
+ * Mount a host `node_modules` directory into the VM in one call so guest
81
+ * `import`/`require` resolve real, host-installed npm packages.
82
+ *
83
+ * Pass the absolute host path to a `node_modules` directory (or an object
84
+ * with that path and an explicit guest location). The whole directory is
85
+ * projected lazily, Docker-style, at a guest `node_modules` on the resolution
86
+ * path, so any package inside it resolves the way Node would over a real
87
+ * filesystem (ancestor `node_modules` walk, `exports`/conditions, symlinks).
88
+ * This is the ergonomic alternative to wiring up individual `mounts` entries
89
+ * per package.
90
+ *
91
+ * By default the directory is mounted at `/tmp/node_modules`, which is where
92
+ * the resolution walk for a program run by {@link NodeRuntime.exec} /
93
+ * {@link NodeRuntime.run} begins (each program is written under `/tmp`). Pass
94
+ * the object form with `guestPath` to mount it elsewhere on a different
95
+ * module's resolution path.
96
+ *
97
+ * ```ts
98
+ * const rt = await NodeRuntime.create({
99
+ * nodeModules: "/abs/path/to/project/node_modules",
100
+ * });
101
+ * await rt.exec(`
102
+ * import isNumber from "is-number";
103
+ * console.log(isNumber(42));
104
+ * `);
105
+ * ```
106
+ *
107
+ * The host filesystem is never exposed beyond the mounted `node_modules`
108
+ * subtree. The mount is read-only.
109
+ */
110
+ nodeModules?: string | NodeModulesMount;
111
+ /**
112
+ * Host-side tools the guest can invoke as shell commands. Each entry is
113
+ * registered as a named guest command; when the guest runs it, the
114
+ * invocation round-trips back to the host and runs the tool's `handler`,
115
+ * whose return value is delivered back to the guest. This is how you give
116
+ * sandboxed guest code controlled, named host capabilities (the kind an AI
117
+ * agent calls as tools) without granting it the underlying access directly.
118
+ *
119
+ * The guest invokes a tool by name with JSON input:
120
+ *
121
+ * ```ts
122
+ * const rt = await NodeRuntime.create({
123
+ * tools: {
124
+ * add: {
125
+ * description: "Add two numbers",
126
+ * inputSchema: {
127
+ * type: "object",
128
+ * properties: { a: { type: "number" }, b: { type: "number" } },
129
+ * required: ["a", "b"],
130
+ * },
131
+ * handler: ({ a, b }: { a: number; b: number }) => ({ sum: a + b }),
132
+ * },
133
+ * },
134
+ * });
135
+ * await rt.exec(`
136
+ * import { execFileSync } from "node:child_process";
137
+ * const out = execFileSync("add", ["add", "--json", JSON.stringify({ a: 2, b: 3 })]);
138
+ * console.log(out.toString());
139
+ * `);
140
+ * ```
141
+ *
142
+ * When `tools` is provided and no `tool` permission scope is set, the `tool`
143
+ * scope is granted so the registered tools are invocable; pass your own
144
+ * `permissions.tool` policy to gate individual tools.
145
+ */
146
+ tools?: Record<string, HostToolDefinition>;
147
+ /**
148
+ * Guest-bound ports that may accept non-loopback connections. By default a
149
+ * guest server is reachable only over loopback inside the VM; listing a port
150
+ * here lifts that restriction for that port, letting connections from outside
151
+ * the loopback interface reach it. Use this for guests that run servers which
152
+ * must accept external connections (for example a dev server you expose
153
+ * beyond loopback).
154
+ *
155
+ * ```ts
156
+ * const rt = await NodeRuntime.create({
157
+ * permissions: { network: "allow" },
158
+ * loopbackExemptPorts: [3000],
159
+ * });
160
+ * ```
161
+ */
162
+ loopbackExemptPorts?: number[];
163
+ }
164
+ /** A host directory projected into the VM's virtual filesystem. */
165
+ export interface HostDirectoryMount {
166
+ /** Absolute guest path the directory appears at inside the VM. */
167
+ guestPath: string;
168
+ /** Absolute host directory to project (read through the VFS, lazily). */
169
+ hostPath: string;
170
+ /** Mount read-only (the default). Pass `false` to allow guest writes. */
171
+ readOnly?: boolean;
172
+ }
173
+ /**
174
+ * Object form of the `nodeModules` create option: a host `node_modules`
175
+ * directory to project, optionally at an explicit guest path. The string form
176
+ * (`nodeModules: "/abs/node_modules"`) is shorthand for `{ hostPath }`.
177
+ */
178
+ export interface NodeModulesMount {
179
+ /** Absolute host `node_modules` directory to project (read lazily). */
180
+ hostPath: string;
181
+ /**
182
+ * Absolute guest path to mount it at. Defaults to `/tmp/node_modules`, where
183
+ * the resolution walk for {@link NodeRuntime.exec} / {@link NodeRuntime.run}
184
+ * programs begins. Override to put it on a different module's resolution path.
185
+ */
186
+ guestPath?: string;
187
+ }
188
+ /** Result of {@link NodeRuntime.exec}. */
189
+ export interface NodeRuntimeExecResult {
190
+ stdout: string;
191
+ stderr: string;
192
+ exitCode: number;
193
+ }
194
+ /** Options for a single {@link NodeRuntime.exec} call. */
195
+ export interface NodeRuntimeExecOptions {
196
+ /** Extra environment variables for this run, merged over the VM env. */
197
+ env?: Record<string, string>;
198
+ /** Working directory for this run. */
199
+ cwd?: string;
200
+ /** Data piped to the guest program's stdin. */
201
+ stdin?: string | Uint8Array;
202
+ /** Abort the run after this many milliseconds. */
203
+ timeout?: number;
204
+ /**
205
+ * Cancel the run when this signal aborts. On abort the guest process is
206
+ * killed inside the VM (the kernel delivers `SIGTERM`) and the call rejects
207
+ * with the signal's abort reason. Use this to cancel an in-flight run from
208
+ * the outside, for example to enforce your own deadline or stop work when a
209
+ * request is canceled.
210
+ *
211
+ * ```ts
212
+ * const controller = new AbortController();
213
+ * const pending = rt.exec("while (true) {}", { signal: controller.signal });
214
+ * controller.abort();
215
+ * await pending.catch((err) => console.log(err.name)); // "AbortError"
216
+ * ```
217
+ */
218
+ signal?: AbortSignal;
219
+ /**
220
+ * Called with each chunk the guest writes to stdout as it is produced,
221
+ * letting you observe output incrementally instead of waiting for the run to
222
+ * finish. Chunks arrive as raw bytes; decode with a `TextDecoder` for text.
223
+ * The complete output is still returned as `result.stdout` when the run ends.
224
+ */
225
+ onStdout?: (chunk: Uint8Array) => void;
226
+ /**
227
+ * Called with each chunk the guest writes to stderr as it is produced,
228
+ * letting you observe output incrementally instead of waiting for the run to
229
+ * finish. Chunks arrive as raw bytes; decode with a `TextDecoder` for text.
230
+ * The complete output is still returned as `result.stderr` when the run ends.
231
+ */
232
+ onStderr?: (chunk: Uint8Array) => void;
233
+ }
234
+ /** The HTTP request {@link NodeRuntime.fetch} drives into the VM. */
235
+ export interface NodeRuntimeFetchInput {
236
+ /** HTTP method. Defaults to `GET`. */
237
+ method?: string;
238
+ /** Request path (and query), e.g. `/api/users?limit=10`. */
239
+ path: string;
240
+ /** Request headers. */
241
+ headers?: Record<string, string>;
242
+ /** Request body. */
243
+ body?: string | Uint8Array;
244
+ }
245
+ /** The HTTP response {@link NodeRuntime.fetch} returns from the VM. */
246
+ export interface NodeRuntimeFetchResponse {
247
+ /** HTTP status code, e.g. `200`. */
248
+ status: number;
249
+ /** HTTP status text, e.g. `OK`. */
250
+ statusText: string;
251
+ /** Response headers, lower-cased by name. */
252
+ headers: Record<string, string>;
253
+ /** Response body decoded as UTF-8 text. */
254
+ body: string;
255
+ }
256
+ /**
257
+ * Options for {@link NodeRuntime.spawn}. Inherits the streaming `onStdout` /
258
+ * `onStderr` hooks from {@link NodeRuntimeExecOptions}.
259
+ */
260
+ export interface NodeRuntimeSpawnOptions extends NodeRuntimeExecOptions {
261
+ }
262
+ /**
263
+ * Describes the guest TCP listener to wait for with
264
+ * {@link NodeRuntime.waitForListener} or look up with
265
+ * {@link NodeRuntime.findListener}. A listener matches when a guest process is
266
+ * accepting connections on the given `port` (and `host`/`path` when supplied).
267
+ */
268
+ export interface NodeRuntimeListenerQuery {
269
+ /** TCP port the guest listener is bound to, e.g. `3000`. */
270
+ port: number;
271
+ /** Bind host to match, e.g. `127.0.0.1`. Omit to match any host. */
272
+ host?: string;
273
+ /** Unix socket path to match, for path-bound listeners. */
274
+ path?: string;
275
+ }
276
+ /**
277
+ * A matched guest listener returned by {@link NodeRuntime.waitForListener} and
278
+ * {@link NodeRuntime.findListener}. `processId` identifies the guest process
279
+ * that owns the listening socket; the `host`/`port`/`path` it is bound to are
280
+ * reported when known.
281
+ */
282
+ export interface NodeRuntimeListener {
283
+ /** The guest process id that owns the listening socket. */
284
+ processId: string;
285
+ /** The host the listener is bound to, when reported. */
286
+ host?: string;
287
+ /** The port the listener is bound to, when reported. */
288
+ port?: number;
289
+ /** The unix socket path the listener is bound to, when reported. */
290
+ path?: string;
291
+ }
292
+ /** Options for {@link NodeRuntime.waitForListener}. */
293
+ export interface NodeRuntimeWaitForListenerOptions {
294
+ /**
295
+ * Give up after this many milliseconds and reject. Defaults to 10000. The
296
+ * wait also rejects if the bound `signal` aborts first.
297
+ */
298
+ timeoutMs?: number;
299
+ /** Abort the wait early; the returned promise rejects when it fires. */
300
+ signal?: AbortSignal;
301
+ /**
302
+ * How long to wait between listener lookups while polling, in milliseconds.
303
+ * Defaults to 50.
304
+ */
305
+ pollIntervalMs?: number;
306
+ }
307
+ /**
308
+ * A live handle to a guest process started with {@link NodeRuntime.spawn}.
309
+ *
310
+ * Unlike {@link NodeRuntime.exec}, which runs a program to completion and
311
+ * returns its captured output, a handle is returned immediately while the
312
+ * process keeps running. Use it to stream stdout/stderr, feed stdin, signal or
313
+ * kill the process, and await its exit. This is the building block for
314
+ * long-running guests such as dev servers: start one here, then drive requests
315
+ * into it with {@link NodeRuntime.fetch}.
316
+ */
317
+ export interface NodeRuntimeProcess {
318
+ /** The guest process id. */
319
+ readonly pid: number;
320
+ /** Write data to the guest process's stdin. */
321
+ writeStdin(data: string | Uint8Array): void;
322
+ /** Close the guest process's stdin, signalling end-of-input. */
323
+ closeStdin(): void;
324
+ /**
325
+ * Send a signal to the guest process. Defaults to `SIGTERM`. Accepts a
326
+ * signal name (e.g. `"SIGKILL"`) or a raw signal number.
327
+ */
328
+ kill(signal?: NodeJS.Signals | number): void;
329
+ /** Resolve with the guest process's exit code once it terminates. */
330
+ wait(): Promise<number>;
331
+ /** The exit code once the process has exited, or `null` while it runs. */
332
+ readonly exitCode: number | null;
333
+ }
334
+ /** Result of {@link NodeRuntime.run}. */
335
+ export interface NodeRuntimeRunResult<T = unknown> {
336
+ /** The JSON-decoded value the guest produced, when the run succeeded. */
337
+ value?: T;
338
+ stdout: string;
339
+ stderr: string;
340
+ exitCode: number;
341
+ }
342
+ /**
343
+ * Ergonomic, batteries-included runtime for executing guest JavaScript.
344
+ *
345
+ * Construct one with {@link NodeRuntime.create}, run programs with
346
+ * {@link NodeRuntime.exec} / {@link NodeRuntime.run}, and release the VM with
347
+ * {@link NodeRuntime.dispose}. A single instance can run many programs; each
348
+ * call executes a fresh guest process.
349
+ */
350
+ export declare class NodeRuntime {
351
+ private readonly kernel;
352
+ private constructor();
353
+ /**
354
+ * Boot a VM and return a ready-to-use runtime. Spawns the sidecar, opens a
355
+ * session, creates the VM with a bootstrapped root filesystem, mounts the
356
+ * shell and Node runtimes, and waits for the VM to report ready.
357
+ */
358
+ static create(options?: NodeRuntimeCreateOptions): Promise<NodeRuntime>;
359
+ /**
360
+ * Run `code` as a guest Node program and capture its output.
361
+ *
362
+ * The source is written to an ES module inside the VM and executed with
363
+ * `node <file>`; it runs as standard ESM (top-level `await`, `import`).
364
+ */
365
+ exec(code: string, options?: NodeRuntimeExecOptions): Promise<NodeRuntimeExecResult>;
366
+ /**
367
+ * Run an already-written guest program file to completion and capture its
368
+ * output, honoring a caller-supplied `signal` for cancellation.
369
+ *
370
+ * Without a `signal`, this runs the program through the shell (`node <file>`)
371
+ * exactly as before. With a `signal`, it starts the program as a guest
372
+ * process so the run can be cancelled: when the signal aborts, the process is
373
+ * killed inside the VM (the kernel delivers `SIGTERM`) and the call rejects
374
+ * with the signal's abort reason.
375
+ */
376
+ private runProgram;
377
+ /**
378
+ * Start `code` as a long-running guest Node program and return a live handle
379
+ * to it, without waiting for it to finish.
380
+ *
381
+ * The source is written to an ES module inside the VM and started with
382
+ * `node <file>`; it runs as standard ESM (top-level `await`, `import`). The
383
+ * returned {@link NodeRuntimeProcess} lets you stream output, write to stdin,
384
+ * signal or kill the process, and await its exit. Pass `onStdout`/`onStderr`
385
+ * to receive output chunks as they are produced.
386
+ *
387
+ * Use this for guests that do not run to completion, such as a dev server you
388
+ * later drive with {@link NodeRuntime.fetch}:
389
+ *
390
+ * ```ts
391
+ * const server = await rt.spawn(`
392
+ * import http from "node:http";
393
+ * http.createServer((_, res) => res.end("ok")).listen(3000);
394
+ * `);
395
+ * const res = await rt.fetch(3000, { path: "/" });
396
+ * server.kill();
397
+ * await server.wait();
398
+ * ```
399
+ */
400
+ spawn(code: string, options?: NodeRuntimeSpawnOptions): Promise<NodeRuntimeProcess>;
401
+ /**
402
+ * Run `code` and return the JSON-serializable value it produces.
403
+ *
404
+ * The guest exposes a `__return(value)` function; call it with a
405
+ * JSON-serializable value and that value is decoded on the host as
406
+ * `result.value`. If `__return` is never called the value is `undefined`.
407
+ * stdout/stderr/exitCode are still captured.
408
+ */
409
+ run<T = unknown>(code: string, options?: NodeRuntimeExecOptions): Promise<NodeRuntimeRunResult<T>>;
410
+ /**
411
+ * Drive an HTTP request to a guest HTTP server listening inside the VM and
412
+ * read the response back on the host.
413
+ *
414
+ * Point this at a port a guest program is serving, for example a dev server
415
+ * started with {@link NodeRuntime.exec}. The
416
+ * request and response never leave the VM: the connection is made to the
417
+ * guest's loopback listener through the kernel socket table, so this works
418
+ * even when guest network egress is denied.
419
+ *
420
+ * ```ts
421
+ * const res = await rt.fetch(3000, { path: "/health" });
422
+ * console.log(res.status, res.body);
423
+ * ```
424
+ */
425
+ fetch(port: number, input: NodeRuntimeFetchInput): Promise<NodeRuntimeFetchResponse>;
426
+ /**
427
+ * Look up a guest TCP listener once and return it, or `null` when nothing is
428
+ * listening yet.
429
+ *
430
+ * This is the immediate, non-blocking check behind
431
+ * {@link NodeRuntime.waitForListener}: it asks the kernel socket table
432
+ * whether a guest process is accepting connections on the requested `port`
433
+ * (optionally narrowed by `host`/`path`) and returns the match, or `null` if
434
+ * none is up. Use {@link NodeRuntime.waitForListener} when you want to block
435
+ * until one appears.
436
+ *
437
+ * ```ts
438
+ * const listener = rt.findListener({ port: 3000 });
439
+ * if (listener) console.log("up on pid", listener.processId);
440
+ * ```
441
+ */
442
+ findListener(query: NodeRuntimeListenerQuery): NodeRuntimeListener | null;
443
+ /**
444
+ * Block until a guest TCP listener is accepting connections on the requested
445
+ * `port` (optionally narrowed by `host`/`path`), then resolve with it.
446
+ *
447
+ * This is the companion to {@link NodeRuntime.spawn} and
448
+ * {@link NodeRuntime.fetch} for dev-server scenarios: start a server, wait
449
+ * until it is actually listening, then drive requests into it. The kernel
450
+ * socket table is polled until a matching listener appears or the wait is
451
+ * cut short. If `timeoutMs` elapses (default 10000) or the supplied `signal`
452
+ * aborts first, the returned promise rejects.
453
+ *
454
+ * ```ts
455
+ * const server = await rt.spawn(`
456
+ * import http from "node:http";
457
+ * http.createServer((_, res) => res.end("ok")).listen(3000);
458
+ * `);
459
+ * const listener = await rt.waitForListener({ port: 3000 });
460
+ * const res = await rt.fetch(listener.port ?? 3000, { path: "/" });
461
+ * server.kill();
462
+ * await server.wait();
463
+ * ```
464
+ */
465
+ waitForListener(query: NodeRuntimeListenerQuery, options?: NodeRuntimeWaitForListenerOptions): Promise<NodeRuntimeListener>;
466
+ /**
467
+ * Register host-side tools the guest can invoke as shell commands, after the
468
+ * VM is already running. Each entry becomes a named guest command; when the
469
+ * guest runs it, the invocation round-trips back to the host and runs the
470
+ * tool's `handler`, whose return value is delivered back to the guest. This
471
+ * is the same capability as the `tools` create option, exposed for adding
472
+ * tools to a live runtime. See `tools` on {@link NodeRuntime.create} for the
473
+ * invocation shape and permission behavior.
474
+ *
475
+ * When registering tools this way, make sure the `tool` permission scope is
476
+ * granted (for example `permissions: { tool: "allow" }` on
477
+ * {@link NodeRuntime.create}) so the tools are invocable.
478
+ */
479
+ registerTools(tools: Record<string, HostToolDefinition>): Promise<void>;
480
+ /**
481
+ * Write a file into the VM's virtual filesystem, creating parent
482
+ * directories as needed. Use this to project assets or npm packages into
483
+ * the sandbox after boot; the host filesystem is never touched.
484
+ */
485
+ writeFile(filePath: string, content: string | Uint8Array): Promise<void>;
486
+ /** Read a file from the VM's virtual filesystem as raw bytes. */
487
+ readFile(filePath: string): Promise<Uint8Array>;
488
+ /** Tear down the VM and release the sidecar. */
489
+ dispose(): Promise<void>;
490
+ }