@secure-exec/nodejs 0.2.0-rc.1

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 (68) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +7 -0
  3. package/dist/bindings.d.ts +31 -0
  4. package/dist/bindings.js +67 -0
  5. package/dist/bridge/active-handles.d.ts +22 -0
  6. package/dist/bridge/active-handles.js +112 -0
  7. package/dist/bridge/child-process.d.ts +99 -0
  8. package/dist/bridge/child-process.js +672 -0
  9. package/dist/bridge/dispatch.d.ts +2 -0
  10. package/dist/bridge/dispatch.js +40 -0
  11. package/dist/bridge/fs.d.ts +502 -0
  12. package/dist/bridge/fs.js +3307 -0
  13. package/dist/bridge/index.d.ts +10 -0
  14. package/dist/bridge/index.js +41 -0
  15. package/dist/bridge/module.d.ts +75 -0
  16. package/dist/bridge/module.js +325 -0
  17. package/dist/bridge/network.d.ts +1093 -0
  18. package/dist/bridge/network.js +8651 -0
  19. package/dist/bridge/os.d.ts +13 -0
  20. package/dist/bridge/os.js +256 -0
  21. package/dist/bridge/polyfills.d.ts +9 -0
  22. package/dist/bridge/polyfills.js +67 -0
  23. package/dist/bridge/process.d.ts +121 -0
  24. package/dist/bridge/process.js +1382 -0
  25. package/dist/bridge/whatwg-url.d.ts +67 -0
  26. package/dist/bridge/whatwg-url.js +712 -0
  27. package/dist/bridge-contract.d.ts +774 -0
  28. package/dist/bridge-contract.js +172 -0
  29. package/dist/bridge-handlers.d.ts +199 -0
  30. package/dist/bridge-handlers.js +4263 -0
  31. package/dist/bridge-loader.d.ts +9 -0
  32. package/dist/bridge-loader.js +87 -0
  33. package/dist/bridge-setup.d.ts +1 -0
  34. package/dist/bridge-setup.js +3 -0
  35. package/dist/bridge.js +21652 -0
  36. package/dist/builtin-modules.d.ts +25 -0
  37. package/dist/builtin-modules.js +312 -0
  38. package/dist/default-network-adapter.d.ts +13 -0
  39. package/dist/default-network-adapter.js +351 -0
  40. package/dist/driver.d.ts +87 -0
  41. package/dist/driver.js +191 -0
  42. package/dist/esm-compiler.d.ts +14 -0
  43. package/dist/esm-compiler.js +68 -0
  44. package/dist/execution-driver.d.ts +37 -0
  45. package/dist/execution-driver.js +977 -0
  46. package/dist/host-network-adapter.d.ts +7 -0
  47. package/dist/host-network-adapter.js +279 -0
  48. package/dist/index.d.ts +20 -0
  49. package/dist/index.js +23 -0
  50. package/dist/isolate-bootstrap.d.ts +86 -0
  51. package/dist/isolate-bootstrap.js +125 -0
  52. package/dist/ivm-compat.d.ts +7 -0
  53. package/dist/ivm-compat.js +31 -0
  54. package/dist/kernel-runtime.d.ts +58 -0
  55. package/dist/kernel-runtime.js +535 -0
  56. package/dist/module-access.d.ts +75 -0
  57. package/dist/module-access.js +606 -0
  58. package/dist/module-resolver.d.ts +8 -0
  59. package/dist/module-resolver.js +150 -0
  60. package/dist/os-filesystem.d.ts +42 -0
  61. package/dist/os-filesystem.js +161 -0
  62. package/dist/package-bundler.d.ts +36 -0
  63. package/dist/package-bundler.js +497 -0
  64. package/dist/polyfills.d.ts +17 -0
  65. package/dist/polyfills.js +97 -0
  66. package/dist/worker-adapter.d.ts +21 -0
  67. package/dist/worker-adapter.js +34 -0
  68. package/package.json +123 -0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Concrete HostNetworkAdapter for Node.js, delegating to node:net,
3
+ * node:dgram, and node:dns for real external I/O.
4
+ */
5
+ import type { HostNetworkAdapter } from "@secure-exec/core";
6
+ /** Create a Node.js HostNetworkAdapter that uses real OS networking. */
7
+ export declare function createNodeHostNetworkAdapter(): HostNetworkAdapter;
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Concrete HostNetworkAdapter for Node.js, delegating to node:net,
3
+ * node:dgram, and node:dns for real external I/O.
4
+ */
5
+ import * as net from "node:net";
6
+ import * as dgram from "node:dgram";
7
+ import * as dns from "node:dns";
8
+ /**
9
+ * Queued-read adapter: incoming data/EOF/errors are buffered so that
10
+ * each read() call returns the next chunk or null for EOF.
11
+ */
12
+ class NodeHostSocket {
13
+ socket;
14
+ readQueue = [];
15
+ waiters = [];
16
+ ended = false;
17
+ errored = null;
18
+ constructor(socket) {
19
+ this.socket = socket;
20
+ socket.on("data", (chunk) => {
21
+ const data = new Uint8Array(chunk);
22
+ const waiter = this.waiters.shift();
23
+ if (waiter) {
24
+ waiter(data);
25
+ }
26
+ else {
27
+ this.readQueue.push(data);
28
+ }
29
+ });
30
+ socket.on("end", () => {
31
+ this.ended = true;
32
+ const waiter = this.waiters.shift();
33
+ if (waiter) {
34
+ waiter(null);
35
+ }
36
+ else {
37
+ this.readQueue.push(null);
38
+ }
39
+ });
40
+ socket.on("error", (err) => {
41
+ this.errored = err;
42
+ // Wake all pending readers with EOF
43
+ for (const waiter of this.waiters.splice(0)) {
44
+ waiter(null);
45
+ }
46
+ if (!this.ended) {
47
+ this.ended = true;
48
+ this.readQueue.push(null);
49
+ }
50
+ });
51
+ }
52
+ async write(data) {
53
+ return new Promise((resolve, reject) => {
54
+ this.socket.write(data, (err) => {
55
+ if (err)
56
+ reject(err);
57
+ else
58
+ resolve();
59
+ });
60
+ });
61
+ }
62
+ async read() {
63
+ const queued = this.readQueue.shift();
64
+ if (queued !== undefined)
65
+ return queued;
66
+ if (this.ended)
67
+ return null;
68
+ return new Promise((resolve) => {
69
+ this.waiters.push(resolve);
70
+ });
71
+ }
72
+ async close() {
73
+ return new Promise((resolve) => {
74
+ if (this.socket.destroyed) {
75
+ resolve();
76
+ return;
77
+ }
78
+ this.socket.once("close", () => resolve());
79
+ this.socket.destroy();
80
+ });
81
+ }
82
+ setOption(level, optname, optval) {
83
+ // Forward common options to the real socket
84
+ this.socket.setNoDelay(optval !== 0);
85
+ }
86
+ shutdown(how) {
87
+ if (how === "write" || how === "both") {
88
+ this.socket.end();
89
+ }
90
+ if (how === "read" || how === "both") {
91
+ this.socket.pause();
92
+ this.socket.removeAllListeners("data");
93
+ if (!this.ended) {
94
+ this.ended = true;
95
+ const waiter = this.waiters.shift();
96
+ if (waiter)
97
+ waiter(null);
98
+ else
99
+ this.readQueue.push(null);
100
+ }
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * TCP listener backed by node:net.Server. Incoming connections are
106
+ * queued so each accept() call returns the next one.
107
+ */
108
+ class NodeHostListener {
109
+ server;
110
+ _port;
111
+ connQueue = [];
112
+ waiters = [];
113
+ closed = false;
114
+ constructor(server, port) {
115
+ this.server = server;
116
+ this._port = port;
117
+ server.on("connection", (socket) => {
118
+ const waiter = this.waiters.shift();
119
+ if (waiter) {
120
+ waiter(socket);
121
+ }
122
+ else {
123
+ this.connQueue.push(socket);
124
+ }
125
+ });
126
+ }
127
+ get port() {
128
+ return this._port;
129
+ }
130
+ async accept() {
131
+ const queued = this.connQueue.shift();
132
+ if (queued)
133
+ return new NodeHostSocket(queued);
134
+ if (this.closed)
135
+ throw new Error("Listener closed");
136
+ return new Promise((resolve, reject) => {
137
+ if (this.closed) {
138
+ reject(new Error("Listener closed"));
139
+ return;
140
+ }
141
+ this.waiters.push((socket) => {
142
+ resolve(new NodeHostSocket(socket));
143
+ });
144
+ });
145
+ }
146
+ async close() {
147
+ this.closed = true;
148
+ // Reject pending accept waiters
149
+ for (const waiter of this.waiters.splice(0)) {
150
+ // Resolve with a destroyed socket to signal closure — caller handles
151
+ // the error via the socket's error/close events
152
+ }
153
+ return new Promise((resolve, reject) => {
154
+ this.server.close((err) => {
155
+ if (err)
156
+ reject(err);
157
+ else
158
+ resolve();
159
+ });
160
+ });
161
+ }
162
+ }
163
+ /**
164
+ * UDP socket backed by node:dgram.Socket. Messages are queued
165
+ * so each recv() call returns the next datagram.
166
+ */
167
+ class NodeHostUdpSocket {
168
+ socket;
169
+ msgQueue = [];
170
+ waiters = [];
171
+ closed = false;
172
+ constructor(socket) {
173
+ this.socket = socket;
174
+ socket.on("message", (msg, rinfo) => {
175
+ const entry = {
176
+ data: new Uint8Array(msg),
177
+ remoteAddr: { host: rinfo.address, port: rinfo.port },
178
+ };
179
+ const waiter = this.waiters.shift();
180
+ if (waiter) {
181
+ waiter(entry);
182
+ }
183
+ else {
184
+ this.msgQueue.push(entry);
185
+ }
186
+ });
187
+ }
188
+ async recv() {
189
+ const queued = this.msgQueue.shift();
190
+ if (queued)
191
+ return queued;
192
+ if (this.closed)
193
+ throw new Error("UDP socket closed");
194
+ return new Promise((resolve, reject) => {
195
+ if (this.closed) {
196
+ reject(new Error("UDP socket closed"));
197
+ return;
198
+ }
199
+ this.waiters.push(resolve);
200
+ });
201
+ }
202
+ async close() {
203
+ this.closed = true;
204
+ return new Promise((resolve) => {
205
+ this.socket.close(() => resolve());
206
+ });
207
+ }
208
+ }
209
+ /** Create a Node.js HostNetworkAdapter that uses real OS networking. */
210
+ export function createNodeHostNetworkAdapter() {
211
+ return {
212
+ async tcpConnect(host, port) {
213
+ return new Promise((resolve, reject) => {
214
+ const socket = net.connect({ host, port });
215
+ socket.once("connect", () => {
216
+ socket.removeListener("error", reject);
217
+ resolve(new NodeHostSocket(socket));
218
+ });
219
+ socket.once("error", (err) => {
220
+ socket.removeListener("connect", resolve);
221
+ reject(err);
222
+ });
223
+ });
224
+ },
225
+ async tcpListen(host, port) {
226
+ return new Promise((resolve, reject) => {
227
+ const server = net.createServer();
228
+ server.once("listening", () => {
229
+ server.removeListener("error", reject);
230
+ const addr = server.address();
231
+ resolve(new NodeHostListener(server, addr.port));
232
+ });
233
+ server.once("error", (err) => {
234
+ server.removeListener("listening", resolve);
235
+ reject(err);
236
+ });
237
+ server.listen(port, host);
238
+ });
239
+ },
240
+ async udpBind(host, port) {
241
+ return new Promise((resolve, reject) => {
242
+ const socket = dgram.createSocket("udp4");
243
+ socket.once("listening", () => {
244
+ socket.removeListener("error", reject);
245
+ resolve(new NodeHostUdpSocket(socket));
246
+ });
247
+ socket.once("error", (err) => {
248
+ socket.removeListener("listening", resolve);
249
+ reject(err);
250
+ });
251
+ socket.bind(port, host);
252
+ });
253
+ },
254
+ async udpSend(socket, data, host, port) {
255
+ // Access the underlying dgram socket via the wrapper
256
+ const udp = socket;
257
+ const inner = udp.socket;
258
+ return new Promise((resolve, reject) => {
259
+ inner.send(data, 0, data.length, port, host, (err) => {
260
+ if (err)
261
+ reject(err);
262
+ else
263
+ resolve();
264
+ });
265
+ });
266
+ },
267
+ async dnsLookup(hostname, rrtype) {
268
+ const family = rrtype === "AAAA" ? 6 : 4;
269
+ return new Promise((resolve, reject) => {
270
+ dns.lookup(hostname, { family }, (err, address, resultFamily) => {
271
+ if (err)
272
+ reject(err);
273
+ else
274
+ resolve({ address, family: resultFamily });
275
+ });
276
+ });
277
+ },
278
+ };
279
+ }
@@ -0,0 +1,20 @@
1
+ export { getRawBridgeCode, getBridgeAttachCode } from "./bridge-loader.js";
2
+ export { bundlePolyfill, getAvailableStdlib, hasPolyfill, prebundleAllPolyfills, } from "./polyfills.js";
3
+ export { NodeExecutionDriver } from "./execution-driver.js";
4
+ export type { NodeExecutionDriverOptions } from "./isolate-bootstrap.js";
5
+ export { createDefaultNetworkAdapter, createNodeDriver, createNodeRuntimeDriverFactory, NodeFileSystem, filterEnv, isPrivateIp, } from "./driver.js";
6
+ export type { NodeDriverOptions, NodeRuntimeDriverFactoryOptions, } from "./driver.js";
7
+ export { ModuleAccessFileSystem } from "./module-access.js";
8
+ export type { ModuleAccessOptions } from "./module-access.js";
9
+ export { emitConsoleEvent, stripDangerousEnv, createProcessConfigForExecution, } from "./bridge-handlers.js";
10
+ export type { BindingTree, BindingFunction } from "./bindings.js";
11
+ export { BINDING_PREFIX, flattenBindingTree } from "./bindings.js";
12
+ export { createNodeRuntime } from "./kernel-runtime.js";
13
+ export type { NodeRuntimeOptions } from "./kernel-runtime.js";
14
+ export { createKernelCommandExecutor, createKernelVfsAdapter, createHostFallbackVfs, } from "./kernel-runtime.js";
15
+ export { HostNodeFileSystem } from "./os-filesystem.js";
16
+ export type { HostNodeFileSystemOptions } from "./os-filesystem.js";
17
+ export { NodeWorkerAdapter } from "./worker-adapter.js";
18
+ export type { WorkerHandle } from "./worker-adapter.js";
19
+ export { createNodeHostNetworkAdapter } from "./host-network-adapter.js";
20
+ export { TIMEOUT_EXIT_CODE, TIMEOUT_ERROR_MESSAGE, } from "@secure-exec/core";
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ // Bridge compilation
2
+ export { getRawBridgeCode, getBridgeAttachCode } from "./bridge-loader.js";
3
+ // Stdlib polyfill bundling
4
+ export { bundlePolyfill, getAvailableStdlib, hasPolyfill, prebundleAllPolyfills, } from "./polyfills.js";
5
+ // Node execution driver
6
+ export { NodeExecutionDriver } from "./execution-driver.js";
7
+ // Node system driver
8
+ export { createDefaultNetworkAdapter, createNodeDriver, createNodeRuntimeDriverFactory, NodeFileSystem, filterEnv, isPrivateIp, } from "./driver.js";
9
+ // Module access filesystem
10
+ export { ModuleAccessFileSystem } from "./module-access.js";
11
+ // Bridge handlers
12
+ export { emitConsoleEvent, stripDangerousEnv, createProcessConfigForExecution, } from "./bridge-handlers.js";
13
+ export { BINDING_PREFIX, flattenBindingTree } from "./bindings.js";
14
+ // Kernel runtime driver (RuntimeDriver for kernel.mount())
15
+ export { createNodeRuntime } from "./kernel-runtime.js";
16
+ export { createKernelCommandExecutor, createKernelVfsAdapter, createHostFallbackVfs, } from "./kernel-runtime.js";
17
+ // OS platform adapters (host filesystem with root, worker threads)
18
+ export { HostNodeFileSystem } from "./os-filesystem.js";
19
+ export { NodeWorkerAdapter } from "./worker-adapter.js";
20
+ // Host network adapter (HostNetworkAdapter for kernel delegation)
21
+ export { createNodeHostNetworkAdapter } from "./host-network-adapter.js";
22
+ // Timeout utilities (re-exported from core)
23
+ export { TIMEOUT_EXIT_CODE, TIMEOUT_ERROR_MESSAGE, } from "@secure-exec/core";
@@ -0,0 +1,86 @@
1
+ import type { Permissions, VirtualFileSystem } from "@secure-exec/core";
2
+ import type { CommandExecutor, NetworkAdapter, RuntimeDriverOptions, SpawnedProcess } from "@secure-exec/core";
3
+ import type { StdioHook, OSConfig, ProcessConfig, TimingMitigation } from "@secure-exec/core/internal/shared/api-types";
4
+ import type { ResolutionCache } from "./package-bundler.js";
5
+ import type { BindingTree } from "./bindings.js";
6
+ export interface NodeExecutionDriverOptions extends RuntimeDriverOptions {
7
+ createIsolate?(memoryLimit: number): unknown;
8
+ bindings?: BindingTree;
9
+ /** Callback to toggle PTY raw mode — wired by kernel runtime when PTY is attached. */
10
+ onPtySetRawMode?: (mode: boolean) => void;
11
+ /** Kernel socket table — routes net.connect through kernel instead of host TCP. */
12
+ socketTable?: import("@secure-exec/core").SocketTable;
13
+ /** Kernel process table — registers child processes for cross-runtime visibility. */
14
+ processTable?: import("@secure-exec/core").ProcessTable;
15
+ /** Kernel timer table — tracks sandbox timers for budget enforcement and cleanup. */
16
+ timerTable?: import("@secure-exec/core").TimerTable;
17
+ /** Process ID for kernel socket/process ownership. Required when socketTable/processTable is set. */
18
+ pid?: number;
19
+ }
20
+ export interface BudgetState {
21
+ outputBytes: number;
22
+ bridgeCalls: number;
23
+ activeTimers: number;
24
+ childProcesses: number;
25
+ }
26
+ /** Shared mutable state owned by NodeExecutionDriver, passed to extracted modules. */
27
+ export interface DriverDeps {
28
+ filesystem: VirtualFileSystem;
29
+ commandExecutor: CommandExecutor;
30
+ networkAdapter: NetworkAdapter;
31
+ permissions?: Permissions;
32
+ processConfig: ProcessConfig;
33
+ osConfig: OSConfig;
34
+ onStdio?: StdioHook;
35
+ cpuTimeLimitMs?: number;
36
+ timingMitigation: TimingMitigation;
37
+ bridgeBase64TransferLimitBytes: number;
38
+ isolateJsonPayloadLimitBytes: number;
39
+ maxOutputBytes?: number;
40
+ maxBridgeCalls?: number;
41
+ maxTimers?: number;
42
+ maxChildProcesses?: number;
43
+ maxHandles?: number;
44
+ budgetState: BudgetState;
45
+ activeHttpServerIds: Set<number>;
46
+ activeHttpServerClosers: Map<number, () => Promise<void>>;
47
+ activeChildProcesses: Map<number, SpawnedProcess>;
48
+ activeHostTimers: Set<ReturnType<typeof setTimeout>>;
49
+ moduleFormatCache: Map<string, "esm" | "cjs" | "json">;
50
+ packageTypeCache: Map<string, "module" | "commonjs" | null>;
51
+ resolutionCache: ResolutionCache;
52
+ /** Optional callback for PTY setRawMode — wired by kernel when PTY is attached. */
53
+ onPtySetRawMode?: (mode: boolean) => void;
54
+ }
55
+ export declare const DEFAULT_BRIDGE_BASE64_TRANSFER_BYTES: number;
56
+ export declare const DEFAULT_ISOLATE_JSON_PAYLOAD_BYTES: number;
57
+ export declare const MIN_CONFIGURED_PAYLOAD_BYTES = 1024;
58
+ export declare const MAX_CONFIGURED_PAYLOAD_BYTES: number;
59
+ export declare const PAYLOAD_LIMIT_ERROR_CODE = "ERR_SANDBOX_PAYLOAD_TOO_LARGE";
60
+ export declare const RESOURCE_BUDGET_ERROR_CODE = "ERR_RESOURCE_BUDGET_EXCEEDED";
61
+ export declare const DEFAULT_MAX_TIMERS = 10000;
62
+ export declare const DEFAULT_MAX_HANDLES = 10000;
63
+ export declare const DEFAULT_SANDBOX_CWD = "/root";
64
+ export declare const DEFAULT_SANDBOX_HOME = "/root";
65
+ export declare const DEFAULT_SANDBOX_TMPDIR = "/tmp";
66
+ export declare class PayloadLimitError extends Error {
67
+ constructor(payloadLabel: string, maxBytes: number, actualBytes: number);
68
+ }
69
+ export declare function normalizePayloadLimit(configuredValue: number | undefined, defaultValue: number, optionName: string): number;
70
+ export declare function getUtf8ByteLength(text: string): number;
71
+ export declare function getBase64EncodedByteLength(rawByteLength: number): number;
72
+ export declare function assertPayloadByteLength(payloadLabel: string, actualBytes: number, maxBytes: number): void;
73
+ export declare function assertTextPayloadSize(payloadLabel: string, text: string, maxBytes: number): void;
74
+ export declare function createBudgetState(): BudgetState;
75
+ export declare function clearActiveHostTimers(deps: Pick<DriverDeps, "activeHostTimers">): void;
76
+ export declare function killActiveChildProcesses(deps: Pick<DriverDeps, "activeChildProcesses">): void;
77
+ export declare function checkBridgeBudget(deps: Pick<DriverDeps, "maxBridgeCalls" | "budgetState">): void;
78
+ export declare function parseJsonWithLimit<T>(payloadLabel: string, jsonText: string, maxBytes: number): T;
79
+ export declare function getExecutionTimeoutMs(override: number | undefined, cpuTimeLimitMs: number | undefined): number | undefined;
80
+ export declare function getTimingMitigation(override: TimingMitigation | undefined, defaultMitigation: TimingMitigation): TimingMitigation;
81
+ export declare const polyfillCodeCache: Map<string, string>;
82
+ export declare const polyfillNamedExportsCache: Map<string, string[]>;
83
+ export declare const hostBuiltinNamedExportsCache: Map<string, string[]>;
84
+ export declare const hostRequire: NodeJS.Require;
85
+ export declare function isValidExportName(name: string): boolean;
86
+ export declare function getHostBuiltinNamedExports(moduleName: string): string[];
@@ -0,0 +1,125 @@
1
+ import { createRequire } from "node:module";
2
+ // Constants
3
+ export const DEFAULT_BRIDGE_BASE64_TRANSFER_BYTES = 16 * 1024 * 1024;
4
+ export const DEFAULT_ISOLATE_JSON_PAYLOAD_BYTES = 4 * 1024 * 1024;
5
+ export const MIN_CONFIGURED_PAYLOAD_BYTES = 1024;
6
+ export const MAX_CONFIGURED_PAYLOAD_BYTES = 64 * 1024 * 1024;
7
+ export const PAYLOAD_LIMIT_ERROR_CODE = "ERR_SANDBOX_PAYLOAD_TOO_LARGE";
8
+ export const RESOURCE_BUDGET_ERROR_CODE = "ERR_RESOURCE_BUDGET_EXCEEDED";
9
+ export const DEFAULT_MAX_TIMERS = 10_000;
10
+ export const DEFAULT_MAX_HANDLES = 10_000;
11
+ export const DEFAULT_SANDBOX_CWD = "/root";
12
+ export const DEFAULT_SANDBOX_HOME = "/root";
13
+ export const DEFAULT_SANDBOX_TMPDIR = "/tmp";
14
+ export class PayloadLimitError extends Error {
15
+ constructor(payloadLabel, maxBytes, actualBytes) {
16
+ super(`${PAYLOAD_LIMIT_ERROR_CODE}: ${payloadLabel} exceeds ${maxBytes} bytes (got ${actualBytes})`);
17
+ this.name = "PayloadLimitError";
18
+ }
19
+ }
20
+ export function normalizePayloadLimit(configuredValue, defaultValue, optionName) {
21
+ if (configuredValue === undefined) {
22
+ return defaultValue;
23
+ }
24
+ if (!Number.isFinite(configuredValue) || configuredValue <= 0) {
25
+ throw new RangeError(`${optionName} must be a positive finite number`);
26
+ }
27
+ const normalizedValue = Math.floor(configuredValue);
28
+ if (normalizedValue < MIN_CONFIGURED_PAYLOAD_BYTES) {
29
+ throw new RangeError(`${optionName} must be at least ${MIN_CONFIGURED_PAYLOAD_BYTES} bytes`);
30
+ }
31
+ if (normalizedValue > MAX_CONFIGURED_PAYLOAD_BYTES) {
32
+ throw new RangeError(`${optionName} must be at most ${MAX_CONFIGURED_PAYLOAD_BYTES} bytes`);
33
+ }
34
+ return normalizedValue;
35
+ }
36
+ export function getUtf8ByteLength(text) {
37
+ return Buffer.byteLength(text, "utf8");
38
+ }
39
+ export function getBase64EncodedByteLength(rawByteLength) {
40
+ return Math.ceil(rawByteLength / 3) * 4;
41
+ }
42
+ export function assertPayloadByteLength(payloadLabel, actualBytes, maxBytes) {
43
+ if (actualBytes <= maxBytes) {
44
+ return;
45
+ }
46
+ throw new PayloadLimitError(payloadLabel, maxBytes, actualBytes);
47
+ }
48
+ export function assertTextPayloadSize(payloadLabel, text, maxBytes) {
49
+ assertPayloadByteLength(payloadLabel, getUtf8ByteLength(text), maxBytes);
50
+ }
51
+ export function createBudgetState() {
52
+ return { outputBytes: 0, bridgeCalls: 0, activeTimers: 0, childProcesses: 0 };
53
+ }
54
+ export function clearActiveHostTimers(deps) {
55
+ for (const id of deps.activeHostTimers) {
56
+ clearTimeout(id);
57
+ }
58
+ deps.activeHostTimers.clear();
59
+ }
60
+ export function killActiveChildProcesses(deps) {
61
+ for (const proc of deps.activeChildProcesses.values()) {
62
+ try {
63
+ proc.kill(9); // SIGKILL
64
+ }
65
+ catch {
66
+ // Process may already be dead
67
+ }
68
+ }
69
+ deps.activeChildProcesses.clear();
70
+ }
71
+ export function checkBridgeBudget(deps) {
72
+ if (deps.maxBridgeCalls === undefined)
73
+ return;
74
+ deps.budgetState.bridgeCalls++;
75
+ if (deps.budgetState.bridgeCalls > deps.maxBridgeCalls) {
76
+ throw new Error(`${RESOURCE_BUDGET_ERROR_CODE}: maximum bridge calls exceeded`);
77
+ }
78
+ }
79
+ export function parseJsonWithLimit(payloadLabel, jsonText, maxBytes) {
80
+ assertTextPayloadSize(payloadLabel, jsonText, maxBytes);
81
+ return JSON.parse(jsonText);
82
+ }
83
+ export function getExecutionTimeoutMs(override, cpuTimeLimitMs) {
84
+ const timeoutMs = override ?? cpuTimeLimitMs;
85
+ if (timeoutMs === undefined) {
86
+ return undefined;
87
+ }
88
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
89
+ throw new RangeError("cpuTimeLimitMs must be a positive finite number");
90
+ }
91
+ return Math.floor(timeoutMs);
92
+ }
93
+ export function getTimingMitigation(override, defaultMitigation) {
94
+ return override ?? defaultMitigation;
95
+ }
96
+ // Module-level caches for polyfill and host builtin named exports
97
+ export const polyfillCodeCache = new Map();
98
+ export const polyfillNamedExportsCache = new Map();
99
+ export const hostBuiltinNamedExportsCache = new Map();
100
+ export const hostRequire = createRequire(import.meta.url);
101
+ export function isValidExportName(name) {
102
+ return /^[A-Za-z_$][\w$]*$/.test(name);
103
+ }
104
+ export function getHostBuiltinNamedExports(moduleName) {
105
+ const cached = hostBuiltinNamedExportsCache.get(moduleName);
106
+ if (cached) {
107
+ return cached;
108
+ }
109
+ try {
110
+ const loaded = hostRequire(`node:${moduleName}`);
111
+ const names = Array.from(new Set([
112
+ ...Object.keys(loaded ?? {}),
113
+ ...Object.getOwnPropertyNames(loaded ?? {}),
114
+ ]))
115
+ .filter((name) => name !== "default")
116
+ .filter(isValidExportName)
117
+ .sort();
118
+ hostBuiltinNamedExportsCache.set(moduleName, names);
119
+ return names;
120
+ }
121
+ catch {
122
+ hostBuiltinNamedExportsCache.set(moduleName, []);
123
+ return [];
124
+ }
125
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Generate JS source for the ivm-compat shim.
3
+ *
4
+ * Must run AFTER the Rust side registers bridge functions on the global,
5
+ * and BEFORE the bridge bundle IIFE executes.
6
+ */
7
+ export declare function getIvmCompatShimSource(): string;
@@ -0,0 +1,31 @@
1
+ // Compatibility shim for bridge calling conventions.
2
+ //
3
+ // The bridge bundle code calls host functions via ivm.Reference methods:
4
+ // .applySync(ctx, args) — sync call
5
+ // .applySyncPromise(ctx, args) — sync call (host may be async)
6
+ // .apply(ctx, args, opts) — async call (opts ignored, Function.prototype.apply works)
7
+ //
8
+ // The Rust V8 runtime registers host functions as plain FunctionTemplate functions.
9
+ // This shim adds the missing .applySync() and .applySyncPromise() methods.
10
+ // .apply() already works via Function.prototype.apply (third arg is ignored).
11
+ import { HOST_BRIDGE_GLOBAL_KEY_LIST, } from "./bridge-contract.js";
12
+ /**
13
+ * Generate JS source for the ivm-compat shim.
14
+ *
15
+ * Must run AFTER the Rust side registers bridge functions on the global,
16
+ * and BEFORE the bridge bundle IIFE executes.
17
+ */
18
+ export function getIvmCompatShimSource() {
19
+ const keyListJson = JSON.stringify(HOST_BRIDGE_GLOBAL_KEY_LIST.filter(
20
+ // _processConfig and _osConfig are config objects, not callable functions
21
+ (k) => k !== "_processConfig" && k !== "_osConfig"));
22
+ return `(function(){
23
+ var keys = ${keyListJson};
24
+ for (var i = 0; i < keys.length; i++) {
25
+ var fn = globalThis[keys[i]];
26
+ if (typeof fn !== 'function') continue;
27
+ fn.applySync = function(ctx, args) { return this.call(null, ...(args || [])); };
28
+ fn.applySyncPromise = function(ctx, args) { return this.call(null, ...(args || [])); };
29
+ }
30
+ })();`;
31
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Node.js runtime driver for kernel integration.
3
+ *
4
+ * Wraps the existing NodeExecutionDriver behind the kernel RuntimeDriver
5
+ * interface. Each spawn() creates a fresh V8 isolate via NodeExecutionDriver
6
+ * and executes the target script. The bridge child_process.spawn routes
7
+ * through KernelInterface.spawn() so shell commands dispatch to WasmVM
8
+ * or other mounted runtimes.
9
+ */
10
+ import type { KernelRuntimeDriver as RuntimeDriver, KernelInterface, Permissions, VirtualFileSystem } from '@secure-exec/core';
11
+ import type { BindingTree } from './bindings.js';
12
+ import type { CommandExecutor } from '@secure-exec/core';
13
+ export interface NodeRuntimeOptions {
14
+ /** Memory limit in MB for each V8 isolate (default: 128). */
15
+ memoryLimit?: number;
16
+ /**
17
+ * Host filesystem paths that the isolate may read for module resolution
18
+ * (e.g. npm's own install directory). By default, the driver discovers
19
+ * the host npm location automatically.
20
+ */
21
+ moduleAccessPaths?: string[];
22
+ /**
23
+ * Bridge permissions for isolate processes. Defaults to allowAllChildProcess
24
+ * (fs/network/env deny-by-default). Use allowAll for full sandbox access.
25
+ */
26
+ permissions?: Partial<Permissions>;
27
+ /**
28
+ * Host-side functions exposed to sandbox code via SecureExec.bindings.
29
+ * Nested objects become dot-separated paths (max depth 4, max 64 leaves).
30
+ */
31
+ bindings?: BindingTree;
32
+ }
33
+ /**
34
+ * Create a Node.js RuntimeDriver that can be mounted into the kernel.
35
+ */
36
+ export declare function createNodeRuntime(options?: NodeRuntimeOptions): RuntimeDriver;
37
+ /**
38
+ * CommandExecutor adapter that wraps KernelInterface.spawn().
39
+ * This is the critical integration point: when code inside the V8 isolate
40
+ * calls child_process.spawn('sh', ['-c', 'echo hello']), the bridge
41
+ * delegates here, which calls kernel.spawn() to route 'sh' to WasmVM.
42
+ */
43
+ export declare function createKernelCommandExecutor(kernel: KernelInterface, parentPid: number): CommandExecutor;
44
+ /**
45
+ * Thin adapter from kernel VFS to secure-exec VFS interface.
46
+ * The kernel VFS is a superset, so this just narrows the type.
47
+ */
48
+ export declare function createKernelVfsAdapter(kernelVfs: KernelInterface['vfs']): VirtualFileSystem;
49
+ /**
50
+ * Wrap a VFS with host filesystem fallback for read operations.
51
+ *
52
+ * When npm/npx runs inside the V8 isolate, require() must resolve npm's own
53
+ * internal modules (e.g. '../lib/cli/entry'). These live on the host
54
+ * filesystem, not in the kernel VFS. This wrapper tries the kernel VFS first
55
+ * and falls back to the host filesystem for reads. Writes always go to the
56
+ * kernel VFS.
57
+ */
58
+ export declare function createHostFallbackVfs(base: VirtualFileSystem): VirtualFileSystem;