@utdk/isolate 0.1.0-dev.646adf4

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.
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Credential bridge for the Isolate runtime.
3
+ *
4
+ * The bridge is the ONLY channel through which credentials and provider
5
+ * operations enter the sandboxed vm context. It is created in the host context
6
+ * (where Node.js module loading works normally) and exposed as `__bridge__`
7
+ * inside the sandbox.
8
+ *
9
+ * Design:
10
+ * - The provider module is dynamically imported in the HOST context, so it
11
+ * can resolve its own dependencies normally.
12
+ * - Credentials are mapped to an `AuthProvider` from `@utdk/common`, which
13
+ * injects them into HTTP request headers at call time.
14
+ * - The operation function is resolved via dot-notation path on the client.
15
+ * - The sandbox script calls `await __bridge__.call(args)` — it never
16
+ * touches `process.env` or any host filesystem API.
17
+ */
18
+ import { ApiKey, BearerToken } from "@utdk/common";
19
+ // ---------------------------------------------------------------------------
20
+ // Credential resolution
21
+ // ---------------------------------------------------------------------------
22
+ /**
23
+ * Resolves an `AuthProvider` from a flat credentials map and the provider's
24
+ * `utdk.auth` config array (from its `package.json`).
25
+ *
26
+ * Supported patterns:
27
+ * api_key – Bearer header: `api_key: "Bearer ${TOKEN_NAME}"`
28
+ * api_key – Raw header: `api_key: "${TOKEN_NAME}"`
29
+ * oauth2 – Pre-resolved: looks for `PROVIDER_ACCESS_TOKEN` key
30
+ *
31
+ * Falls back to `BearerToken(firstValue)` when the auth config is absent or
32
+ * the pattern is not recognised.
33
+ */
34
+ export function resolveAuthProvider(credentials, authConfigs = []) {
35
+ if (Object.keys(credentials).length === 0) {
36
+ return undefined;
37
+ }
38
+ for (const config of authConfigs) {
39
+ if (config.auth_type === "api_key" && config.api_key) {
40
+ // Pattern: "Bearer ${TOKEN_NAME}"
41
+ const bearerMatch = config.api_key.match(/^Bearer \$\{([^}]+)\}$/);
42
+ if (bearerMatch) {
43
+ const varName = bearerMatch[1];
44
+ if (varName && credentials[varName]) {
45
+ return new BearerToken(credentials[varName]);
46
+ }
47
+ }
48
+ // Pattern: "${TOKEN_NAME}" (raw header value)
49
+ const rawMatch = config.api_key.match(/^\$\{([^}]+)\}$/);
50
+ if (rawMatch) {
51
+ const varName = rawMatch[1];
52
+ if (varName && credentials[varName]) {
53
+ return new ApiKey({
54
+ headerName: config.var_name ?? "X-Api-Key",
55
+ value: credentials[varName],
56
+ });
57
+ }
58
+ }
59
+ }
60
+ if (config.auth_type === "oauth2") {
61
+ // Gateway pre-resolves OAuth2 tokens and passes them as ACCESS_TOKEN
62
+ // e.g. credentials = { SPOTIFY_ACCESS_TOKEN: 'eyJ...' }
63
+ const tokenKey = Object.keys(credentials).find((k) => k.endsWith("_ACCESS_TOKEN") ||
64
+ k.endsWith("_TOKEN"));
65
+ const tokenValue = tokenKey ? credentials[tokenKey] : undefined;
66
+ if (tokenKey && tokenValue) {
67
+ return new BearerToken(tokenValue);
68
+ }
69
+ }
70
+ }
71
+ // Fallback: treat the first credential value as a Bearer token
72
+ const firstValue = Object.values(credentials)[0];
73
+ if (firstValue !== undefined) {
74
+ return new BearerToken(firstValue);
75
+ }
76
+ return undefined;
77
+ }
78
+ /**
79
+ * Extracts the `utdk.auth` array from a provider module's package.json.
80
+ * Returns an empty array if the module does not export a `packageJson`
81
+ * property or if the config is absent.
82
+ */
83
+ export function extractAuthConfigs(providerModule) {
84
+ const pkg = providerModule["packageJson"];
85
+ return pkg?.utdk?.auth ?? [];
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // Operation resolution
89
+ // ---------------------------------------------------------------------------
90
+ /**
91
+ * Resolves a dot-notation operation path on a client object.
92
+ *
93
+ * @example
94
+ * resolveOperation(client, 'users.getByUsername')
95
+ * // → client.users.getByUsername (as a bound function)
96
+ */
97
+ export function resolveOperation(client, operationPath) {
98
+ const segments = operationPath.split(".");
99
+ let current = client;
100
+ for (const segment of segments) {
101
+ if (current === null || current === undefined || typeof current !== "object") {
102
+ throw new TypeError(`Cannot resolve operation path "${operationPath}": "${segment}" is not an object`);
103
+ }
104
+ current = current[segment];
105
+ }
106
+ if (typeof current !== "function") {
107
+ throw new TypeError(`Operation "${operationPath}" resolved to ${typeof current}, expected a function`);
108
+ }
109
+ return current;
110
+ }
111
+ /**
112
+ * Creates a bridge object that is safe to expose inside a vm context.
113
+ *
114
+ * The bridge has a single method, `call(args)`, which invokes the pre-resolved
115
+ * provider operation with the given arguments. Credentials are already baked
116
+ * into the operation via the auth provider — the sandbox script never sees
117
+ * them.
118
+ */
119
+ export function createBridge(options) {
120
+ return {
121
+ async call(args) {
122
+ return options.operation(args);
123
+ },
124
+ };
125
+ }
126
+ // ---------------------------------------------------------------------------
127
+ // Provider loading
128
+ // ---------------------------------------------------------------------------
129
+ /** Name of the factory function pattern in provider modules. */
130
+ const CREATE_CLIENT_PATTERN = /^create[A-Z].+Client$/;
131
+ /**
132
+ * Locates and invokes the `create*Client` factory in a provider module.
133
+ *
134
+ * Provider modules export a `create<Name>Client(options?)` function that
135
+ * returns a `Promise<Client>`. This helper finds it and calls it with the
136
+ * supplied auth provider.
137
+ *
138
+ * @throws if no factory function matching `create*Client` is found.
139
+ */
140
+ export async function loadProviderClient(providerModule, auth) {
141
+ // Find the first create*Client export
142
+ const factoryEntry = Object.entries(providerModule).find(([name, value]) => CREATE_CLIENT_PATTERN.test(name) && typeof value === "function");
143
+ if (!factoryEntry) {
144
+ throw new Error("Provider module does not export a create*Client function. " +
145
+ `Exports found: ${Object.keys(providerModule).join(", ")}`);
146
+ }
147
+ const [, factory] = factoryEntry;
148
+ const client = await factory(auth ? { auth } : {});
149
+ return client;
150
+ }
151
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../src/bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { Isolate } from "./isolate.js";
2
+ export { createSandboxContext } from "./sandbox.js";
3
+ export { createBridge, extractAuthConfigs, loadProviderClient, resolveAuthProvider, resolveOperation, } from "./bridge.js";
4
+ export type { ExecuteOptions, ExecuteResult, TimeoutError, UtdkAuthConfig } from "./types.js";
5
+ export { TimeoutError as IsolateTimeoutError } from "./types.js";
@@ -0,0 +1,5 @@
1
+ export { Isolate } from "./isolate.js";
2
+ export { createSandboxContext } from "./sandbox.js";
3
+ export { createBridge, extractAuthConfigs, loadProviderClient, resolveAuthProvider, resolveOperation, } from "./bridge.js";
4
+ export { TimeoutError as IsolateTimeoutError } from "./types.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,YAAY,IAAI,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Isolate — sandboxed Node.js vm execution runtime for @utdk tool calls.
3
+ *
4
+ * Each call to `execute()` runs in a fresh `vm.createContext()` sandbox:
5
+ * - No access to `process.env`, `fs`, `child_process`, `require`, or any
6
+ * other Node.js host API that could leak credentials or touch the disk.
7
+ * - Credentials are injected by the gateway at call time via a controlled
8
+ * bridge object (`__bridge__`). The sandbox script only calls the bridge;
9
+ * it never receives raw credential strings.
10
+ * - Module cache is isolated per call (new context each time, no
11
+ * cross-call state leakage).
12
+ * - Timeout is enforced via `Promise.race`; runaway async operations are
13
+ * rejected with a `TimeoutError`.
14
+ *
15
+ * For synchronous CPU spin-loops an additional `vm.Script` timeout guard is
16
+ * applied; the async watchdog covers everything else.
17
+ *
18
+ * ## Architecture
19
+ *
20
+ * ```
21
+ * caller
22
+ * │
23
+ * ▼
24
+ * Isolate.execute(options)
25
+ * │
26
+ * ├─ [host] dynamically import provider module
27
+ * ├─ [host] resolve auth provider from credentials + utdk.auth config
28
+ * ├─ [host] create provider client with auth provider
29
+ * ├─ [host] resolve operation function (dot-notation path)
30
+ * ├─ [host] create bridge { call(args) => operationFn(args) }
31
+ * │
32
+ * ├─ [vm] createContext(minimal globals + __bridge__ + __args__)
33
+ * └─ [vm] run `__bridge__.call(__args__)` inside sandbox
34
+ * └─ [host, via bridge] call provider with credentials
35
+ * └─ [network] HTTP request with injected auth headers
36
+ * ```
37
+ *
38
+ * ## ESM / vm.Module note
39
+ *
40
+ * Node.js 20+ supports `vm.SourceTextModule` (ESM) with the
41
+ * `--experimental-vm-modules` flag. The current implementation uses the
42
+ * stable `vm.Script` API for the thin orchestration script, which is
43
+ * sufficient because:
44
+ * - The script is one line (`__bridge__.call(__args__)`).
45
+ * - Full provider code runs outside the sandbox (in host context) behind
46
+ * the bridge abstraction.
47
+ *
48
+ * If future requirements call for running provider code itself inside an ESM
49
+ * module sandbox, upgrade to `vm.SourceTextModule` with a custom linker
50
+ * (see `upgrade notes` in the README).
51
+ */
52
+ import { type ExecuteOptions, type ExecuteResult } from "./types.js";
53
+ /**
54
+ * Sandboxed execution runtime for @utdk tool calls.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const isolate = new Isolate();
59
+ * const result = await isolate.execute({
60
+ * module: '@utdk/github',
61
+ * operation: 'users.getByUsername',
62
+ * args: { username: 'octocat' },
63
+ * credentials: { GITHUB_TOKEN: '<injected-by-gateway>' },
64
+ * timeout: 10_000,
65
+ * });
66
+ * console.log(result.data, `(${result.durationMs}ms)`);
67
+ * ```
68
+ */
69
+ export declare class Isolate {
70
+ /**
71
+ * Executes a single @utdk tool call inside a fresh vm sandbox.
72
+ *
73
+ * Steps:
74
+ * 1. Dynamically import the provider module (host context, cached by Node.js).
75
+ * 2. Resolve an `AuthProvider` from the supplied credentials and the
76
+ * provider's `utdk.auth` config. Credentials are NEVER written to
77
+ * `process.env`.
78
+ * 3. Instantiate the provider client with the auth provider.
79
+ * 4. Resolve the operation function via dot-notation path.
80
+ * 5. Create a bridge and a fresh vm context.
81
+ * 6. Run the sandbox script `__bridge__.call(__args__)` inside the context.
82
+ * 7. Race the execution against the timeout.
83
+ *
84
+ * @throws `TimeoutError` if execution exceeds `options.timeout`.
85
+ * @throws `TypeError` if the operation path does not resolve to a function.
86
+ * @throws `Error` on provider loading or HTTP failures.
87
+ */
88
+ execute(options: ExecuteOptions): Promise<ExecuteResult>;
89
+ /**
90
+ * Runs `SANDBOX_SCRIPT` in the given context and races against a timeout.
91
+ *
92
+ * The synchronous `vm.Script` timeout (`timeout` option in runInContext)
93
+ * guards against infinite synchronous loops. The async watchdog (Promise.race)
94
+ * guards against long-running await chains.
95
+ */
96
+ private _runWithTimeout;
97
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Isolate — sandboxed Node.js vm execution runtime for @utdk tool calls.
3
+ *
4
+ * Each call to `execute()` runs in a fresh `vm.createContext()` sandbox:
5
+ * - No access to `process.env`, `fs`, `child_process`, `require`, or any
6
+ * other Node.js host API that could leak credentials or touch the disk.
7
+ * - Credentials are injected by the gateway at call time via a controlled
8
+ * bridge object (`__bridge__`). The sandbox script only calls the bridge;
9
+ * it never receives raw credential strings.
10
+ * - Module cache is isolated per call (new context each time, no
11
+ * cross-call state leakage).
12
+ * - Timeout is enforced via `Promise.race`; runaway async operations are
13
+ * rejected with a `TimeoutError`.
14
+ *
15
+ * For synchronous CPU spin-loops an additional `vm.Script` timeout guard is
16
+ * applied; the async watchdog covers everything else.
17
+ *
18
+ * ## Architecture
19
+ *
20
+ * ```
21
+ * caller
22
+ * │
23
+ * ▼
24
+ * Isolate.execute(options)
25
+ * │
26
+ * ├─ [host] dynamically import provider module
27
+ * ├─ [host] resolve auth provider from credentials + utdk.auth config
28
+ * ├─ [host] create provider client with auth provider
29
+ * ├─ [host] resolve operation function (dot-notation path)
30
+ * ├─ [host] create bridge { call(args) => operationFn(args) }
31
+ * │
32
+ * ├─ [vm] createContext(minimal globals + __bridge__ + __args__)
33
+ * └─ [vm] run `__bridge__.call(__args__)` inside sandbox
34
+ * └─ [host, via bridge] call provider with credentials
35
+ * └─ [network] HTTP request with injected auth headers
36
+ * ```
37
+ *
38
+ * ## ESM / vm.Module note
39
+ *
40
+ * Node.js 20+ supports `vm.SourceTextModule` (ESM) with the
41
+ * `--experimental-vm-modules` flag. The current implementation uses the
42
+ * stable `vm.Script` API for the thin orchestration script, which is
43
+ * sufficient because:
44
+ * - The script is one line (`__bridge__.call(__args__)`).
45
+ * - Full provider code runs outside the sandbox (in host context) behind
46
+ * the bridge abstraction.
47
+ *
48
+ * If future requirements call for running provider code itself inside an ESM
49
+ * module sandbox, upgrade to `vm.SourceTextModule` with a custom linker
50
+ * (see `upgrade notes` in the README).
51
+ */
52
+ import vm from "node:vm";
53
+ import { createBridge, extractAuthConfigs, loadProviderClient, resolveAuthProvider, resolveOperation, } from "./bridge.js";
54
+ import { createSandboxContext } from "./sandbox.js";
55
+ import { TimeoutError } from "./types.js";
56
+ /** Script executed inside the sandbox for every tool call. */
57
+ const SANDBOX_SCRIPT = new vm.Script("__bridge__.call(__args__)");
58
+ /**
59
+ * Sandboxed execution runtime for @utdk tool calls.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const isolate = new Isolate();
64
+ * const result = await isolate.execute({
65
+ * module: '@utdk/github',
66
+ * operation: 'users.getByUsername',
67
+ * args: { username: 'octocat' },
68
+ * credentials: { GITHUB_TOKEN: '<injected-by-gateway>' },
69
+ * timeout: 10_000,
70
+ * });
71
+ * console.log(result.data, `(${result.durationMs}ms)`);
72
+ * ```
73
+ */
74
+ export class Isolate {
75
+ /**
76
+ * Executes a single @utdk tool call inside a fresh vm sandbox.
77
+ *
78
+ * Steps:
79
+ * 1. Dynamically import the provider module (host context, cached by Node.js).
80
+ * 2. Resolve an `AuthProvider` from the supplied credentials and the
81
+ * provider's `utdk.auth` config. Credentials are NEVER written to
82
+ * `process.env`.
83
+ * 3. Instantiate the provider client with the auth provider.
84
+ * 4. Resolve the operation function via dot-notation path.
85
+ * 5. Create a bridge and a fresh vm context.
86
+ * 6. Run the sandbox script `__bridge__.call(__args__)` inside the context.
87
+ * 7. Race the execution against the timeout.
88
+ *
89
+ * @throws `TimeoutError` if execution exceeds `options.timeout`.
90
+ * @throws `TypeError` if the operation path does not resolve to a function.
91
+ * @throws `Error` on provider loading or HTTP failures.
92
+ */
93
+ async execute(options) {
94
+ const { module: moduleName, operation, args = {}, credentials = {}, timeout = 30_000, } = options;
95
+ const startMs = performance.now();
96
+ // ------------------------------------------------------------------
97
+ // Step 1: Load the provider module in HOST context
98
+ // ------------------------------------------------------------------
99
+ // Dynamic import is Node.js-cached so repeated calls to the same
100
+ // provider do not re-parse the module. The provider runs in host
101
+ // context (not in the sandbox) — it is trusted code.
102
+ const providerModule = (await import(moduleName));
103
+ // ------------------------------------------------------------------
104
+ // Step 2: Resolve auth provider from credentials
105
+ // ------------------------------------------------------------------
106
+ const authConfigs = extractAuthConfigs(providerModule);
107
+ const auth = resolveAuthProvider(credentials, authConfigs);
108
+ // ------------------------------------------------------------------
109
+ // Step 3: Create the provider client with credential injection
110
+ // ------------------------------------------------------------------
111
+ const client = await loadProviderClient(providerModule, auth);
112
+ // ------------------------------------------------------------------
113
+ // Step 4: Resolve the operation function
114
+ // ------------------------------------------------------------------
115
+ const operationFn = resolveOperation(client, operation);
116
+ // ------------------------------------------------------------------
117
+ // Step 5: Create the credential bridge and sandboxed vm context
118
+ // ------------------------------------------------------------------
119
+ // The bridge is created in host context and passed into the sandbox.
120
+ // The sandbox script can only call bridge.call(args) — it cannot
121
+ // reach process.env, require, fs, or any host API.
122
+ const bridge = createBridge({ operation: operationFn });
123
+ const context = createSandboxContext({
124
+ extraGlobals: {
125
+ __bridge__: bridge,
126
+ __args__: args,
127
+ },
128
+ });
129
+ // ------------------------------------------------------------------
130
+ // Step 6 + 7: Run inside sandbox, race against timeout
131
+ // ------------------------------------------------------------------
132
+ const data = await this._runWithTimeout(context, timeout);
133
+ const durationMs = performance.now() - startMs;
134
+ return { data, durationMs };
135
+ }
136
+ /**
137
+ * Runs `SANDBOX_SCRIPT` in the given context and races against a timeout.
138
+ *
139
+ * The synchronous `vm.Script` timeout (`timeout` option in runInContext)
140
+ * guards against infinite synchronous loops. The async watchdog (Promise.race)
141
+ * guards against long-running await chains.
142
+ */
143
+ _runWithTimeout(context, timeoutMs) {
144
+ // The synchronous portion of the script (if any) is bounded by the vm timeout.
145
+ // For the async portion we use Promise.race.
146
+ let resultPromise;
147
+ try {
148
+ // runInContext returns whatever the script evaluates to.
149
+ // For our one-liner that calls an async bridge method, this is a Promise.
150
+ const scriptResult = SANDBOX_SCRIPT.runInContext(context, {
151
+ // Guard against synchronous infinite loops / busy spins.
152
+ // This only applies to the synchronous execution phase; async
153
+ // continuations are handled by the Promise.race below.
154
+ timeout: timeoutMs,
155
+ });
156
+ resultPromise = Promise.resolve(scriptResult);
157
+ }
158
+ catch (err) {
159
+ return Promise.reject(err);
160
+ }
161
+ // Async watchdog
162
+ const timeoutPromise = new Promise((_, reject) => {
163
+ const timer = setTimeout(() => {
164
+ reject(new TimeoutError(timeoutMs));
165
+ }, timeoutMs);
166
+ // Don't keep the process alive if only the timer is pending
167
+ if (typeof timer.unref === "function") {
168
+ timer.unref();
169
+ }
170
+ });
171
+ return Promise.race([resultPromise, timeoutPromise]);
172
+ }
173
+ }
174
+ //# sourceMappingURL=isolate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isolate.js","sourceRoot":"","sources":["../../src/isolate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Sandbox context creation for the Isolate runtime.
3
+ *
4
+ * A sandboxed vm context is created per tool call. The context provides a
5
+ * minimal set of safe globals and explicitly excludes `process`, `require`,
6
+ * `fs`, `child_process`, and other Node.js host APIs that could leak
7
+ * environment variables or allow filesystem/subprocess access.
8
+ *
9
+ * The credential bridge (`__bridge__`) and call arguments (`__args__`) are
10
+ * the only channels through which external state enters the sandbox.
11
+ */
12
+ import vm from "node:vm";
13
+ export interface SandboxOptions {
14
+ /**
15
+ * Additional globals to expose inside the vm context.
16
+ * Use sparingly; every addition is a potential escape hatch.
17
+ */
18
+ extraGlobals?: Record<string, unknown>;
19
+ }
20
+ /**
21
+ * Creates a fresh, restricted vm context.
22
+ *
23
+ * Accessible globals:
24
+ * console, JSON, Math, Date, Promise, Array, Object, String, Number,
25
+ * Boolean, Error, TypeError, RangeError, Map, Set, WeakMap, WeakSet,
26
+ * Symbol, typed arrays, ArrayBuffer, URL, URLSearchParams,
27
+ * TextEncoder, TextDecoder, setTimeout, clearTimeout, setInterval,
28
+ * clearInterval, parseInt, parseFloat, isNaN, isFinite, encodeURI,
29
+ * decodeURI, encodeURIComponent, decodeURIComponent, Infinity, NaN,
30
+ * undefined.
31
+ *
32
+ * Deliberately excluded:
33
+ * process, require, global, Buffer, __dirname, __filename,
34
+ * fs, child_process, net, http, https, os, crypto (node:crypto),
35
+ * and any other Node.js built-in that exposes host resources.
36
+ *
37
+ * @param options - Optional extra globals to inject.
38
+ * @returns A contextified vm sandbox object.
39
+ */
40
+ export declare function createSandboxContext(options?: SandboxOptions): vm.Context;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Sandbox context creation for the Isolate runtime.
3
+ *
4
+ * A sandboxed vm context is created per tool call. The context provides a
5
+ * minimal set of safe globals and explicitly excludes `process`, `require`,
6
+ * `fs`, `child_process`, and other Node.js host APIs that could leak
7
+ * environment variables or allow filesystem/subprocess access.
8
+ *
9
+ * The credential bridge (`__bridge__`) and call arguments (`__args__`) are
10
+ * the only channels through which external state enters the sandbox.
11
+ */
12
+ import vm from "node:vm";
13
+ const PREFIXED_METHODS = ["log", "error", "warn", "info", "debug"];
14
+ function createPrefixedConsole(prefix) {
15
+ const console_ = console;
16
+ return Object.fromEntries(PREFIXED_METHODS.map((method) => [method, (...args) => console_[method](prefix, ...args)]));
17
+ }
18
+ const sandboxConsole = createPrefixedConsole("[sandbox]");
19
+ /**
20
+ * Creates a fresh, restricted vm context.
21
+ *
22
+ * Accessible globals:
23
+ * console, JSON, Math, Date, Promise, Array, Object, String, Number,
24
+ * Boolean, Error, TypeError, RangeError, Map, Set, WeakMap, WeakSet,
25
+ * Symbol, typed arrays, ArrayBuffer, URL, URLSearchParams,
26
+ * TextEncoder, TextDecoder, setTimeout, clearTimeout, setInterval,
27
+ * clearInterval, parseInt, parseFloat, isNaN, isFinite, encodeURI,
28
+ * decodeURI, encodeURIComponent, decodeURIComponent, Infinity, NaN,
29
+ * undefined.
30
+ *
31
+ * Deliberately excluded:
32
+ * process, require, global, Buffer, __dirname, __filename,
33
+ * fs, child_process, net, http, https, os, crypto (node:crypto),
34
+ * and any other Node.js built-in that exposes host resources.
35
+ *
36
+ * @param options - Optional extra globals to inject.
37
+ * @returns A contextified vm sandbox object.
38
+ */
39
+ export function createSandboxContext(options = {}) {
40
+ const sandbox = {
41
+ // Safe builtins
42
+ console: sandboxConsole,
43
+ JSON,
44
+ Math,
45
+ Date,
46
+ Promise,
47
+ Array,
48
+ Object,
49
+ String,
50
+ Number,
51
+ Boolean,
52
+ Error,
53
+ EvalError,
54
+ RangeError,
55
+ ReferenceError,
56
+ SyntaxError,
57
+ TypeError,
58
+ URIError,
59
+ Map,
60
+ Set,
61
+ WeakMap,
62
+ WeakSet,
63
+ WeakRef,
64
+ Symbol,
65
+ BigInt,
66
+ // Typed arrays
67
+ ArrayBuffer,
68
+ SharedArrayBuffer,
69
+ DataView,
70
+ Int8Array,
71
+ Uint8Array,
72
+ Uint8ClampedArray,
73
+ Int16Array,
74
+ Uint16Array,
75
+ Int32Array,
76
+ Uint32Array,
77
+ Float32Array,
78
+ Float64Array,
79
+ BigInt64Array,
80
+ BigUint64Array,
81
+ // Web-compatible globals
82
+ URL,
83
+ URLSearchParams,
84
+ TextEncoder,
85
+ TextDecoder,
86
+ // Timers (these are host-side and safe to expose)
87
+ setTimeout,
88
+ clearTimeout,
89
+ setInterval,
90
+ clearInterval,
91
+ // Standard globals
92
+ parseInt,
93
+ parseFloat,
94
+ isNaN,
95
+ isFinite,
96
+ encodeURI,
97
+ decodeURI,
98
+ encodeURIComponent,
99
+ decodeURIComponent,
100
+ Infinity,
101
+ NaN,
102
+ undefined,
103
+ // Explicitly do NOT add:
104
+ // process — would expose process.env (credential leak)
105
+ // require — would allow arbitrary module imports
106
+ // global — alias for Node.js global; exposes process
107
+ // Buffer — not needed in sandboxed operations
108
+ // __dirname — filesystem information leak
109
+ // __filename — filesystem information leak
110
+ // fetch — injected separately per-operation via bridge (see bridge.ts)
111
+ // crypto — would allow subtle attacks; excluded for now
112
+ ...options.extraGlobals,
113
+ };
114
+ return vm.createContext(sandbox);
115
+ }
116
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AAIzB,MAAM,gBAAgB,GAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpF,SAAS,qBAAqB,CAAC,MAAc;IAC3C,MAAM,QAAQ,GAAiC,OAAO,CAAC;IACvD,OAAO,MAAM,CAAC,WAAW,CACvB,gBAAgB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAC/C,CAAC;AAC3D,CAAC;AAED,MAAM,cAAc,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;AAU1D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAA0B,EAAE;IAC/D,MAAM,OAAO,GAA4B;QACvC,gBAAgB;QAChB,OAAO,EAAE,cAAc;QACvB,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,OAAO;QACP,KAAK;QACL,MAAM;QACN,MAAM;QACN,MAAM;QACN,OAAO;QACP,KAAK;QACL,SAAS;QACT,UAAU;QACV,cAAc;QACd,WAAW;QACX,SAAS;QACT,QAAQ;QACR,GAAG;QACH,GAAG;QACH,OAAO;QACP,OAAO;QACP,OAAO;QACP,MAAM;QACN,MAAM;QAEN,eAAe;QACf,WAAW;QACX,iBAAiB;QACjB,QAAQ;QACR,SAAS;QACT,UAAU;QACV,iBAAiB;QACjB,UAAU;QACV,WAAW;QACX,UAAU;QACV,WAAW;QACX,YAAY;QACZ,YAAY;QACZ,aAAa;QACb,cAAc;QAEd,yBAAyB;QACzB,GAAG;QACH,eAAe;QACf,WAAW;QACX,WAAW;QAEX,kDAAkD;QAClD,UAAU;QACV,YAAY;QACZ,WAAW;QACX,aAAa;QAEb,mBAAmB;QACnB,QAAQ;QACR,UAAU;QACV,KAAK;QACL,QAAQ;QACR,SAAS;QACT,SAAS;QACT,kBAAkB;QAClB,kBAAkB;QAClB,QAAQ;QACR,GAAG;QACH,SAAS;QAET,yBAAyB;QACzB,gEAAgE;QAChE,0DAA0D;QAC1D,+DAA+D;QAC/D,wDAAwD;QACxD,iDAAiD;QACjD,iDAAiD;QACjD,kFAAkF;QAClF,kEAAkE;QAElE,GAAG,OAAO,CAAC,YAAY;KACxB,CAAC;IAEF,OAAO,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;AACnC,CAAC"}