@yarlisai/sandbox 0.1.0-alpha.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.
- package/LICENSE +15 -0
- package/README.md +138 -0
- package/dist/adapters/memory.d.ts +12 -0
- package/dist/adapters/memory.d.ts.map +1 -0
- package/dist/adapters/memory.js +36 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/node-worker.d.ts +24 -0
- package/dist/adapters/node-worker.d.ts.map +1 -0
- package/dist/adapters/node-worker.js +304 -0
- package/dist/adapters/node-worker.js.map +1 -0
- package/dist/adapters/worker-source.d.ts +27 -0
- package/dist/adapters/worker-source.d.ts.map +1 -0
- package/dist/adapters/worker-source.js +231 -0
- package/dist/adapters/worker-source.js.map +1 -0
- package/dist/client.d.ts +35 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +46 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +132 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +26 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 YarlisAI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# @yarlisai/sandbox
|
|
2
|
+
|
|
3
|
+
Pluggable sandbox runner for executing untrusted user JavaScript. Built on a **Port/Adapter** pattern (ADR 0007). Swap between a `worker_threads` pool, an in-process eval, or any custom transport by changing one line.
|
|
4
|
+
|
|
5
|
+
## Port signature
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
export interface SandboxRunner {
|
|
9
|
+
readonly name: string
|
|
10
|
+
execute(args: SandboxExecuteArgs): Promise<SandboxExecuteResult>
|
|
11
|
+
dispose(): Promise<void>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SandboxClientConfig {
|
|
15
|
+
adapter: SandboxRunner
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`execute()` runs ONE piece of user code and resolves with its return value. `dispose()` tears the runner down (drain pool / close handles).
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun add @yarlisai/sandbox@alpha
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The default `node-worker` adapter relies on Node's built-in `worker_threads` and `vm` — no extra runtime deps.
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Your app ──► SandboxClient ──► SandboxRunner (port) ──► [node-worker | memory | custom] (adapter)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- **`SandboxRunner`** — the port: `{ name, execute(args), dispose() }`
|
|
36
|
+
- **`createSandboxClient({ adapter })`** — thin façade so callers don't reach into the adapter directly
|
|
37
|
+
- **Adapters** — each implements `SandboxRunner`
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { createSandboxClient, nodeWorkerAdapter } from '@yarlisai/sandbox'
|
|
43
|
+
|
|
44
|
+
// Production: pool of worker_threads, vm.Script + wall-clock kill switch.
|
|
45
|
+
const sandbox = createSandboxClient({
|
|
46
|
+
adapter: nodeWorkerAdapter({ maxWorkers: 4, workerMemoryMb: 256 }),
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const { result } = await sandbox.execute({
|
|
50
|
+
code: 'return params.a + params.b',
|
|
51
|
+
executionParams: { a: 1, b: 2 },
|
|
52
|
+
envVars: {},
|
|
53
|
+
contextVariables: {},
|
|
54
|
+
isCustomTool: false,
|
|
55
|
+
timeout: 5_000,
|
|
56
|
+
secureFetchImpl: (url, init) => fetch(url, init as RequestInit),
|
|
57
|
+
consoleSink: {
|
|
58
|
+
log: (m) => console.log(m),
|
|
59
|
+
info: (m) => console.info(m),
|
|
60
|
+
warn: (m) => console.warn(m),
|
|
61
|
+
error: (m) => console.error(m),
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Graceful shutdown.
|
|
66
|
+
await sandbox.dispose()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Message protocol
|
|
70
|
+
|
|
71
|
+
The `node-worker` adapter speaks the following protocol over `postMessage`:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
main → worker
|
|
75
|
+
{ type: 'execute', execId, code, contextVariables, executionParams,
|
|
76
|
+
envVars, isCustomTool, syncTimeout }
|
|
77
|
+
{ type: 'fetch_response', id, ok, status, statusText, headers, body, error? }
|
|
78
|
+
|
|
79
|
+
worker → main
|
|
80
|
+
{ type: 'fetch', id, url, init } // proxied back to secureFetchImpl
|
|
81
|
+
{ type: 'console', level, message } // forwarded to consoleSink
|
|
82
|
+
{ type: 'result', execId, value }
|
|
83
|
+
{ type: 'error', execId, error: { name, message, stack } }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Both message types are exported as `SandboxMainToWorkerMessage` and `SandboxWorkerToMainMessage` for adapter authors who want to reuse the same worker source. The raw worker source is also exported as `WORKER_SOURCE`.
|
|
87
|
+
|
|
88
|
+
## Security caveats
|
|
89
|
+
|
|
90
|
+
- `node-worker` is **isolation, not sandboxing**: a `worker_threads` Worker shares the host filesystem, native crypto, and (without `resourceLimits`) memory. Code that bypasses `vm.runInContext` (e.g. by triggering an unhandled rejection deeper than the script) can in principle reach those surfaces. Combine with OS-level sandboxing if you accept code from anonymous users.
|
|
91
|
+
- All `fetch()` calls inside the sandbox proxy back to `secureFetchImpl` on the main thread. **You must SSRF-validate every URL there** — the worker has no network restriction of its own.
|
|
92
|
+
- The wall-clock timeout is enforced by `worker.terminate()` on the main thread. The `vm.Script` `timeout` option is a second kill switch; do not rely on it alone (microtask-starvation loops can outlast it).
|
|
93
|
+
- The `memory` adapter has **no isolation**. It runs user code in the same process via `new Function()` for unit tests / dev loops only. Never accept untrusted input through it.
|
|
94
|
+
|
|
95
|
+
## Writing a custom adapter
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import type { SandboxRunner, SandboxExecuteArgs } from '@yarlisai/sandbox'
|
|
99
|
+
|
|
100
|
+
export function myAdapter(): SandboxRunner {
|
|
101
|
+
return {
|
|
102
|
+
name: 'my-adapter',
|
|
103
|
+
async execute(args: SandboxExecuteArgs) {
|
|
104
|
+
// call your runtime (firecracker, gvisor, e2b, ...)
|
|
105
|
+
return { result: someValue }
|
|
106
|
+
},
|
|
107
|
+
async dispose() {
|
|
108
|
+
// optional teardown
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Drop it into `createSandboxClient({ adapter: myAdapter() })` — done.
|
|
115
|
+
|
|
116
|
+
## Built-in adapters
|
|
117
|
+
|
|
118
|
+
| Adapter | Factory | Transport | Use for |
|
|
119
|
+
|---|---|---|---|
|
|
120
|
+
| Node Worker | `nodeWorkerAdapter` | `worker_threads` pool + `vm.Script` | production |
|
|
121
|
+
| Memory | `memoryAdapter` | `new Function(code)` in same process | unit tests, dev |
|
|
122
|
+
|
|
123
|
+
## Build
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
bun install
|
|
127
|
+
cd packages/sandbox
|
|
128
|
+
bun run build
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Related
|
|
132
|
+
|
|
133
|
+
- [`@yarlisai/core`](../core/README.md) — logger / env helpers used by sibling packages.
|
|
134
|
+
- [ADR 0007 — Port/Adapter protocol](../../docs/architecture/decisions/0007-yarlisai-port-adapter.md) — the rules every service package follows.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory adapter for the SandboxRunner port.
|
|
3
|
+
*
|
|
4
|
+
* Runs user code in the SAME process via `new Function()`. There is **no
|
|
5
|
+
* isolation, no resource limit, no timeout enforcement, and no SSRF gate**.
|
|
6
|
+
* Use only in unit tests / dev loops where you control the input code.
|
|
7
|
+
*
|
|
8
|
+
* Production callers should use `nodeWorkerAdapter`.
|
|
9
|
+
*/
|
|
10
|
+
import type { SandboxRunner } from '../types';
|
|
11
|
+
export declare function memoryAdapter(): SandboxRunner;
|
|
12
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAA4C,aAAa,EAAE,MAAM,UAAU,CAAA;AAEvF,wBAAgB,aAAa,IAAI,aAAa,CA+B7C"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory adapter for the SandboxRunner port.
|
|
3
|
+
*
|
|
4
|
+
* Runs user code in the SAME process via `new Function()`. There is **no
|
|
5
|
+
* isolation, no resource limit, no timeout enforcement, and no SSRF gate**.
|
|
6
|
+
* Use only in unit tests / dev loops where you control the input code.
|
|
7
|
+
*
|
|
8
|
+
* Production callers should use `nodeWorkerAdapter`.
|
|
9
|
+
*/
|
|
10
|
+
export function memoryAdapter() {
|
|
11
|
+
return {
|
|
12
|
+
name: 'memory',
|
|
13
|
+
async execute(args) {
|
|
14
|
+
const { code, executionParams, envVars, contextVariables, secureFetchImpl, consoleSink } = args;
|
|
15
|
+
// Build a flat ctx object. Keys collide last-write-wins, mirroring the
|
|
16
|
+
// node-worker spread order: params, envVars, contextVariables, fetch, console.
|
|
17
|
+
const ctx = {
|
|
18
|
+
params: executionParams ?? {},
|
|
19
|
+
environmentVariables: envVars ?? {},
|
|
20
|
+
...(contextVariables ?? {}),
|
|
21
|
+
fetch: secureFetchImpl,
|
|
22
|
+
console: consoleSink,
|
|
23
|
+
};
|
|
24
|
+
// Wrap in async IIFE so user code can `await` and `return`.
|
|
25
|
+
const wrapped = `return (async () => { ${code} })()`;
|
|
26
|
+
// eslint-disable-next-line no-new-func
|
|
27
|
+
const fn = new Function('ctx', `with (ctx) { ${wrapped} }`);
|
|
28
|
+
const value = await fn(ctx);
|
|
29
|
+
return { result: value };
|
|
30
|
+
},
|
|
31
|
+
async dispose() {
|
|
32
|
+
// Nothing to tear down.
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,KAAK,CAAC,OAAO,CAAC,IAAwB;YACpC,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,WAAW,EAAE,GACtF,IAAI,CAAA;YAEN,uEAAuE;YACvE,+EAA+E;YAC/E,MAAM,GAAG,GAA4B;gBACnC,MAAM,EAAE,eAAe,IAAI,EAAE;gBAC7B,oBAAoB,EAAE,OAAO,IAAI,EAAE;gBACnC,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC;gBAC3B,KAAK,EAAE,eAAe;gBACtB,OAAO,EAAE,WAAW;aACrB,CAAA;YAED,4DAA4D;YAC5D,MAAM,OAAO,GAAG,yBAAyB,IAAI,OAAO,CAAA;YACpD,uCAAuC;YACvC,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,KAAK,EAAE,gBAAgB,OAAO,IAAI,CAErC,CAAA;YAErB,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,CAAA;YAC3B,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;QAC1B,CAAC;QACD,KAAK,CAAC,OAAO;YACX,wBAAwB;QAC1B,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node `worker_threads` adapter for the SandboxRunner port.
|
|
3
|
+
*
|
|
4
|
+
* Architecture:
|
|
5
|
+
* - A small pool (default 4) of reusable Workers spawned with `{ eval: true }`
|
|
6
|
+
* pointing at WORKER_SOURCE.
|
|
7
|
+
* - One execution per worker at a time. Concurrent calls queue.
|
|
8
|
+
* - Wall-clock timeout enforced by `worker.terminate()` on the main thread.
|
|
9
|
+
* A second `vm.Script` timeout inside the worker is a belt-and-braces.
|
|
10
|
+
* - All `fetch()` calls inside the sandbox proxy back to the caller-supplied
|
|
11
|
+
* `secureFetchImpl` over postMessage so SSRF / authn checks can run on the
|
|
12
|
+
* main thread.
|
|
13
|
+
*
|
|
14
|
+
* No external deps — `worker_threads` and `vm` are built into Node.
|
|
15
|
+
*/
|
|
16
|
+
import type { NodeWorkerAdapterConfig, SandboxRunner } from '../types';
|
|
17
|
+
/**
|
|
18
|
+
* Construct a worker-pool-backed `SandboxRunner`.
|
|
19
|
+
*
|
|
20
|
+
* The returned runner owns its own pool. Call `dispose()` to drain it (e.g.
|
|
21
|
+
* between integration tests or on graceful shutdown).
|
|
22
|
+
*/
|
|
23
|
+
export declare function nodeWorkerAdapter(config?: NodeWorkerAdapterConfig): SandboxRunner;
|
|
24
|
+
//# sourceMappingURL=node-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-worker.d.ts","sourceRoot":"","sources":["../../src/adapters/node-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAEV,uBAAuB,EAGvB,aAAa,EAEd,MAAM,UAAU,CAAA;AAmHjB;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,uBAA4B,GAAG,aAAa,CAkJrF"}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node `worker_threads` adapter for the SandboxRunner port.
|
|
3
|
+
*
|
|
4
|
+
* Architecture:
|
|
5
|
+
* - A small pool (default 4) of reusable Workers spawned with `{ eval: true }`
|
|
6
|
+
* pointing at WORKER_SOURCE.
|
|
7
|
+
* - One execution per worker at a time. Concurrent calls queue.
|
|
8
|
+
* - Wall-clock timeout enforced by `worker.terminate()` on the main thread.
|
|
9
|
+
* A second `vm.Script` timeout inside the worker is a belt-and-braces.
|
|
10
|
+
* - All `fetch()` calls inside the sandbox proxy back to the caller-supplied
|
|
11
|
+
* `secureFetchImpl` over postMessage so SSRF / authn checks can run on the
|
|
12
|
+
* main thread.
|
|
13
|
+
*
|
|
14
|
+
* No external deps — `worker_threads` and `vm` are built into Node.
|
|
15
|
+
*/
|
|
16
|
+
import { Worker } from 'worker_threads';
|
|
17
|
+
import { SandboxTimeoutError } from '../types';
|
|
18
|
+
import { WORKER_SOURCE } from './worker-source';
|
|
19
|
+
const DEFAULT_MAX_WORKERS = 4;
|
|
20
|
+
const DEFAULT_WORKER_MEMORY_MB = 256;
|
|
21
|
+
class WorkerPool {
|
|
22
|
+
maxWorkers;
|
|
23
|
+
workerMemoryMb;
|
|
24
|
+
workers = [];
|
|
25
|
+
waiters = [];
|
|
26
|
+
constructor(maxWorkers, workerMemoryMb) {
|
|
27
|
+
this.maxWorkers = maxWorkers;
|
|
28
|
+
this.workerMemoryMb = workerMemoryMb;
|
|
29
|
+
}
|
|
30
|
+
size() {
|
|
31
|
+
return this.workers.length;
|
|
32
|
+
}
|
|
33
|
+
acquire() {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const idle = this.workers.find((w) => !w.busy);
|
|
36
|
+
if (idle) {
|
|
37
|
+
idle.busy = true;
|
|
38
|
+
resolve(idle);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (this.workers.length < this.maxWorkers) {
|
|
42
|
+
try {
|
|
43
|
+
const pooled = this.spawn();
|
|
44
|
+
pooled.busy = true;
|
|
45
|
+
resolve(pooled);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
reject(err);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.waiters.push((pooled) => {
|
|
53
|
+
pooled.busy = true;
|
|
54
|
+
resolve(pooled);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
release(pooled) {
|
|
59
|
+
pooled.busy = false;
|
|
60
|
+
const next = this.waiters.shift();
|
|
61
|
+
if (next) {
|
|
62
|
+
next(pooled);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
retire(pooled) {
|
|
66
|
+
const idx = this.workers.indexOf(pooled);
|
|
67
|
+
if (idx >= 0)
|
|
68
|
+
this.workers.splice(idx, 1);
|
|
69
|
+
if (this.waiters.length > 0 && this.workers.length < this.maxWorkers) {
|
|
70
|
+
try {
|
|
71
|
+
const pooledNew = this.spawn();
|
|
72
|
+
const waiter = this.waiters.shift();
|
|
73
|
+
if (waiter) {
|
|
74
|
+
pooledNew.busy = true;
|
|
75
|
+
waiter(pooledNew);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Best-effort. Waiters reject on their own timeout in the caller.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async drain() {
|
|
84
|
+
const workers = this.workers.slice();
|
|
85
|
+
this.workers = [];
|
|
86
|
+
this.waiters = [];
|
|
87
|
+
await Promise.all(workers.map((p) => p.worker.terminate().catch(() => {
|
|
88
|
+
/* swallow */
|
|
89
|
+
})));
|
|
90
|
+
}
|
|
91
|
+
spawn() {
|
|
92
|
+
const worker = new Worker(WORKER_SOURCE, {
|
|
93
|
+
eval: true,
|
|
94
|
+
resourceLimits: {
|
|
95
|
+
maxOldGenerationSizeMb: this.workerMemoryMb,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
const pooled = { worker, busy: false };
|
|
99
|
+
this.workers.push(pooled);
|
|
100
|
+
worker.once('exit', () => {
|
|
101
|
+
this.retire(pooled);
|
|
102
|
+
});
|
|
103
|
+
worker.on('error', () => {
|
|
104
|
+
this.retire(pooled);
|
|
105
|
+
});
|
|
106
|
+
return pooled;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Construct a worker-pool-backed `SandboxRunner`.
|
|
111
|
+
*
|
|
112
|
+
* The returned runner owns its own pool. Call `dispose()` to drain it (e.g.
|
|
113
|
+
* between integration tests or on graceful shutdown).
|
|
114
|
+
*/
|
|
115
|
+
export function nodeWorkerAdapter(config = {}) {
|
|
116
|
+
const pool = new WorkerPool(config.maxWorkers ?? DEFAULT_MAX_WORKERS, config.workerMemoryMb ?? DEFAULT_WORKER_MEMORY_MB);
|
|
117
|
+
let nextExecId = 1;
|
|
118
|
+
return {
|
|
119
|
+
name: 'node-worker',
|
|
120
|
+
async execute(args) {
|
|
121
|
+
const { code, executionParams, envVars, contextVariables, isCustomTool, timeout, secureFetchImpl, consoleSink, } = args;
|
|
122
|
+
const pooled = await pool.acquire();
|
|
123
|
+
const worker = pooled.worker;
|
|
124
|
+
const execId = nextExecId++;
|
|
125
|
+
let timer = null;
|
|
126
|
+
let settled = false;
|
|
127
|
+
let terminated = false;
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
const cleanup = () => {
|
|
130
|
+
if (timer) {
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
timer = null;
|
|
133
|
+
}
|
|
134
|
+
worker.off('message', onMessage);
|
|
135
|
+
worker.off('error', onError);
|
|
136
|
+
worker.off('exit', onExit);
|
|
137
|
+
};
|
|
138
|
+
const settleSuccess = (value) => {
|
|
139
|
+
if (settled)
|
|
140
|
+
return;
|
|
141
|
+
settled = true;
|
|
142
|
+
cleanup();
|
|
143
|
+
pool.release(pooled);
|
|
144
|
+
resolve({ result: value });
|
|
145
|
+
};
|
|
146
|
+
const settleFailure = (err, kill) => {
|
|
147
|
+
if (settled)
|
|
148
|
+
return;
|
|
149
|
+
settled = true;
|
|
150
|
+
cleanup();
|
|
151
|
+
if (kill) {
|
|
152
|
+
terminated = true;
|
|
153
|
+
worker.terminate().catch(() => {
|
|
154
|
+
/* already gone */
|
|
155
|
+
});
|
|
156
|
+
pool.retire(pooled);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
pool.release(pooled);
|
|
160
|
+
}
|
|
161
|
+
reject(err);
|
|
162
|
+
};
|
|
163
|
+
const onMessage = (msg) => {
|
|
164
|
+
if (!msg || typeof msg !== 'object')
|
|
165
|
+
return;
|
|
166
|
+
switch (msg.type) {
|
|
167
|
+
case 'console': {
|
|
168
|
+
const level = msg.level;
|
|
169
|
+
const sink = consoleSink[level] ?? consoleSink.log;
|
|
170
|
+
try {
|
|
171
|
+
sink(String(msg.message ?? ''));
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
/* sink failures must not break execution */
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
case 'fetch': {
|
|
179
|
+
handleFetchRpc(worker, msg, secureFetchImpl);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
case 'result': {
|
|
183
|
+
if (msg.execId !== execId)
|
|
184
|
+
return;
|
|
185
|
+
settleSuccess(msg.value);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
case 'error': {
|
|
189
|
+
if (msg.execId !== execId)
|
|
190
|
+
return;
|
|
191
|
+
const e = new Error(msg.error?.message ?? 'Function execution failed');
|
|
192
|
+
if (msg.error?.name)
|
|
193
|
+
e.name = msg.error.name;
|
|
194
|
+
if (msg.error?.stack)
|
|
195
|
+
e.stack = msg.error.stack;
|
|
196
|
+
settleFailure(e, false);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const onError = (err) => {
|
|
202
|
+
settleFailure(err, true);
|
|
203
|
+
};
|
|
204
|
+
const onExit = (_code) => {
|
|
205
|
+
if (settled)
|
|
206
|
+
return;
|
|
207
|
+
// Worker died unexpectedly. If we initiated termination, `settled`
|
|
208
|
+
// is normally already true by the time this fires.
|
|
209
|
+
const reason = terminated
|
|
210
|
+
? new SandboxTimeoutError(timeout)
|
|
211
|
+
: new Error('Function worker exited unexpectedly');
|
|
212
|
+
settleFailure(reason, false);
|
|
213
|
+
};
|
|
214
|
+
worker.on('message', onMessage);
|
|
215
|
+
worker.on('error', onError);
|
|
216
|
+
worker.on('exit', onExit);
|
|
217
|
+
timer = setTimeout(() => {
|
|
218
|
+
settleFailure(new SandboxTimeoutError(timeout), true);
|
|
219
|
+
}, timeout);
|
|
220
|
+
try {
|
|
221
|
+
worker.postMessage({
|
|
222
|
+
type: 'execute',
|
|
223
|
+
execId,
|
|
224
|
+
code,
|
|
225
|
+
contextVariables,
|
|
226
|
+
executionParams,
|
|
227
|
+
envVars,
|
|
228
|
+
isCustomTool,
|
|
229
|
+
syncTimeout: timeout,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
settleFailure(err, true);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
},
|
|
237
|
+
async dispose() {
|
|
238
|
+
await pool.drain();
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
async function handleFetchRpc(worker, msg, secureFetchImpl) {
|
|
243
|
+
const { id, url, init } = msg;
|
|
244
|
+
try {
|
|
245
|
+
const res = (await secureFetchImpl(url, init));
|
|
246
|
+
let bodyText = null;
|
|
247
|
+
let status = 200;
|
|
248
|
+
let statusText = 'OK';
|
|
249
|
+
let ok = true;
|
|
250
|
+
const headersOut = {};
|
|
251
|
+
let outUrl = url;
|
|
252
|
+
if (res && typeof res === 'object') {
|
|
253
|
+
if (typeof res.status === 'number')
|
|
254
|
+
status = res.status;
|
|
255
|
+
if (typeof res.statusText === 'string')
|
|
256
|
+
statusText = res.statusText;
|
|
257
|
+
if (typeof res.ok === 'boolean')
|
|
258
|
+
ok = res.ok;
|
|
259
|
+
else
|
|
260
|
+
ok = status >= 200 && status < 300;
|
|
261
|
+
if (typeof res.url === 'string')
|
|
262
|
+
outUrl = res.url;
|
|
263
|
+
if (res.headers && typeof res.headers.entries === 'function') {
|
|
264
|
+
for (const [k, v] of res.headers.entries()) {
|
|
265
|
+
headersOut[String(k).toLowerCase()] = String(v);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else if (res.headers && typeof res.headers === 'object') {
|
|
269
|
+
for (const [k, v] of Object.entries(res.headers)) {
|
|
270
|
+
headersOut[String(k).toLowerCase()] = String(v);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (typeof res.text === 'function') {
|
|
274
|
+
try {
|
|
275
|
+
bodyText = await res.text();
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
bodyText = null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else if (typeof res === 'string') {
|
|
282
|
+
bodyText = res;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
worker.postMessage({
|
|
286
|
+
type: 'fetch_response',
|
|
287
|
+
id,
|
|
288
|
+
ok,
|
|
289
|
+
status,
|
|
290
|
+
statusText,
|
|
291
|
+
headers: headersOut,
|
|
292
|
+
body: bodyText,
|
|
293
|
+
url: outUrl,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
worker.postMessage({
|
|
298
|
+
type: 'fetch_response',
|
|
299
|
+
id,
|
|
300
|
+
error: { name: err?.name ?? 'Error', message: err?.message ?? String(err) },
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=node-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-worker.js","sourceRoot":"","sources":["../../src/adapters/node-worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker source for the Function-block sandbox.
|
|
3
|
+
*
|
|
4
|
+
* This file exports a string literal containing the JS source that runs in a
|
|
5
|
+
* `worker_threads` Worker (spawned with `{ eval: true }`). The string is
|
|
6
|
+
* intentionally self-contained — no `require` of TS-only modules, no
|
|
7
|
+
* cross-package imports — because it is parsed and executed by Node directly
|
|
8
|
+
* inside the worker. Loading via `eval: true` instead of a file path avoids
|
|
9
|
+
* Next.js bundling / `import.meta.url` resolution issues in the API route.
|
|
10
|
+
*
|
|
11
|
+
* Wire format (postMessage payloads):
|
|
12
|
+
*
|
|
13
|
+
* main → worker:
|
|
14
|
+
* { type: 'execute', execId, code, contextVariables, executionParams,
|
|
15
|
+
* envVars, isCustomTool, syncTimeout }
|
|
16
|
+
* { type: 'fetch_response', id, ok, status, statusText, headers, body, error? }
|
|
17
|
+
*
|
|
18
|
+
* worker → main:
|
|
19
|
+
* { type: 'fetch', id, url, init }
|
|
20
|
+
* { type: 'console', level, message }
|
|
21
|
+
* { type: 'result', execId, value }
|
|
22
|
+
* { type: 'error', execId, error: { name, message, stack } }
|
|
23
|
+
*
|
|
24
|
+
* Each Worker handles ONE execution at a time. The pool serialises calls.
|
|
25
|
+
*/
|
|
26
|
+
export declare const WORKER_SOURCE: string;
|
|
27
|
+
//# sourceMappingURL=worker-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-source.d.ts","sourceRoot":"","sources":["../../src/adapters/worker-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,eAAO,MAAM,aAAa,QA4MzB,CAAA"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker source for the Function-block sandbox.
|
|
3
|
+
*
|
|
4
|
+
* This file exports a string literal containing the JS source that runs in a
|
|
5
|
+
* `worker_threads` Worker (spawned with `{ eval: true }`). The string is
|
|
6
|
+
* intentionally self-contained — no `require` of TS-only modules, no
|
|
7
|
+
* cross-package imports — because it is parsed and executed by Node directly
|
|
8
|
+
* inside the worker. Loading via `eval: true` instead of a file path avoids
|
|
9
|
+
* Next.js bundling / `import.meta.url` resolution issues in the API route.
|
|
10
|
+
*
|
|
11
|
+
* Wire format (postMessage payloads):
|
|
12
|
+
*
|
|
13
|
+
* main → worker:
|
|
14
|
+
* { type: 'execute', execId, code, contextVariables, executionParams,
|
|
15
|
+
* envVars, isCustomTool, syncTimeout }
|
|
16
|
+
* { type: 'fetch_response', id, ok, status, statusText, headers, body, error? }
|
|
17
|
+
*
|
|
18
|
+
* worker → main:
|
|
19
|
+
* { type: 'fetch', id, url, init }
|
|
20
|
+
* { type: 'console', level, message }
|
|
21
|
+
* { type: 'result', execId, value }
|
|
22
|
+
* { type: 'error', execId, error: { name, message, stack } }
|
|
23
|
+
*
|
|
24
|
+
* Each Worker handles ONE execution at a time. The pool serialises calls.
|
|
25
|
+
*/
|
|
26
|
+
export const WORKER_SOURCE = String.raw `
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
const { parentPort } = require('worker_threads');
|
|
30
|
+
const vm = require('vm');
|
|
31
|
+
|
|
32
|
+
if (!parentPort) {
|
|
33
|
+
throw new Error('sandbox-worker must be spawned via worker_threads with a parentPort');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Track in-flight fetch RPCs so we can settle them when main posts back.
|
|
37
|
+
let nextFetchId = 1;
|
|
38
|
+
const pendingFetches = new Map();
|
|
39
|
+
|
|
40
|
+
function sendToParent(msg) {
|
|
41
|
+
try {
|
|
42
|
+
parentPort.postMessage(msg);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
// Last resort — main is gone; nothing we can do from inside the worker.
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function makeSecureFetch() {
|
|
49
|
+
return function secureFetch(input, init) {
|
|
50
|
+
const id = nextFetchId++;
|
|
51
|
+
const url = typeof input === 'string' ? input : (input && input.url) || input;
|
|
52
|
+
|
|
53
|
+
let initSerialisable;
|
|
54
|
+
if (init && typeof init === 'object') {
|
|
55
|
+
initSerialisable = {};
|
|
56
|
+
// Only forward the fields we know how to serialise across MessageChannel.
|
|
57
|
+
if (init.method) initSerialisable.method = init.method;
|
|
58
|
+
if (init.headers) {
|
|
59
|
+
// Headers might be a Headers instance, plain object, or [k,v][] array.
|
|
60
|
+
if (typeof init.headers.entries === 'function') {
|
|
61
|
+
const h = {};
|
|
62
|
+
for (const [k, v] of init.headers.entries()) h[k] = v;
|
|
63
|
+
initSerialisable.headers = h;
|
|
64
|
+
} else {
|
|
65
|
+
initSerialisable.headers = init.headers;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (typeof init.body !== 'undefined') {
|
|
69
|
+
// Strings and JSON-serialisable objects only. Streams/blobs would
|
|
70
|
+
// throw on postMessage anyway.
|
|
71
|
+
initSerialisable.body = init.body;
|
|
72
|
+
}
|
|
73
|
+
if (init.redirect) initSerialisable.redirect = init.redirect;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
pendingFetches.set(id, { resolve, reject });
|
|
78
|
+
sendToParent({ type: 'fetch', id, url, init: initSerialisable });
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function makeConsole() {
|
|
84
|
+
function fwd(level) {
|
|
85
|
+
return function () {
|
|
86
|
+
const args = Array.prototype.slice.call(arguments);
|
|
87
|
+
const message = args
|
|
88
|
+
.map((a) => {
|
|
89
|
+
if (typeof a === 'string') return a;
|
|
90
|
+
try {
|
|
91
|
+
return JSON.stringify(a);
|
|
92
|
+
} catch {
|
|
93
|
+
return String(a);
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
.join(' ');
|
|
97
|
+
sendToParent({ type: 'console', level, message });
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
log: fwd('log'),
|
|
102
|
+
info: fwd('info'),
|
|
103
|
+
warn: fwd('warn'),
|
|
104
|
+
error: fwd('error'),
|
|
105
|
+
debug: fwd('log'),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildScript({ code, isCustomTool, executionParams }) {
|
|
110
|
+
const wrapperLines = ['(async () => {', ' try {'];
|
|
111
|
+
if (isCustomTool) {
|
|
112
|
+
wrapperLines.push(' // For custom tools, make parameters directly accessible');
|
|
113
|
+
for (const key of Object.keys(executionParams || {})) {
|
|
114
|
+
wrapperLines.push(' const ' + key + ' = params.' + key + ';');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const userCodeStartLine = wrapperLines.length + 1;
|
|
118
|
+
const fullScript = [
|
|
119
|
+
...wrapperLines,
|
|
120
|
+
' ' + (code || '').split('\n').join('\n '),
|
|
121
|
+
' } catch (error) {',
|
|
122
|
+
' console.error(error);',
|
|
123
|
+
' throw error;',
|
|
124
|
+
' }',
|
|
125
|
+
'})()',
|
|
126
|
+
].join('\n');
|
|
127
|
+
return { fullScript, userCodeStartLine };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function runOne(msg) {
|
|
131
|
+
const {
|
|
132
|
+
execId,
|
|
133
|
+
code,
|
|
134
|
+
contextVariables,
|
|
135
|
+
executionParams,
|
|
136
|
+
envVars,
|
|
137
|
+
isCustomTool,
|
|
138
|
+
syncTimeout,
|
|
139
|
+
} = msg;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const ctx = vm.createContext({
|
|
143
|
+
params: executionParams || {},
|
|
144
|
+
environmentVariables: envVars || {},
|
|
145
|
+
...(contextVariables || {}),
|
|
146
|
+
fetch: makeSecureFetch(),
|
|
147
|
+
console: makeConsole(),
|
|
148
|
+
// Surface a few harmless globals that user code may reach for.
|
|
149
|
+
setTimeout,
|
|
150
|
+
clearTimeout,
|
|
151
|
+
setInterval,
|
|
152
|
+
clearInterval,
|
|
153
|
+
Buffer,
|
|
154
|
+
URL,
|
|
155
|
+
URLSearchParams,
|
|
156
|
+
TextEncoder,
|
|
157
|
+
TextDecoder,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const { fullScript } = buildScript({ code, isCustomTool, executionParams });
|
|
161
|
+
const script = new vm.Script(fullScript, { filename: 'user-function.js' });
|
|
162
|
+
|
|
163
|
+
// The sync timeout stays armed inside the worker as a SECOND kill switch.
|
|
164
|
+
// The main thread also enforces a wall-clock timeout via worker.terminate().
|
|
165
|
+
// We cap the script-level timeout at the requested syncTimeout value so we
|
|
166
|
+
// do not undershoot the wall-clock deadline.
|
|
167
|
+
const result = await script.runInContext(ctx, {
|
|
168
|
+
timeout: typeof syncTimeout === 'number' && syncTimeout > 0 ? syncTimeout : 30000,
|
|
169
|
+
displayErrors: true,
|
|
170
|
+
breakOnSigint: true,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
sendToParent({ type: 'result', execId, value: result });
|
|
174
|
+
} catch (err) {
|
|
175
|
+
const error = {
|
|
176
|
+
name: (err && err.name) || 'Error',
|
|
177
|
+
message: (err && err.message) || String(err),
|
|
178
|
+
stack: err && err.stack,
|
|
179
|
+
};
|
|
180
|
+
sendToParent({ type: 'error', execId, error });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
parentPort.on('message', (msg) => {
|
|
185
|
+
if (!msg || typeof msg !== 'object') return;
|
|
186
|
+
if (msg.type === 'execute') {
|
|
187
|
+
runOne(msg);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (msg.type === 'fetch_response') {
|
|
191
|
+
const pending = pendingFetches.get(msg.id);
|
|
192
|
+
if (!pending) return;
|
|
193
|
+
pendingFetches.delete(msg.id);
|
|
194
|
+
if (msg.error) {
|
|
195
|
+
const err = new Error(msg.error.message || 'fetch failed');
|
|
196
|
+
if (msg.error.name) err.name = msg.error.name;
|
|
197
|
+
pending.reject(err);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// Reconstruct a Response-shaped object that user code can interact with.
|
|
201
|
+
const headers = msg.headers || {};
|
|
202
|
+
const body = msg.body;
|
|
203
|
+
const response = {
|
|
204
|
+
ok: !!msg.ok,
|
|
205
|
+
status: msg.status,
|
|
206
|
+
statusText: msg.statusText,
|
|
207
|
+
headers: {
|
|
208
|
+
get: (name) => headers[String(name).toLowerCase()] ?? null,
|
|
209
|
+
entries: () => Object.entries(headers),
|
|
210
|
+
forEach: (cb) => {
|
|
211
|
+
for (const [k, v] of Object.entries(headers)) cb(v, k);
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
url: msg.url,
|
|
215
|
+
text: () => Promise.resolve(typeof body === 'string' ? body : JSON.stringify(body ?? '')),
|
|
216
|
+
json: () =>
|
|
217
|
+
Promise.resolve(
|
|
218
|
+
typeof body === 'string' ? (body ? JSON.parse(body) : null) : body
|
|
219
|
+
),
|
|
220
|
+
arrayBuffer: () =>
|
|
221
|
+
Promise.resolve(
|
|
222
|
+
typeof body === 'string'
|
|
223
|
+
? new TextEncoder().encode(body).buffer
|
|
224
|
+
: new TextEncoder().encode(JSON.stringify(body ?? '')).buffer
|
|
225
|
+
),
|
|
226
|
+
};
|
|
227
|
+
pending.resolve(response);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
`;
|
|
231
|
+
//# sourceMappingURL=worker-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-source.js","sourceRoot":"","sources":["../../src/adapters/worker-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4MtC,CAAA"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SandboxClient, SandboxClientConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a provider-agnostic sandbox client.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { createSandboxClient, nodeWorkerAdapter } from '@yarlisai/sandbox'
|
|
8
|
+
*
|
|
9
|
+
* const sandbox = createSandboxClient({
|
|
10
|
+
* adapter: nodeWorkerAdapter({ maxWorkers: 4 }),
|
|
11
|
+
* })
|
|
12
|
+
*
|
|
13
|
+
* const { result } = await sandbox.execute({
|
|
14
|
+
* code: 'return params.a + params.b',
|
|
15
|
+
* executionParams: { a: 1, b: 2 },
|
|
16
|
+
* envVars: {},
|
|
17
|
+
* contextVariables: {},
|
|
18
|
+
* isCustomTool: false,
|
|
19
|
+
* timeout: 5000,
|
|
20
|
+
* secureFetchImpl: (url, init) => fetch(url, init),
|
|
21
|
+
* consoleSink: {
|
|
22
|
+
* log: (m) => console.log(m),
|
|
23
|
+
* info: (m) => console.info(m),
|
|
24
|
+
* warn: (m) => console.warn(m),
|
|
25
|
+
* error: (m) => console.error(m),
|
|
26
|
+
* },
|
|
27
|
+
* })
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Swap adapters by changing only the `adapter` field — `nodeWorkerAdapter`
|
|
31
|
+
* for production, `memoryAdapter` for tests, or roll your own. All implement
|
|
32
|
+
* the same `SandboxRunner` port.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createSandboxClient(config: SandboxClientConfig): SandboxClient;
|
|
35
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,mBAAmB,EAGpB,MAAM,SAAS,CAAA;AAEhB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CAY9E"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a provider-agnostic sandbox client.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { createSandboxClient, nodeWorkerAdapter } from '@yarlisai/sandbox'
|
|
7
|
+
*
|
|
8
|
+
* const sandbox = createSandboxClient({
|
|
9
|
+
* adapter: nodeWorkerAdapter({ maxWorkers: 4 }),
|
|
10
|
+
* })
|
|
11
|
+
*
|
|
12
|
+
* const { result } = await sandbox.execute({
|
|
13
|
+
* code: 'return params.a + params.b',
|
|
14
|
+
* executionParams: { a: 1, b: 2 },
|
|
15
|
+
* envVars: {},
|
|
16
|
+
* contextVariables: {},
|
|
17
|
+
* isCustomTool: false,
|
|
18
|
+
* timeout: 5000,
|
|
19
|
+
* secureFetchImpl: (url, init) => fetch(url, init),
|
|
20
|
+
* consoleSink: {
|
|
21
|
+
* log: (m) => console.log(m),
|
|
22
|
+
* info: (m) => console.info(m),
|
|
23
|
+
* warn: (m) => console.warn(m),
|
|
24
|
+
* error: (m) => console.error(m),
|
|
25
|
+
* },
|
|
26
|
+
* })
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Swap adapters by changing only the `adapter` field — `nodeWorkerAdapter`
|
|
30
|
+
* for production, `memoryAdapter` for tests, or roll your own. All implement
|
|
31
|
+
* the same `SandboxRunner` port.
|
|
32
|
+
*/
|
|
33
|
+
export function createSandboxClient(config) {
|
|
34
|
+
return {
|
|
35
|
+
execute(args) {
|
|
36
|
+
return config.adapter.execute(args);
|
|
37
|
+
},
|
|
38
|
+
dispose() {
|
|
39
|
+
return config.adapter.dispose();
|
|
40
|
+
},
|
|
41
|
+
get adapter() {
|
|
42
|
+
return config.adapter.name;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAA2B;IAC7D,OAAO;QACL,OAAO,CAAC,IAAwB;YAC9B,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACrC,CAAC;QACD,OAAO;YACL,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAA;QACjC,CAAC;QACD,IAAI,OAAO;YACT,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAA;QAC5B,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { memoryAdapter } from './adapters/memory';
|
|
2
|
+
export { nodeWorkerAdapter } from './adapters/node-worker';
|
|
3
|
+
export { WORKER_SOURCE } from './adapters/worker-source';
|
|
4
|
+
export { createSandboxClient } from './client';
|
|
5
|
+
export * from './types';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAC9C,cAAc,SAAS,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// @yarlisai/sandbox — pluggable sandbox runner for executing untrusted JS code.
|
|
2
|
+
//
|
|
3
|
+
// Add a new adapter: implement `SandboxRunner` (see types.ts) and export a factory.
|
|
4
|
+
export { memoryAdapter } from './adapters/memory';
|
|
5
|
+
// First-party adapters
|
|
6
|
+
export { nodeWorkerAdapter } from './adapters/node-worker';
|
|
7
|
+
// Re-exported for adapter authors who want to embed the same worker source.
|
|
8
|
+
export { WORKER_SOURCE } from './adapters/worker-source';
|
|
9
|
+
export { createSandboxClient } from './client';
|
|
10
|
+
export * from './types';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,oFAAoF;AAEpF,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,uBAAuB;AACvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,4EAA4E;AAC5E,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAC9C,cAAc,SAAS,CAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @yarlisai/sandbox — port for executing untrusted JS code in isolation.
|
|
3
|
+
*
|
|
4
|
+
* Adapters (`node-worker`, `memory`, custom) implement `SandboxRunner`. Callers
|
|
5
|
+
* use `createSandboxClient({ adapter })` and never see the underlying transport.
|
|
6
|
+
*
|
|
7
|
+
* The default `node-worker` adapter spawns a small pool of `worker_threads`
|
|
8
|
+
* Workers and runs each piece of user code inside `vm.Script.runInContext`
|
|
9
|
+
* with a wall-clock kill switch. The `memory` adapter runs code synchronously
|
|
10
|
+
* in the parent process — no isolation, intended for unit tests / dev loops.
|
|
11
|
+
*/
|
|
12
|
+
/** Console output forwarded out of the sandbox to the caller. */
|
|
13
|
+
export interface ConsoleSink {
|
|
14
|
+
log: (msg: string) => void;
|
|
15
|
+
info: (msg: string) => void;
|
|
16
|
+
warn: (msg: string) => void;
|
|
17
|
+
error: (msg: string) => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Caller-supplied fetch implementation. The sandbox cannot make network calls
|
|
21
|
+
* directly — all `fetch()` invocations inside the sandbox proxy back to this
|
|
22
|
+
* function on the main thread, where workspace-scoped headers / SSRF guards
|
|
23
|
+
* can be applied before the request leaves the box.
|
|
24
|
+
*/
|
|
25
|
+
export type SecureFetchImpl = (url: string, init?: unknown) => Promise<Response> | Promise<unknown>;
|
|
26
|
+
/** Arguments for a single `execute()` call. */
|
|
27
|
+
export interface SandboxExecuteArgs {
|
|
28
|
+
/** Raw user code (JavaScript). The sandbox wraps this in an async IIFE. */
|
|
29
|
+
code: string;
|
|
30
|
+
/** Parameters made available to the script as `params.<name>`. */
|
|
31
|
+
executionParams: Record<string, unknown>;
|
|
32
|
+
/** Available to the script as `environmentVariables.<name>`. */
|
|
33
|
+
envVars: Record<string, unknown>;
|
|
34
|
+
/** Free-form context variables spread into the VM context. */
|
|
35
|
+
contextVariables: Record<string, unknown>;
|
|
36
|
+
/**
|
|
37
|
+
* If true, the wrapper destructures `executionParams` into top-level
|
|
38
|
+
* `const`s before user code runs (custom-tool ergonomics).
|
|
39
|
+
*/
|
|
40
|
+
isCustomTool: boolean;
|
|
41
|
+
/** Wall-clock + in-VM timeout in milliseconds. */
|
|
42
|
+
timeout: number;
|
|
43
|
+
/** Main-thread fetch impl (SSRF / authn / quota gate). */
|
|
44
|
+
secureFetchImpl: SecureFetchImpl;
|
|
45
|
+
/** Sink for forwarded `console.*` output. */
|
|
46
|
+
consoleSink: ConsoleSink;
|
|
47
|
+
}
|
|
48
|
+
export interface SandboxExecuteResult {
|
|
49
|
+
/** The awaited return value of the user code. */
|
|
50
|
+
result: unknown;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Port: any sandbox adapter must implement this.
|
|
54
|
+
* - `execute()` runs ONE piece of user code and resolves with its return value.
|
|
55
|
+
* - `dispose()` tears the runner down (drain pool / close handles). Idempotent.
|
|
56
|
+
*/
|
|
57
|
+
export interface SandboxRunner {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
execute(args: SandboxExecuteArgs): Promise<SandboxExecuteResult>;
|
|
60
|
+
dispose(): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
/** Configuration for the worker-pool adapter. */
|
|
63
|
+
export interface NodeWorkerAdapterConfig {
|
|
64
|
+
/** Maximum live workers. Defaults to 4. */
|
|
65
|
+
maxWorkers?: number;
|
|
66
|
+
/** Per-worker `maxOldGenerationSizeMb`. Defaults to 256. */
|
|
67
|
+
workerMemoryMb?: number;
|
|
68
|
+
}
|
|
69
|
+
/** `createSandboxClient` config. */
|
|
70
|
+
export interface SandboxClientConfig {
|
|
71
|
+
adapter: SandboxRunner;
|
|
72
|
+
}
|
|
73
|
+
/** Public client surface returned by `createSandboxClient`. */
|
|
74
|
+
export interface SandboxClient {
|
|
75
|
+
execute(args: SandboxExecuteArgs): Promise<SandboxExecuteResult>;
|
|
76
|
+
dispose(): Promise<void>;
|
|
77
|
+
readonly adapter: string;
|
|
78
|
+
}
|
|
79
|
+
export type SandboxMainToWorkerMessage = {
|
|
80
|
+
type: 'execute';
|
|
81
|
+
execId: number;
|
|
82
|
+
code: string;
|
|
83
|
+
contextVariables: Record<string, unknown>;
|
|
84
|
+
executionParams: Record<string, unknown>;
|
|
85
|
+
envVars: Record<string, unknown>;
|
|
86
|
+
isCustomTool: boolean;
|
|
87
|
+
syncTimeout: number;
|
|
88
|
+
} | {
|
|
89
|
+
type: 'fetch_response';
|
|
90
|
+
id: number;
|
|
91
|
+
ok?: boolean;
|
|
92
|
+
status?: number;
|
|
93
|
+
statusText?: string;
|
|
94
|
+
headers?: Record<string, string>;
|
|
95
|
+
body?: string | null;
|
|
96
|
+
url?: string;
|
|
97
|
+
error?: {
|
|
98
|
+
name?: string;
|
|
99
|
+
message?: string;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
export type SandboxWorkerToMainMessage = {
|
|
103
|
+
type: 'fetch';
|
|
104
|
+
id: number;
|
|
105
|
+
url: string;
|
|
106
|
+
init?: unknown;
|
|
107
|
+
} | {
|
|
108
|
+
type: 'console';
|
|
109
|
+
level: 'log' | 'info' | 'warn' | 'error';
|
|
110
|
+
message: string;
|
|
111
|
+
} | {
|
|
112
|
+
type: 'result';
|
|
113
|
+
execId: number;
|
|
114
|
+
value: unknown;
|
|
115
|
+
} | {
|
|
116
|
+
type: 'error';
|
|
117
|
+
execId: number;
|
|
118
|
+
error: {
|
|
119
|
+
name?: string;
|
|
120
|
+
message?: string;
|
|
121
|
+
stack?: string;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Reasons surfaced by the `node-worker` adapter when the wall-clock timeout
|
|
126
|
+
* fires. The `name` is preserved so existing matchers in apps continue to
|
|
127
|
+
* recognise the error.
|
|
128
|
+
*/
|
|
129
|
+
export declare class SandboxTimeoutError extends Error {
|
|
130
|
+
constructor(timeout: number);
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @yarlisai/sandbox — port for executing untrusted JS code in isolation.
|
|
3
|
+
*
|
|
4
|
+
* Adapters (`node-worker`, `memory`, custom) implement `SandboxRunner`. Callers
|
|
5
|
+
* use `createSandboxClient({ adapter })` and never see the underlying transport.
|
|
6
|
+
*
|
|
7
|
+
* The default `node-worker` adapter spawns a small pool of `worker_threads`
|
|
8
|
+
* Workers and runs each piece of user code inside `vm.Script.runInContext`
|
|
9
|
+
* with a wall-clock kill switch. The `memory` adapter runs code synchronously
|
|
10
|
+
* in the parent process — no isolation, intended for unit tests / dev loops.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Reasons surfaced by the `node-worker` adapter when the wall-clock timeout
|
|
14
|
+
* fires. The `name` is preserved so existing matchers in apps continue to
|
|
15
|
+
* recognise the error.
|
|
16
|
+
*/
|
|
17
|
+
export class SandboxTimeoutError extends Error {
|
|
18
|
+
constructor(timeout) {
|
|
19
|
+
// Phrasing intentionally includes both "exceeded" and "timed out" so the
|
|
20
|
+
// legacy `vm.Script` watchdog message and the worker wall-clock kill share
|
|
21
|
+
// the same matcher in tests and user-facing surfaces.
|
|
22
|
+
super(`Sandbox execution exceeded ${timeout}ms timeout (execution timed out)`);
|
|
23
|
+
this.name = 'SandboxTimeoutError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAmHH;;;;GAIG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAe;QACzB,yEAAyE;QACzE,2EAA2E;QAC3E,sDAAsD;QACtD,KAAK,CAAC,8BAA8B,OAAO,kCAAkC,CAAC,CAAA;QAC9E,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAA;IACnC,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yarlisai/sandbox",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Pluggable sandbox runner for executing untrusted user code (worker_threads pool + in-memory fallback).",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"bun": "./src/index.ts",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"lint": "biome check .",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"build": "bun run clean && bunx tsc -p tsconfig.build.json",
|
|
19
|
+
"clean": "rm -rf dist",
|
|
20
|
+
"prepublishOnly": "bun run build"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^20.11.0",
|
|
24
|
+
"typescript": "^5.7.3"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"author": "YarlisAI <support@yarlis.ai>",
|
|
28
|
+
"homepage": "https://github.com/yarlisai/mybotbox-platform/tree/main/packages/sandbox#readme",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/yarlisai/mybotbox-platform.git",
|
|
32
|
+
"directory": "packages/sandbox"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/yarlisai/mybotbox-platform/issues"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"yarlisai",
|
|
39
|
+
"framework",
|
|
40
|
+
"sandbox",
|
|
41
|
+
"worker_threads",
|
|
42
|
+
"code-execution",
|
|
43
|
+
"vm"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public",
|
|
47
|
+
"registry": "https://registry.npmjs.org/"
|
|
48
|
+
},
|
|
49
|
+
"module": "./dist/index.js",
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"README.md",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
],
|
|
55
|
+
"sideEffects": false
|
|
56
|
+
}
|