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.
- package/README.md +18 -1
- package/package.json +1 -1
- package/src/db/workflow-db.ts +26 -7
- package/src/expression/evaluator.ts +1 -0
- package/src/parser/agent-parser.test.ts +8 -5
- package/src/parser/schema.ts +8 -2
- package/src/runner/audit-verification.test.ts +106 -0
- package/src/runner/llm-adapter.ts +196 -4
- package/src/runner/llm-clarification.test.ts +182 -0
- package/src/runner/llm-executor.ts +118 -26
- package/src/runner/mcp-manager.ts +4 -1
- package/src/runner/mcp-server.test.ts +115 -1
- package/src/runner/mcp-server.ts +161 -4
- package/src/runner/shell-executor.ts +1 -1
- package/src/runner/step-executor.test.ts +33 -10
- package/src/runner/step-executor.ts +110 -14
- package/src/runner/workflow-runner.test.ts +132 -0
- package/src/runner/workflow-runner.ts +118 -23
- package/src/templates/agents/keystone-architect.md +13 -6
- package/src/ui/dashboard.tsx +32 -4
- package/src/utils/auth-manager.test.ts +31 -0
- package/src/utils/auth-manager.ts +21 -5
- package/src/utils/json-parser.test.ts +35 -0
- package/src/utils/json-parser.ts +95 -0
- package/src/utils/mermaid.ts +12 -0
- package/src/utils/sandbox.test.ts +12 -4
- package/src/utils/sandbox.ts +69 -49
|
@@ -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(
|
|
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(
|
|
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
|
|
package/src/utils/sandbox.ts
CHANGED
|
@@ -1,61 +1,81 @@
|
|
|
1
1
|
import * as vm from 'node:vm';
|
|
2
2
|
|
|
3
3
|
export interface SandboxOptions {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
timeout?: number;
|
|
5
|
+
memoryLimit?: number;
|
|
6
|
+
allowInsecureFallback?: boolean;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export class SafeSandbox {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
}
|