keystone-cli 0.3.2 → 0.4.0

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.
@@ -3,22 +3,30 @@ import { SafeSandbox } from './sandbox';
3
3
 
4
4
  describe('SafeSandbox', () => {
5
5
  it('should execute basic arithmetic', async () => {
6
- const result = await SafeSandbox.execute('1 + 2');
6
+ const result = await SafeSandbox.execute('1 + 2', {}, { allowInsecureFallback: true });
7
7
  expect(result).toBe(3);
8
8
  });
9
9
 
10
10
  it('should have access to context variables', async () => {
11
- const result = await SafeSandbox.execute('a + b', { a: 10, b: 20 });
11
+ const result = await SafeSandbox.execute(
12
+ 'a + b',
13
+ { a: 10, b: 20 },
14
+ { allowInsecureFallback: true }
15
+ );
12
16
  expect(result).toBe(30);
13
17
  });
14
18
 
15
19
  it('should not have access to Node.js globals', async () => {
16
- const result = await SafeSandbox.execute('typeof process');
20
+ const result = await SafeSandbox.execute('typeof process', {}, { allowInsecureFallback: true });
17
21
  expect(result).toBe('undefined');
18
22
  });
19
23
 
20
24
  it('should handle object results', async () => {
21
- const result = await SafeSandbox.execute('({ x: 1, y: 2 })');
25
+ const result = await SafeSandbox.execute(
26
+ '({ x: 1, y: 2 })',
27
+ {},
28
+ { allowInsecureFallback: true }
29
+ );
22
30
  expect(result).toEqual({ x: 1, y: 2 });
23
31
  });
24
32
 
@@ -1,61 +1,81 @@
1
1
  import * as vm from 'node:vm';
2
2
 
3
3
  export interface SandboxOptions {
4
- timeout?: number;
5
- memoryLimit?: number;
4
+ timeout?: number;
5
+ memoryLimit?: number;
6
+ allowInsecureFallback?: boolean;
6
7
  }
7
8
 
8
9
  export class SafeSandbox {
9
- /**
10
- * Execute a script in a secure sandbox
11
- */
12
- static async execute(
13
- code: string,
14
- context: Record<string, unknown> = {},
15
- options: SandboxOptions = {}
16
- ): Promise<unknown> {
10
+ /**
11
+ * Execute a script in a secure sandbox
12
+ */
13
+ static async execute(
14
+ code: string,
15
+ context: Record<string, unknown> = {},
16
+ options: SandboxOptions = {}
17
+ ): Promise<unknown> {
18
+ try {
19
+ // Try to use isolated-vm if available (dynamic import)
20
+ // Note: This will likely fail on Bun as it expects V8 host symbols
21
+ const ivm = await import('isolated-vm').then((m) => m.default || m).catch(() => null);
22
+
23
+ if (ivm && typeof ivm.Isolate === 'function') {
24
+ const isolate = new ivm.Isolate({ memoryLimit: options.memoryLimit || 128 });
17
25
  try {
18
- // Try to use isolated-vm if available (dynamic import)
19
- // Note: This will likely fail on Bun as it expects V8 host symbols
20
- const ivm = await import('isolated-vm').then((m) => m.default || m).catch(() => null);
21
-
22
- if (ivm && typeof ivm.Isolate === 'function') {
23
- const isolate = new ivm.Isolate({ memoryLimit: options.memoryLimit || 128 });
24
- try {
25
- const contextInstance = await isolate.createContext();
26
- const jail = contextInstance.global;
27
-
28
- // Set up global context
29
- await jail.set('global', jail.derefInto());
30
-
31
- // Inject context variables
32
- for (const [key, value] of Object.entries(context)) {
33
- // Only copy non-undefined values
34
- if (value !== undefined) {
35
- await jail.set(key, new ivm.ExternalCopy(value).copyInto());
36
- }
37
- }
38
-
39
- const script = await isolate.compileScript(code);
40
- const result = await script.run(contextInstance, { timeout: options.timeout || 5000 });
41
-
42
- if (result && typeof result === 'object' && result instanceof ivm.Reference) {
43
- return await result.copy();
44
- }
45
- return result;
46
- } finally {
47
- isolate.dispose();
48
- }
26
+ const contextInstance = await isolate.createContext();
27
+ const jail = contextInstance.global;
28
+
29
+ // Set up global context
30
+ await jail.set('global', jail.derefInto());
31
+
32
+ // Inject context variables
33
+ for (const [key, value] of Object.entries(context)) {
34
+ // Only copy non-undefined values
35
+ if (value !== undefined) {
36
+ await jail.set(key, new ivm.ExternalCopy(value).copyInto());
49
37
  }
50
- } catch (e) {
51
- // Fallback to node:vm if isolated-vm fails to load or run
38
+ }
39
+
40
+ const script = await isolate.compileScript(code);
41
+ const result = await script.run(contextInstance, { timeout: options.timeout || 5000 });
42
+
43
+ if (result && typeof result === 'object' && result instanceof ivm.Reference) {
44
+ return await result.copy();
45
+ }
46
+ return result;
47
+ } finally {
48
+ isolate.dispose();
52
49
  }
50
+ }
51
+ } catch (e) {
52
+ // If isolated-vm failed during execution (not just loading), log it if we're debugging
53
+ }
54
+
55
+ // Fallback implementation using node:vm (built-in)
56
+ // ONLY allowed if explicitly opted in via options
57
+ if (!options.allowInsecureFallback) {
58
+ throw new Error(
59
+ 'Execution in secure sandbox failed (isolated-vm not available) and insecure fallback is disabled.\n' +
60
+ 'To allow insecure execution, set allowInsecureFallback: true in sandbox options.\n' +
61
+ 'Note: Insecure execution is NOT recommended for untrusted code.'
62
+ );
63
+ }
53
64
 
54
- // Fallback implementation using node:vm (built-in)
55
- const sandbox = { ...context };
56
- return vm.runInNewContext(code, sandbox, {
57
- timeout: options.timeout || 5000,
58
- displayErrors: true,
59
- });
65
+ if (!SafeSandbox.warned) {
66
+ const isBun = typeof Bun !== 'undefined';
67
+ console.warn(
68
+ `\n⚠️ SECURITY WARNING: Using ${isBun ? 'Bun' : 'Node.js'} built-in VM for script execution.\n This sandbox is NOT secure against malicious code.\n Only run workflows from trusted sources.\n`
69
+ );
70
+ SafeSandbox.warned = true;
60
71
  }
72
+
73
+ const sandbox = { ...context };
74
+ return vm.runInNewContext(code, sandbox, {
75
+ timeout: options.timeout || 5000,
76
+ displayErrors: true,
77
+ });
78
+ }
79
+
80
+ private static warned = false;
61
81
  }