@tyvm/knowhow 0.0.108 → 0.0.109-dev.2b94ba2
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 +45 -0
- package/package.json +9 -4
- package/scripts/build-for-node.sh +10 -24
- package/scripts/publish.sh +86 -0
- package/src/agents/base/base.ts +10 -0
- package/src/agents/tools/execCommand.ts +49 -6
- package/src/agents/tools/index.ts +0 -1
- package/src/agents/tools/list.ts +2 -4
- package/src/chat/CliChatService.ts +11 -2
- package/src/chat/modules/AgentModule.ts +61 -31
- package/src/chat/modules/SessionsModule.ts +47 -3
- package/src/chat/modules/SystemModule.ts +2 -2
- package/src/chat/renderer/CompactRenderer.ts +20 -0
- package/src/chat/renderer/ConsoleRenderer.ts +19 -0
- package/src/chat/renderer/FancyRenderer.ts +19 -0
- package/src/chat/renderer/types.ts +11 -0
- package/src/cli.ts +91 -659
- package/src/clients/anthropic.ts +18 -17
- package/src/clients/index.ts +31 -11
- package/src/clients/openai.ts +8 -5
- package/src/clients/types.ts +48 -10
- package/src/clients/withRetry.ts +89 -0
- package/src/cloudWorker.ts +175 -113
- package/src/commands/agent.ts +246 -0
- package/src/commands/misc.ts +174 -0
- package/src/commands/modules.ts +552 -0
- package/src/commands/services.ts +77 -0
- package/src/commands/workers.ts +168 -0
- package/src/config.ts +38 -1
- package/src/fileSync.ts +70 -29
- package/src/hashes.ts +35 -13
- package/src/index.ts +18 -0
- package/src/logger.ts +197 -0
- package/src/plugins/embedding.ts +11 -6
- package/src/plugins/plugins.ts +0 -21
- package/src/plugins/vim.ts +5 -16
- package/src/processors/JsonCompressor.ts +6 -6
- package/src/services/EventService.ts +61 -1
- package/src/services/KnowhowClient.ts +34 -4
- package/src/services/MediaProcessorService.ts +79 -10
- package/src/services/modules/index.ts +102 -53
- package/src/services/modules/types.ts +6 -0
- package/src/tunnel.ts +216 -0
- package/src/types.ts +0 -1
- package/src/worker.ts +105 -312
- package/src/workers/auth/WsMiddleware.ts +99 -0
- package/src/workers/auth/authMiddleware.ts +104 -0
- package/src/workers/auth/types.ts +14 -2
- package/src/workers/tools/index.ts +2 -0
- package/src/workers/tools/reloadConfig.ts +84 -0
- package/tests/services/WorkerReloadConfig.test.ts +141 -0
- package/tests/unit/clients/AIClient.test.ts +446 -0
- package/tests/unit/clients/withRetry.test.ts +319 -0
- package/tests/unit/commands/github-credentials.test.ts +210 -0
- package/tests/unit/modules/moduleLoading.test.ts +39 -37
- package/tests/unit/plugins/pluginLoading.test.ts +0 -85
- package/ts_build/package.json +9 -4
- package/ts_build/src/agents/base/base.js +11 -0
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
- package/ts_build/src/agents/tools/execCommand.js +39 -5
- package/ts_build/src/agents/tools/execCommand.js.map +1 -1
- package/ts_build/src/agents/tools/index.d.ts +0 -1
- package/ts_build/src/agents/tools/index.js +0 -1
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +2 -4
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/chat/CliChatService.js +14 -2
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +43 -20
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/SessionsModule.js +37 -3
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
- package/ts_build/src/chat/modules/SystemModule.js +2 -2
- package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
- package/ts_build/src/chat/renderer/CompactRenderer.d.ts +4 -0
- package/ts_build/src/chat/renderer/CompactRenderer.js +16 -0
- package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -1
- package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +4 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.js +16 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -1
- package/ts_build/src/chat/renderer/FancyRenderer.d.ts +4 -0
- package/ts_build/src/chat/renderer/FancyRenderer.js +16 -0
- package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -1
- package/ts_build/src/chat/renderer/types.d.ts +2 -0
- package/ts_build/src/cli.js +47 -519
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +5 -5
- package/ts_build/src/clients/anthropic.js +18 -17
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/index.js +9 -10
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/openai.js +4 -4
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/types.d.ts +15 -8
- package/ts_build/src/clients/withRetry.d.ts +2 -0
- package/ts_build/src/clients/withRetry.js +60 -0
- package/ts_build/src/clients/withRetry.js.map +1 -0
- package/ts_build/src/cloudWorker.d.ts +14 -0
- package/ts_build/src/cloudWorker.js +105 -66
- package/ts_build/src/cloudWorker.js.map +1 -1
- package/ts_build/src/commands/agent.d.ts +6 -0
- package/ts_build/src/commands/agent.js +229 -0
- package/ts_build/src/commands/agent.js.map +1 -0
- package/ts_build/src/commands/misc.d.ts +10 -0
- package/ts_build/src/commands/misc.js +197 -0
- package/ts_build/src/commands/misc.js.map +1 -0
- package/ts_build/src/commands/modules.d.ts +3 -0
- package/ts_build/src/commands/modules.js +487 -0
- package/ts_build/src/commands/modules.js.map +1 -0
- package/ts_build/src/commands/services.d.ts +5 -0
- package/ts_build/src/commands/services.js +87 -0
- package/ts_build/src/commands/services.js.map +1 -0
- package/ts_build/src/commands/workers.d.ts +6 -0
- package/ts_build/src/commands/workers.js +168 -0
- package/ts_build/src/commands/workers.js.map +1 -0
- package/ts_build/src/config.d.ts +1 -0
- package/ts_build/src/config.js +33 -1
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/fileSync.d.ts +6 -0
- package/ts_build/src/fileSync.js +50 -23
- package/ts_build/src/fileSync.js.map +1 -1
- package/ts_build/src/hashes.d.ts +2 -2
- package/ts_build/src/hashes.js +35 -9
- package/ts_build/src/hashes.js.map +1 -1
- package/ts_build/src/index.d.ts +1 -0
- package/ts_build/src/index.js +17 -1
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/logger.d.ts +21 -0
- package/ts_build/src/logger.js +106 -0
- package/ts_build/src/logger.js.map +1 -0
- package/ts_build/src/plugins/embedding.js +4 -3
- package/ts_build/src/plugins/embedding.js.map +1 -1
- package/ts_build/src/plugins/plugins.d.ts +0 -2
- package/ts_build/src/plugins/plugins.js +0 -11
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/plugins/vim.js +3 -9
- package/ts_build/src/plugins/vim.js.map +1 -1
- package/ts_build/src/processors/JsonCompressor.js +4 -4
- package/ts_build/src/processors/JsonCompressor.js.map +1 -1
- package/ts_build/src/services/EventService.d.ts +6 -1
- package/ts_build/src/services/EventService.js +29 -0
- package/ts_build/src/services/EventService.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +13 -1
- package/ts_build/src/services/KnowhowClient.js +19 -2
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/MediaProcessorService.d.ts +5 -4
- package/ts_build/src/services/MediaProcessorService.js +53 -8
- package/ts_build/src/services/MediaProcessorService.js.map +1 -1
- package/ts_build/src/services/modules/index.d.ts +33 -0
- package/ts_build/src/services/modules/index.js +73 -49
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/services/modules/types.d.ts +6 -0
- package/ts_build/src/tunnel.d.ts +27 -0
- package/ts_build/src/tunnel.js +112 -0
- package/ts_build/src/tunnel.js.map +1 -0
- package/ts_build/src/types.d.ts +0 -1
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker.d.ts +1 -4
- package/ts_build/src/worker.js +59 -227
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/src/workers/auth/WsMiddleware.d.ts +8 -0
- package/ts_build/src/workers/auth/WsMiddleware.js +65 -0
- package/ts_build/src/workers/auth/WsMiddleware.js.map +1 -0
- package/ts_build/src/workers/auth/authMiddleware.d.ts +3 -0
- package/ts_build/src/workers/auth/authMiddleware.js +60 -0
- package/ts_build/src/workers/auth/authMiddleware.js.map +1 -0
- package/ts_build/src/workers/auth/types.d.ts +8 -1
- package/ts_build/src/workers/tools/index.d.ts +2 -0
- package/ts_build/src/workers/tools/index.js +4 -1
- package/ts_build/src/workers/tools/index.js.map +1 -1
- package/ts_build/src/workers/tools/reloadConfig.d.ts +14 -0
- package/ts_build/src/workers/tools/reloadConfig.js +48 -0
- package/ts_build/src/workers/tools/reloadConfig.js.map +1 -0
- package/ts_build/tests/services/WorkerReloadConfig.test.d.ts +1 -0
- package/ts_build/tests/services/WorkerReloadConfig.test.js +86 -0
- package/ts_build/tests/services/WorkerReloadConfig.test.js.map +1 -0
- package/ts_build/tests/unit/clients/AIClient.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/AIClient.test.js +339 -0
- package/ts_build/tests/unit/clients/AIClient.test.js.map +1 -0
- package/ts_build/tests/unit/clients/withRetry.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/withRetry.test.js +225 -0
- package/ts_build/tests/unit/clients/withRetry.test.js.map +1 -0
- package/ts_build/tests/unit/commands/github-credentials.test.d.ts +1 -0
- package/ts_build/tests/unit/commands/github-credentials.test.js +145 -0
- package/ts_build/tests/unit/commands/github-credentials.test.js.map +1 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.js +20 -26
- package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
- package/ts_build/tests/unit/plugins/pluginLoading.test.js +0 -65
- package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
- package/src/agents/tools/executeScript/README.md +0 -94
- package/src/agents/tools/executeScript/definition.ts +0 -79
- package/src/agents/tools/executeScript/examples/dependency-injection-validation.ts +0 -272
- package/src/agents/tools/executeScript/examples/quick-test.ts +0 -74
- package/src/agents/tools/executeScript/examples/serialization-test.ts +0 -321
- package/src/agents/tools/executeScript/examples/test-runner.ts +0 -197
- package/src/agents/tools/executeScript/index.ts +0 -98
- package/src/services/script-execution/SandboxContext.ts +0 -282
- package/src/services/script-execution/ScriptExecutor.ts +0 -441
- package/src/services/script-execution/ScriptPolicy.ts +0 -194
- package/src/services/script-execution/ScriptTracer.ts +0 -249
- package/src/services/script-execution/types.ts +0 -134
- package/ts_build/src/agents/tools/executeScript/definition.d.ts +0 -2
- package/ts_build/src/agents/tools/executeScript/definition.js +0 -76
- package/ts_build/src/agents/tools/executeScript/definition.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.d.ts +0 -18
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js +0 -192
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +0 -3
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +0 -64
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +0 -15
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +0 -266
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +0 -4
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +0 -208
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/index.d.ts +0 -28
- package/ts_build/src/agents/tools/executeScript/index.js +0 -72
- package/ts_build/src/agents/tools/executeScript/index.js.map +0 -1
- package/ts_build/src/services/script-execution/SandboxContext.d.ts +0 -34
- package/ts_build/src/services/script-execution/SandboxContext.js +0 -189
- package/ts_build/src/services/script-execution/SandboxContext.js.map +0 -1
- package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +0 -19
- package/ts_build/src/services/script-execution/ScriptExecutor.js +0 -269
- package/ts_build/src/services/script-execution/ScriptExecutor.js.map +0 -1
- package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +0 -28
- package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -115
- package/ts_build/src/services/script-execution/ScriptPolicy.js.map +0 -1
- package/ts_build/src/services/script-execution/ScriptTracer.d.ts +0 -19
- package/ts_build/src/services/script-execution/ScriptTracer.js +0 -186
- package/ts_build/src/services/script-execution/ScriptTracer.js.map +0 -1
- package/ts_build/src/services/script-execution/types.d.ts +0 -108
- package/ts_build/src/services/script-execution/types.js +0 -3
- package/ts_build/src/services/script-execution/types.js.map +0 -1
|
@@ -1,441 +0,0 @@
|
|
|
1
|
-
import ivm from "isolated-vm";
|
|
2
|
-
import { services, ToolsService } from "../../services";
|
|
3
|
-
import { AIClient, Clients } from "../../clients";
|
|
4
|
-
import { SandboxContext } from "./SandboxContext";
|
|
5
|
-
import { ScriptTracer } from "./ScriptTracer";
|
|
6
|
-
import { ScriptPolicyEnforcer } from "./ScriptPolicy";
|
|
7
|
-
import {
|
|
8
|
-
ExecutionRequest,
|
|
9
|
-
ExecutionResult,
|
|
10
|
-
ResourceQuotas,
|
|
11
|
-
SecurityPolicy,
|
|
12
|
-
ExecutionTrace,
|
|
13
|
-
} from "./types";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Executes TypeScript scripts in a secure sandbox environment
|
|
17
|
-
*/
|
|
18
|
-
export class ScriptExecutor {
|
|
19
|
-
private defaultQuotas: ResourceQuotas = {
|
|
20
|
-
maxToolCalls: 50,
|
|
21
|
-
maxTokens: 10000,
|
|
22
|
-
maxExecutionTimeMs: 30000, // 30 seconds
|
|
23
|
-
maxCostUsd: 1.0,
|
|
24
|
-
maxMemoryMb: 100,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
private defaultPolicy: SecurityPolicy = {
|
|
28
|
-
allowlistedTools: [], // Empty means all tools allowed
|
|
29
|
-
denylistedTools: [
|
|
30
|
-
"executeScript", // Circular script execution
|
|
31
|
-
"execCommand", // Dangerous system commands
|
|
32
|
-
"writeFileChunk", // File system write access
|
|
33
|
-
"patchFile", // File system modification
|
|
34
|
-
],
|
|
35
|
-
maxScriptLength: 50000, // 50KB
|
|
36
|
-
allowNetworkAccess: false,
|
|
37
|
-
allowFileSystemAccess: false,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
constructor(private toolsService: ToolsService, private clients: AIClient) {
|
|
41
|
-
this.validateNodejsEnvironment();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Validate that Node.js environment is properly configured for isolated-vm
|
|
46
|
-
*/
|
|
47
|
-
private validateNodejsEnvironment(): void {
|
|
48
|
-
// Get Node.js version
|
|
49
|
-
const nodeVersion = process.version;
|
|
50
|
-
const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0], 10);
|
|
51
|
-
|
|
52
|
-
// Check if Node.js 20+ and --no-node-snapshot flag is required
|
|
53
|
-
if (majorVersion >= 20) {
|
|
54
|
-
const hasNoNodeSnapshot = process.execArgv.includes('--no-node-snapshot');
|
|
55
|
-
|
|
56
|
-
if (!hasNoNodeSnapshot) {
|
|
57
|
-
const errorMessage = [
|
|
58
|
-
`Node.js ${nodeVersion} detected. The executeScript tool requires the --no-node-snapshot flag for isolated-vm compatibility.`,
|
|
59
|
-
'',
|
|
60
|
-
'This flag is automatically included when running knowhow commands via the CLI (e.g., `knowhow agent`, `knowhow chat`).',
|
|
61
|
-
'',
|
|
62
|
-
'If you are programmatically using knowhow or running custom scripts:',
|
|
63
|
-
'1. Start your application with: node --no-node-snapshot your-app.js',
|
|
64
|
-
'2. Or update your package.json scripts to include the flag:',
|
|
65
|
-
' "scripts": {',
|
|
66
|
-
' "start": "node --no-node-snapshot dist/index.js"',
|
|
67
|
-
' }',
|
|
68
|
-
'',
|
|
69
|
-
'Note: This flag is required for Node.js 20+ to ensure isolated-vm works correctly.'
|
|
70
|
-
].join('\n');
|
|
71
|
-
|
|
72
|
-
throw new Error(errorMessage);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Execute a TypeScript script in sandbox
|
|
79
|
-
*/
|
|
80
|
-
async execute(request: ExecutionRequest): Promise<ExecutionResult> {
|
|
81
|
-
const tracer = new ScriptTracer();
|
|
82
|
-
const quotas = { ...this.defaultQuotas, ...request.quotas };
|
|
83
|
-
const policy = { ...this.defaultPolicy, ...request.policy };
|
|
84
|
-
const policyEnforcer = new ScriptPolicyEnforcer(quotas, policy);
|
|
85
|
-
|
|
86
|
-
tracer.emitEvent("execution_start", {
|
|
87
|
-
scriptLength: request.script.length,
|
|
88
|
-
quotas,
|
|
89
|
-
policy: {
|
|
90
|
-
...policy,
|
|
91
|
-
// Don't log the full tool lists
|
|
92
|
-
allowlistedTools: `${policy.allowlistedTools.length} tools`,
|
|
93
|
-
denylistedTools: `${policy.denylistedTools.length} tools`,
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
// Validate script
|
|
99
|
-
const validation = policyEnforcer.validateScript(request.script, policy.allowNetworkAccess);
|
|
100
|
-
if (!validation.valid) {
|
|
101
|
-
tracer.emitEvent("script_validation_failed", {
|
|
102
|
-
issues: validation.issues,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
success: false,
|
|
107
|
-
error: `Script validation failed: ${validation.issues.join(", ")}`,
|
|
108
|
-
result: null,
|
|
109
|
-
trace: tracer.getTrace(),
|
|
110
|
-
artifacts: [],
|
|
111
|
-
consoleOutput: [],
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
tracer.emitEvent("script_validation_passed", {});
|
|
116
|
-
|
|
117
|
-
// Create sandbox context
|
|
118
|
-
const context = new SandboxContext(
|
|
119
|
-
this.toolsService,
|
|
120
|
-
this.clients,
|
|
121
|
-
tracer,
|
|
122
|
-
policyEnforcer
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
// Execute script with timeout
|
|
126
|
-
const startTime = Date.now();
|
|
127
|
-
const timeoutMs = quotas.maxExecutionTimeMs;
|
|
128
|
-
|
|
129
|
-
const result = await this.executeWithTimeout(
|
|
130
|
-
request.script,
|
|
131
|
-
context,
|
|
132
|
-
timeoutMs,
|
|
133
|
-
tracer,
|
|
134
|
-
policyEnforcer
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
const executionTime = Date.now() - startTime;
|
|
138
|
-
tracer.emitEvent("execution_complete", {
|
|
139
|
-
executionTimeMs: executionTime,
|
|
140
|
-
finalUsage: policyEnforcer.getUsage(),
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
success: true,
|
|
145
|
-
error: null,
|
|
146
|
-
result,
|
|
147
|
-
trace: tracer.getTrace(),
|
|
148
|
-
artifacts: context.getArtifacts(),
|
|
149
|
-
consoleOutput: context.getConsoleOutput(),
|
|
150
|
-
};
|
|
151
|
-
} catch (error) {
|
|
152
|
-
const errorMessage =
|
|
153
|
-
error instanceof Error ? error.message : String(error);
|
|
154
|
-
|
|
155
|
-
tracer.emitEvent("execution_error", {
|
|
156
|
-
error: errorMessage,
|
|
157
|
-
finalUsage: policyEnforcer.getUsage(),
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
success: false,
|
|
162
|
-
error: errorMessage,
|
|
163
|
-
result: null,
|
|
164
|
-
trace: tracer.getTrace(),
|
|
165
|
-
artifacts: [],
|
|
166
|
-
consoleOutput: [],
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Execute script with timeout protection
|
|
173
|
-
*/
|
|
174
|
-
private async executeWithTimeout(
|
|
175
|
-
script: string,
|
|
176
|
-
context: SandboxContext,
|
|
177
|
-
timeoutMs: number,
|
|
178
|
-
tracer: ScriptTracer,
|
|
179
|
-
policyEnforcer: ScriptPolicyEnforcer
|
|
180
|
-
): Promise<any> {
|
|
181
|
-
return new Promise((resolve, reject) => {
|
|
182
|
-
const timeoutId = setTimeout(() => {
|
|
183
|
-
tracer.emitEvent("execution_timeout", { timeoutMs });
|
|
184
|
-
reject(new Error(`Script execution timed out after ${timeoutMs}ms`));
|
|
185
|
-
}, timeoutMs);
|
|
186
|
-
|
|
187
|
-
// Use isolated-vm for secure execution
|
|
188
|
-
this.executeScriptSecure(script, context, tracer, policyEnforcer)
|
|
189
|
-
.then((result) => {
|
|
190
|
-
clearTimeout(timeoutId);
|
|
191
|
-
resolve(result);
|
|
192
|
-
})
|
|
193
|
-
.catch((error) => {
|
|
194
|
-
clearTimeout(timeoutId);
|
|
195
|
-
reject(error);
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Secure script execution using isolated-vm
|
|
202
|
-
*/
|
|
203
|
-
private async executeScriptSecure(
|
|
204
|
-
script: string,
|
|
205
|
-
context: SandboxContext,
|
|
206
|
-
tracer: ScriptTracer,
|
|
207
|
-
policyEnforcer: ScriptPolicyEnforcer
|
|
208
|
-
) {
|
|
209
|
-
tracer.emitEvent("secure_execution_start", {
|
|
210
|
-
note: "Using isolated-vm for secure execution",
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// Create isolated VM instance with memory limit
|
|
214
|
-
const isolate = new ivm.Isolate({
|
|
215
|
-
memoryLimit: policyEnforcer.getQuotas().maxMemoryMb,
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
// Create new context within the isolate
|
|
220
|
-
const vmContext = await isolate.createContext();
|
|
221
|
-
|
|
222
|
-
tracer.emitEvent("vm_context_created", {});
|
|
223
|
-
|
|
224
|
-
// Set up the global environment in the isolated context
|
|
225
|
-
await this.setupIsolatedContext(vmContext, context, tracer);
|
|
226
|
-
|
|
227
|
-
tracer.emitEvent("script_compilation_start", {});
|
|
228
|
-
|
|
229
|
-
// Compile the script.
|
|
230
|
-
// Many scripts follow the pattern: define functions, then call `main();` at the end.
|
|
231
|
-
// The IIFE wrapper captures the *return value* of the function body, but `main();`
|
|
232
|
-
// is an expression statement — it doesn't return anything from the IIFE.
|
|
233
|
-
// We fix this by rewriting the last bare expression-statement into `return <expr>;`
|
|
234
|
-
// so that the script's return value (e.g. the object from main()) is captured.
|
|
235
|
-
const scriptWithReturn = this.injectReturnForLastExpression(script);
|
|
236
|
-
|
|
237
|
-
const wrappedScript = `
|
|
238
|
-
(async function() {
|
|
239
|
-
"use strict";
|
|
240
|
-
${scriptWithReturn}
|
|
241
|
-
})()
|
|
242
|
-
`;
|
|
243
|
-
|
|
244
|
-
const compiledScript = await isolate.compileScript(wrappedScript);
|
|
245
|
-
|
|
246
|
-
tracer.emitEvent("script_compilation_complete", {});
|
|
247
|
-
tracer.emitEvent("script_execution_start", {});
|
|
248
|
-
|
|
249
|
-
// Execute the script and get the result
|
|
250
|
-
// Note: do NOT set timeout here — it kills the isolate while awaiting host async promises.
|
|
251
|
-
// The outer executeWithTimeout wrapper handles wall-clock timeout instead.
|
|
252
|
-
const result = await compiledScript.run(vmContext, {
|
|
253
|
-
promise: true,
|
|
254
|
-
copy: true,
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
tracer.emitEvent("script_execution_complete", {
|
|
258
|
-
resultType: typeof result,
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
return result;
|
|
262
|
-
} finally {
|
|
263
|
-
// Clean up the isolate
|
|
264
|
-
isolate.dispose();
|
|
265
|
-
tracer.emitEvent("vm_cleanup_complete", {});
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Set up the isolated context with safe globals and sandbox functions
|
|
271
|
-
*/
|
|
272
|
-
private async setupIsolatedContext(
|
|
273
|
-
vmContext: ivm.Context,
|
|
274
|
-
sandboxContext: SandboxContext,
|
|
275
|
-
tracer: ScriptTracer
|
|
276
|
-
): Promise<void> {
|
|
277
|
-
tracer.emitEvent("context_setup_start", {});
|
|
278
|
-
|
|
279
|
-
const globalRef = vmContext.global;
|
|
280
|
-
await globalRef.set("globalThis", globalRef.derefInto());
|
|
281
|
-
|
|
282
|
-
// Helper function to expose async host functions
|
|
283
|
-
const exposeAsync = async (
|
|
284
|
-
name: string,
|
|
285
|
-
fn: (...a: any[]) => Promise<any>
|
|
286
|
-
) => {
|
|
287
|
-
await globalRef.set(
|
|
288
|
-
`__host_${name}`,
|
|
289
|
-
new ivm.Reference(async (...args: any[]) => {
|
|
290
|
-
const result = await fn(...args);
|
|
291
|
-
const safeResult = result !== undefined ? result : null;
|
|
292
|
-
const plainResult =
|
|
293
|
-
safeResult !== null && typeof safeResult === 'object'
|
|
294
|
-
? JSON.parse(JSON.stringify(safeResult))
|
|
295
|
-
: safeResult;
|
|
296
|
-
// copyInto() transfers the value into the isolate heap so it's directly usable
|
|
297
|
-
return new ivm.ExternalCopy(plainResult).copyInto();
|
|
298
|
-
})
|
|
299
|
-
);
|
|
300
|
-
// Use applySyncPromise so the script isolate suspends and yields the Node.js event loop
|
|
301
|
-
// back to the host while waiting for async host operations (MCP stdio calls etc.).
|
|
302
|
-
// Without this, the ivm isolate blocks the event loop and stdio-based MCP transports
|
|
303
|
-
// can never deliver their responses → deadlock.
|
|
304
|
-
await vmContext.eval(`
|
|
305
|
-
globalThis.${name} = (...a) =>
|
|
306
|
-
new Promise((resolve, reject) => {
|
|
307
|
-
try {
|
|
308
|
-
// applySyncPromise does not support result options — the Reference fn returns ExternalCopy
|
|
309
|
-
const result = __host_${name}.applySyncPromise(undefined, a,
|
|
310
|
-
{ arguments: { copy: true } });
|
|
311
|
-
resolve(result);
|
|
312
|
-
} catch(e) { reject(e); }
|
|
313
|
-
});
|
|
314
|
-
`);
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
// Helper function to expose sync host functions
|
|
318
|
-
const exposeSync = async (name: string, fn: (...a: any[]) => any) => {
|
|
319
|
-
await globalRef.set(
|
|
320
|
-
`__host_${name}`,
|
|
321
|
-
new ivm.Reference((...args: any[]) => {
|
|
322
|
-
const result = fn(...args);
|
|
323
|
-
return new ivm.ExternalCopy(result).copyInto();
|
|
324
|
-
})
|
|
325
|
-
);
|
|
326
|
-
await vmContext.eval(`
|
|
327
|
-
globalThis.${name} = (...a) =>
|
|
328
|
-
__host_${name}.apply(undefined, a,
|
|
329
|
-
{ arguments: { copy: true }, result: { copy: true } });
|
|
330
|
-
`);
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
// Expose async sandbox functions
|
|
334
|
-
await exposeAsync("callTool", async (tool, params) => {
|
|
335
|
-
try {
|
|
336
|
-
const result = await sandboxContext.callTool(tool as string, params);
|
|
337
|
-
const { functionResp } = result;
|
|
338
|
-
return functionResp !== undefined ? functionResp : null;
|
|
339
|
-
} catch (err) {
|
|
340
|
-
throw err;
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
await exposeAsync("llm", (messages, options) =>
|
|
344
|
-
sandboxContext.llm(messages, options || {})
|
|
345
|
-
);
|
|
346
|
-
await exposeAsync("sleep", (ms) => sandboxContext.sleep(ms));
|
|
347
|
-
|
|
348
|
-
// Expose sync sandbox functions
|
|
349
|
-
await exposeSync("createArtifact", (name, content, type) =>
|
|
350
|
-
sandboxContext.createArtifact(name as string, content, type)
|
|
351
|
-
);
|
|
352
|
-
await exposeSync("getQuotaUsage", () => sandboxContext.getQuotaUsage());
|
|
353
|
-
|
|
354
|
-
// Set up console bridging with individual function references
|
|
355
|
-
for (const level of ["log", "info", "warn", "error"] as const) {
|
|
356
|
-
await globalRef.set(
|
|
357
|
-
`__console_${level}`,
|
|
358
|
-
new ivm.Reference((...args: any[]) =>
|
|
359
|
-
sandboxContext.console[level](...args)
|
|
360
|
-
)
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
await vmContext.eval(`
|
|
364
|
-
globalThis.console = {};
|
|
365
|
-
for (const lvl of ["log", "info", "warn", "error"]) {
|
|
366
|
-
globalThis.console[lvl] = (...a) =>
|
|
367
|
-
globalThis["__console_" + lvl].apply(undefined, a,
|
|
368
|
-
{ arguments: { copy: true } });
|
|
369
|
-
}
|
|
370
|
-
`);
|
|
371
|
-
|
|
372
|
-
tracer.emitEvent("context_setup_complete", {});
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Legacy fallback execution method
|
|
377
|
-
*/
|
|
378
|
-
private async executeScriptFallback(
|
|
379
|
-
script: string,
|
|
380
|
-
context: SandboxContext,
|
|
381
|
-
tracer: ScriptTracer,
|
|
382
|
-
policyEnforcer: ScriptPolicyEnforcer
|
|
383
|
-
): Promise<any> {
|
|
384
|
-
// This is a fallback method that could use vm2 or other sandboxing
|
|
385
|
-
throw new Error("Isolated-vm execution failed, no fallback available");
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Get default quotas
|
|
390
|
-
*/
|
|
391
|
-
getDefaultQuotas(): ResourceQuotas {
|
|
392
|
-
return { ...this.defaultQuotas };
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Get default policy
|
|
397
|
-
*/
|
|
398
|
-
getDefaultPolicy(): SecurityPolicy {
|
|
399
|
-
return { ...this.defaultPolicy };
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Rewrite the last bare expression-statement in a script to use `return` so
|
|
405
|
-
* that the value propagates out of the IIFE wrapper.
|
|
406
|
-
*
|
|
407
|
-
* For example:
|
|
408
|
-
* main(); → return main();
|
|
409
|
-
* main() → return main()
|
|
410
|
-
* someExpr; → return someExpr;
|
|
411
|
-
*
|
|
412
|
-
* We only transform the last non-empty, non-comment line that looks like a
|
|
413
|
-
* plain expression statement (i.e. does NOT start with keywords that aren't
|
|
414
|
-
* valid in expression position: `function`, `class`, `const`, `let`, `var`,
|
|
415
|
-
* `if`, `for`, `while`, `do`, `switch`, `try`, `return`, `throw`, `break`,
|
|
416
|
-
* `continue`, `import`, `export`, `{`).
|
|
417
|
-
*/
|
|
418
|
-
private injectReturnForLastExpression(script: string): string {
|
|
419
|
-
const lines = script.split('\n');
|
|
420
|
-
|
|
421
|
-
// Walk backwards to find the last non-empty, non-comment line
|
|
422
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
423
|
-
const trimmed = lines[i].trim();
|
|
424
|
-
if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Skip lines that start with statement keywords — these can't be returned
|
|
429
|
-
const statementKeywords = /^(function\s|class\s|const\s|let\s|var\s|if\s*[(]|for\s*[(]|while\s*[(]|do\s*[{]|switch\s*[(]|try\s*[{]|return\s|throw\s|break;|continue;|import\s|export\s|[{])/;
|
|
430
|
-
if (statementKeywords.test(trimmed)) {
|
|
431
|
-
break; // last meaningful line is a statement — don't touch it
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// It looks like an expression statement — prepend `return`
|
|
435
|
-
lines[i] = lines[i].replace(trimmed, `return ${trimmed}`);
|
|
436
|
-
return lines.join('\n');
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// No suitable last expression found — return script unchanged
|
|
440
|
-
return script;
|
|
441
|
-
}}
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ResourceQuotas,
|
|
3
|
-
SecurityPolicy,
|
|
4
|
-
QuotaUsage,
|
|
5
|
-
PolicyViolation
|
|
6
|
-
} from './types';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Enforces security policies and resource quotas for script execution
|
|
10
|
-
*/
|
|
11
|
-
export class ScriptPolicyEnforcer {
|
|
12
|
-
private usage: QuotaUsage;
|
|
13
|
-
private violations: PolicyViolation[] = [];
|
|
14
|
-
private complexityLimit: number = 150; // Arbitrary limit for script complexity
|
|
15
|
-
|
|
16
|
-
constructor(
|
|
17
|
-
private quotas: ResourceQuotas,
|
|
18
|
-
private policy: SecurityPolicy,
|
|
19
|
-
) {
|
|
20
|
-
this.usage = {
|
|
21
|
-
toolCalls: 0,
|
|
22
|
-
tokens: 0,
|
|
23
|
-
executionTimeMs: 0,
|
|
24
|
-
costUsd: 0
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Check if a tool call is allowed
|
|
30
|
-
*/
|
|
31
|
-
checkToolCall(toolName: string): boolean {
|
|
32
|
-
// Check if tool is in denylist
|
|
33
|
-
if (this.policy.denylistedTools && this.policy.denylistedTools.includes(toolName)) {
|
|
34
|
-
this.recordViolation('tool_denied', `Tool '${toolName}' is in denylist`);
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Check if tool is in allowlist (if allowlist is defined and not empty)
|
|
39
|
-
if (this.policy.allowlistedTools && this.policy.allowlistedTools.length > 0 &&
|
|
40
|
-
!this.policy.allowlistedTools.includes(toolName)) {
|
|
41
|
-
this.recordViolation('tool_not_allowed', `Tool '${toolName}' is not in allowlist`);
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Check quota
|
|
46
|
-
if (this.usage.toolCalls >= this.quotas.maxToolCalls) {
|
|
47
|
-
this.recordViolation('quota_exceeded', 'Maximum tool calls exceeded');
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Record a tool call
|
|
56
|
-
*/
|
|
57
|
-
recordToolCall(): void {
|
|
58
|
-
this.usage.toolCalls++;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Check if token usage is allowed
|
|
63
|
-
*/
|
|
64
|
-
checkTokenUsage(tokens: number): boolean {
|
|
65
|
-
if (this.usage.tokens + tokens > this.quotas.maxTokens) {
|
|
66
|
-
this.recordViolation('quota_exceeded', 'Maximum tokens would be exceeded');
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Record token usage
|
|
74
|
-
*/
|
|
75
|
-
recordTokenUsage(tokens: number): void {
|
|
76
|
-
this.usage.tokens += tokens;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Check if execution time limit is exceeded
|
|
81
|
-
*/
|
|
82
|
-
checkExecutionTime(currentTimeMs: number): boolean {
|
|
83
|
-
if (currentTimeMs > this.quotas.maxExecutionTimeMs) {
|
|
84
|
-
this.recordViolation('quota_exceeded', 'Maximum execution time exceeded');
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
this.usage.executionTimeMs = currentTimeMs;
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Check if cost limit is exceeded
|
|
93
|
-
*/
|
|
94
|
-
checkCost(additionalCost: number): boolean {
|
|
95
|
-
if (this.usage.costUsd + additionalCost > this.quotas.maxCostUsd) {
|
|
96
|
-
this.recordViolation('quota_exceeded', 'Maximum cost would be exceeded');
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Record cost usage
|
|
104
|
-
*/
|
|
105
|
-
recordCost(cost: number): void {
|
|
106
|
-
this.usage.costUsd += cost;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Get current usage
|
|
111
|
-
*/
|
|
112
|
-
getUsage(): QuotaUsage {
|
|
113
|
-
return { ...this.usage };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Get current quotas
|
|
118
|
-
*/
|
|
119
|
-
getQuotas(): ResourceQuotas {
|
|
120
|
-
return { ...this.quotas };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Get all policy violations
|
|
125
|
-
*/
|
|
126
|
-
getViolations(): PolicyViolation[] {
|
|
127
|
-
return [...this.violations];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Check if there are any violations
|
|
132
|
-
*/
|
|
133
|
-
hasViolations(): boolean {
|
|
134
|
-
return this.violations.length > 0;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Get the most recent violation
|
|
139
|
-
*/
|
|
140
|
-
getLastViolation(): PolicyViolation | undefined {
|
|
141
|
-
return this.violations[this.violations.length - 1];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Reset usage counters
|
|
146
|
-
*/
|
|
147
|
-
resetUsage(): void {
|
|
148
|
-
this.usage = {
|
|
149
|
-
toolCalls: 0,
|
|
150
|
-
tokens: 0,
|
|
151
|
-
executionTimeMs: 0,
|
|
152
|
-
costUsd: 0
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Reset violations
|
|
158
|
-
*/
|
|
159
|
-
resetViolations(): void {
|
|
160
|
-
this.violations = [];
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Validate script content for security issues
|
|
165
|
-
*/
|
|
166
|
-
validateScript(scriptContent: string, allowNetworkAccess?: boolean): { valid: boolean; issues: string[] } {
|
|
167
|
-
const issues: string[] = [];
|
|
168
|
-
|
|
169
|
-
// Check script length
|
|
170
|
-
if (scriptContent.length > this.policy.maxScriptLength) {
|
|
171
|
-
issues.push(`Script too long: ${scriptContent.length} > ${this.policy.maxScriptLength}`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
valid: issues.length === 0,
|
|
176
|
-
issues
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Record a policy violation
|
|
182
|
-
*/
|
|
183
|
-
private recordViolation(type: 'quota_exceeded' | 'tool_denied' | 'tool_not_allowed' | 'script_validation', message: string): void {
|
|
184
|
-
const violation: PolicyViolation = {
|
|
185
|
-
id: `violation-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
186
|
-
type,
|
|
187
|
-
message,
|
|
188
|
-
timestamp: Date.now(),
|
|
189
|
-
usage: { ...this.usage }
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
this.violations.push(violation);
|
|
193
|
-
}
|
|
194
|
-
}
|