@tyvm/knowhow 0.0.21 → 0.0.22
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/package.json +3 -1
- package/src/agents/tools/executeScript/README.md +78 -0
- package/src/agents/tools/executeScript/definition.ts +73 -0
- package/src/agents/tools/executeScript/examples/quick-test.ts +80 -0
- package/src/agents/tools/executeScript/examples/serialization-test.ts +309 -0
- package/src/agents/tools/executeScript/examples/test-runner.ts +204 -0
- package/src/agents/tools/executeScript/index.ts +74 -0
- package/src/agents/tools/index.ts +1 -0
- package/src/agents/tools/list.ts +2 -1
- package/src/cli.ts +2 -6
- package/src/clients/index.ts +23 -9
- package/src/services/Tools.ts +19 -3
- package/src/services/script-execution/SandboxContext.ts +278 -0
- package/src/services/script-execution/ScriptExecutor.ts +337 -0
- package/src/services/script-execution/ScriptPolicy.ts +236 -0
- package/src/services/script-execution/ScriptTracer.ts +249 -0
- package/src/services/script-execution/types.ts +134 -0
- package/ts_build/src/agents/tools/executeScript/definition.d.ts +2 -0
- package/ts_build/src/agents/tools/executeScript/definition.js +70 -0
- package/ts_build/src/agents/tools/executeScript/definition.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +3 -0
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +68 -0
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +15 -0
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +267 -0
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/simple-example.d.ts +20 -0
- package/ts_build/src/agents/tools/executeScript/examples/simple-example.js +35 -0
- package/ts_build/src/agents/tools/executeScript/examples/simple-example.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +4 -0
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +202 -0
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/handler.d.ts +27 -0
- package/ts_build/src/agents/tools/executeScript/handler.js +64 -0
- package/ts_build/src/agents/tools/executeScript/handler.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript/index.d.ts +27 -0
- package/ts_build/src/agents/tools/executeScript/index.js +64 -0
- package/ts_build/src/agents/tools/executeScript/index.js.map +1 -0
- package/ts_build/src/agents/tools/executeScript.d.ts +29 -0
- package/ts_build/src/agents/tools/executeScript.js +124 -0
- package/ts_build/src/agents/tools/executeScript.js.map +1 -0
- package/ts_build/src/agents/tools/index.d.ts +1 -0
- package/ts_build/src/agents/tools/index.js +1 -0
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +2 -0
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/cli.js +2 -6
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +9 -2
- package/ts_build/src/clients/index.js +17 -4
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/services/Tools.d.ts +3 -0
- package/ts_build/src/services/Tools.js +10 -2
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/services/script-execution/SandboxContext.d.ts +34 -0
- package/ts_build/src/services/script-execution/SandboxContext.js +188 -0
- package/ts_build/src/services/script-execution/SandboxContext.js.map +1 -0
- package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +17 -0
- package/ts_build/src/services/script-execution/ScriptExecutor.js +207 -0
- package/ts_build/src/services/script-execution/ScriptExecutor.js.map +1 -0
- package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +27 -0
- package/ts_build/src/services/script-execution/ScriptPolicy.js +150 -0
- package/ts_build/src/services/script-execution/ScriptPolicy.js.map +1 -0
- package/ts_build/src/services/script-execution/ScriptTracer.d.ts +19 -0
- package/ts_build/src/services/script-execution/ScriptTracer.js +186 -0
- package/ts_build/src/services/script-execution/ScriptTracer.js.map +1 -0
- package/ts_build/src/services/script-execution/types.d.ts +108 -0
- package/ts_build/src/services/script-execution/types.js +3 -0
- package/ts_build/src/services/script-execution/types.js.map +1 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import ivm from "isolated-vm";
|
|
2
|
+
import { Tools } from "../../services";
|
|
3
|
+
import { 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
|
+
"execCommand", // Dangerous system commands
|
|
31
|
+
"writeFileChunk", // File system write access
|
|
32
|
+
"patchFile", // File system modification
|
|
33
|
+
],
|
|
34
|
+
maxScriptLength: 50000, // 50KB
|
|
35
|
+
allowNetworkAccess: false,
|
|
36
|
+
allowFileSystemAccess: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
private toolsService: typeof Tools | null = null,
|
|
41
|
+
private clients: typeof Clients | null = null
|
|
42
|
+
) {}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Execute a TypeScript script in sandbox
|
|
46
|
+
*/
|
|
47
|
+
async execute(request: ExecutionRequest): Promise<ExecutionResult> {
|
|
48
|
+
const tracer = new ScriptTracer();
|
|
49
|
+
const quotas = { ...this.defaultQuotas, ...request.quotas };
|
|
50
|
+
const policy = { ...this.defaultPolicy, ...request.policy };
|
|
51
|
+
const policyEnforcer = new ScriptPolicyEnforcer(quotas, policy);
|
|
52
|
+
|
|
53
|
+
tracer.emitEvent("execution_start", {
|
|
54
|
+
scriptLength: request.script.length,
|
|
55
|
+
quotas,
|
|
56
|
+
policy: {
|
|
57
|
+
...policy,
|
|
58
|
+
// Don't log the full tool lists
|
|
59
|
+
allowlistedTools: `${policy.allowlistedTools.length} tools`,
|
|
60
|
+
denylistedTools: `${policy.denylistedTools.length} tools`,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Validate script
|
|
66
|
+
const validation = policyEnforcer.validateScript(request.script);
|
|
67
|
+
if (!validation.valid) {
|
|
68
|
+
tracer.emitEvent("script_validation_failed", {
|
|
69
|
+
issues: validation.issues,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
error: `Script validation failed: ${validation.issues.join(", ")}`,
|
|
75
|
+
result: null,
|
|
76
|
+
trace: tracer.getTrace(),
|
|
77
|
+
artifacts: [],
|
|
78
|
+
consoleOutput: [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
tracer.emitEvent("script_validation_passed", {});
|
|
83
|
+
|
|
84
|
+
// Create sandbox context
|
|
85
|
+
const context = new SandboxContext(
|
|
86
|
+
this.toolsService,
|
|
87
|
+
this.clients,
|
|
88
|
+
tracer,
|
|
89
|
+
policyEnforcer
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Execute script with timeout
|
|
93
|
+
const startTime = Date.now();
|
|
94
|
+
const timeoutMs = quotas.maxExecutionTimeMs;
|
|
95
|
+
|
|
96
|
+
const result = await this.executeWithTimeout(
|
|
97
|
+
request.script,
|
|
98
|
+
context,
|
|
99
|
+
timeoutMs,
|
|
100
|
+
tracer,
|
|
101
|
+
policyEnforcer
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const executionTime = Date.now() - startTime;
|
|
105
|
+
tracer.emitEvent("execution_complete", {
|
|
106
|
+
executionTimeMs: executionTime,
|
|
107
|
+
finalUsage: policyEnforcer.getUsage(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
success: true,
|
|
112
|
+
error: null,
|
|
113
|
+
result,
|
|
114
|
+
trace: tracer.getTrace(),
|
|
115
|
+
artifacts: context.getArtifacts(),
|
|
116
|
+
consoleOutput: context.getConsoleOutput(),
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
const errorMessage =
|
|
120
|
+
error instanceof Error ? error.message : String(error);
|
|
121
|
+
|
|
122
|
+
tracer.emitEvent("execution_error", {
|
|
123
|
+
error: errorMessage,
|
|
124
|
+
finalUsage: policyEnforcer.getUsage(),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
error: errorMessage,
|
|
130
|
+
result: null,
|
|
131
|
+
trace: tracer.getTrace(),
|
|
132
|
+
artifacts: [],
|
|
133
|
+
consoleOutput: [],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Execute script with timeout protection
|
|
140
|
+
*/
|
|
141
|
+
private async executeWithTimeout(
|
|
142
|
+
script: string,
|
|
143
|
+
context: SandboxContext,
|
|
144
|
+
timeoutMs: number,
|
|
145
|
+
tracer: ScriptTracer,
|
|
146
|
+
policyEnforcer: ScriptPolicyEnforcer
|
|
147
|
+
): Promise<any> {
|
|
148
|
+
return new Promise((resolve, reject) => {
|
|
149
|
+
const timeoutId = setTimeout(() => {
|
|
150
|
+
tracer.emitEvent("execution_timeout", { timeoutMs });
|
|
151
|
+
reject(new Error(`Script execution timed out after ${timeoutMs}ms`));
|
|
152
|
+
}, timeoutMs);
|
|
153
|
+
|
|
154
|
+
// Use isolated-vm for secure execution
|
|
155
|
+
this.executeScriptSecure(script, context, tracer, policyEnforcer)
|
|
156
|
+
.then((result) => {
|
|
157
|
+
clearTimeout(timeoutId);
|
|
158
|
+
resolve(result);
|
|
159
|
+
})
|
|
160
|
+
.catch((error) => {
|
|
161
|
+
clearTimeout(timeoutId);
|
|
162
|
+
reject(error);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Secure script execution using isolated-vm
|
|
169
|
+
*/
|
|
170
|
+
private async executeScriptSecure(
|
|
171
|
+
script: string,
|
|
172
|
+
context: SandboxContext,
|
|
173
|
+
tracer: ScriptTracer,
|
|
174
|
+
policyEnforcer: ScriptPolicyEnforcer
|
|
175
|
+
): Promise<any> {
|
|
176
|
+
tracer.emitEvent("secure_execution_start", {
|
|
177
|
+
note: "Using isolated-vm for secure execution",
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Create isolated VM instance with memory limit
|
|
181
|
+
const isolate = new ivm.Isolate({
|
|
182
|
+
memoryLimit: policyEnforcer.getQuotas().maxMemoryMb,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
// Create new context within the isolate
|
|
187
|
+
const vmContext = await isolate.createContext();
|
|
188
|
+
|
|
189
|
+
tracer.emitEvent("vm_context_created", {});
|
|
190
|
+
|
|
191
|
+
// Set up the global environment in the isolated context
|
|
192
|
+
await this.setupIsolatedContext(vmContext, context, tracer);
|
|
193
|
+
|
|
194
|
+
tracer.emitEvent("script_compilation_start", {});
|
|
195
|
+
|
|
196
|
+
// Compile the script
|
|
197
|
+
const wrappedScript = `
|
|
198
|
+
(async function() {
|
|
199
|
+
"use strict";
|
|
200
|
+
${script}
|
|
201
|
+
})()
|
|
202
|
+
`;
|
|
203
|
+
|
|
204
|
+
const compiledScript = await isolate.compileScript(wrappedScript);
|
|
205
|
+
|
|
206
|
+
tracer.emitEvent("script_compilation_complete", {});
|
|
207
|
+
tracer.emitEvent("script_execution_start", {});
|
|
208
|
+
|
|
209
|
+
// Execute the script and get the result
|
|
210
|
+
const result = await compiledScript.run(vmContext, {
|
|
211
|
+
timeout: policyEnforcer.getQuotas().maxExecutionTimeMs,
|
|
212
|
+
promise: true,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
tracer.emitEvent("script_execution_complete", {
|
|
216
|
+
resultType: typeof result,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return result;
|
|
220
|
+
} finally {
|
|
221
|
+
// Clean up the isolate
|
|
222
|
+
isolate.dispose();
|
|
223
|
+
tracer.emitEvent("vm_cleanup_complete", {});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Set up the isolated context with safe globals and sandbox functions
|
|
229
|
+
*/
|
|
230
|
+
private async setupIsolatedContext(
|
|
231
|
+
vmContext: ivm.Context,
|
|
232
|
+
sandboxContext: SandboxContext,
|
|
233
|
+
tracer: ScriptTracer
|
|
234
|
+
): Promise<void> {
|
|
235
|
+
tracer.emitEvent("context_setup_start", {});
|
|
236
|
+
|
|
237
|
+
const globalRef = vmContext.global;
|
|
238
|
+
await globalRef.set("globalThis", globalRef.derefInto());
|
|
239
|
+
|
|
240
|
+
// Helper function to expose async host functions
|
|
241
|
+
const exposeAsync = async (
|
|
242
|
+
name: string,
|
|
243
|
+
fn: (...a: any[]) => Promise<any>
|
|
244
|
+
) => {
|
|
245
|
+
await globalRef.set(
|
|
246
|
+
`__host_${name}`,
|
|
247
|
+
new ivm.Reference(async (...args: any[]) => {
|
|
248
|
+
const result = await fn(...args);
|
|
249
|
+
return new ivm.ExternalCopy(result).copyInto();
|
|
250
|
+
})
|
|
251
|
+
);
|
|
252
|
+
await vmContext.eval(`
|
|
253
|
+
globalThis.${name} = (...a) =>
|
|
254
|
+
__host_${name}.apply(undefined, a,
|
|
255
|
+
{ arguments: { copy: true }, result: { promise: true, copy: true } });
|
|
256
|
+
`);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// Helper function to expose sync host functions
|
|
260
|
+
const exposeSync = async (name: string, fn: (...a: any[]) => any) => {
|
|
261
|
+
await globalRef.set(
|
|
262
|
+
`__host_${name}`,
|
|
263
|
+
new ivm.Reference((...args: any[]) => {
|
|
264
|
+
const result = fn(...args);
|
|
265
|
+
return new ivm.ExternalCopy(result).copyInto();
|
|
266
|
+
})
|
|
267
|
+
);
|
|
268
|
+
await vmContext.eval(`
|
|
269
|
+
globalThis.${name} = (...a) =>
|
|
270
|
+
__host_${name}.apply(undefined, a,
|
|
271
|
+
{ arguments: { copy: true }, result: { copy: true } });
|
|
272
|
+
`);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// Expose async sandbox functions
|
|
276
|
+
await exposeAsync("callTool", (tool, params) =>
|
|
277
|
+
sandboxContext.callTool(tool as string, params)
|
|
278
|
+
);
|
|
279
|
+
await exposeAsync("llm", (messages, options) =>
|
|
280
|
+
sandboxContext.llm(messages, options || {})
|
|
281
|
+
);
|
|
282
|
+
await exposeAsync("sleep", (ms) => sandboxContext.sleep(ms));
|
|
283
|
+
|
|
284
|
+
// Expose sync sandbox functions
|
|
285
|
+
await exposeSync("createArtifact", (name, content, type) =>
|
|
286
|
+
sandboxContext.createArtifact(name as string, content, type)
|
|
287
|
+
);
|
|
288
|
+
await exposeSync("getQuotaUsage", () => sandboxContext.getQuotaUsage());
|
|
289
|
+
|
|
290
|
+
// Set up console bridging with individual function references
|
|
291
|
+
for (const level of ["log", "info", "warn", "error"] as const) {
|
|
292
|
+
await globalRef.set(
|
|
293
|
+
`__console_${level}`,
|
|
294
|
+
new ivm.Reference((...args: any[]) =>
|
|
295
|
+
sandboxContext.console[level](...args)
|
|
296
|
+
)
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
await vmContext.eval(`
|
|
300
|
+
globalThis.console = {};
|
|
301
|
+
for (const lvl of ["log", "info", "warn", "error"]) {
|
|
302
|
+
globalThis.console[lvl] = (...a) =>
|
|
303
|
+
globalThis["__console_" + lvl].apply(undefined, a,
|
|
304
|
+
{ arguments: { copy: true } });
|
|
305
|
+
}
|
|
306
|
+
`);
|
|
307
|
+
|
|
308
|
+
tracer.emitEvent("context_setup_complete", {});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Legacy fallback execution method
|
|
313
|
+
*/
|
|
314
|
+
private async executeScriptFallback(
|
|
315
|
+
script: string,
|
|
316
|
+
context: SandboxContext,
|
|
317
|
+
tracer: ScriptTracer,
|
|
318
|
+
policyEnforcer: ScriptPolicyEnforcer
|
|
319
|
+
): Promise<any> {
|
|
320
|
+
// This is a fallback method that could use vm2 or other sandboxing
|
|
321
|
+
throw new Error("Isolated-vm execution failed, no fallback available");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Get default quotas
|
|
326
|
+
*/
|
|
327
|
+
getDefaultQuotas(): ResourceQuotas {
|
|
328
|
+
return { ...this.defaultQuotas };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get default policy
|
|
333
|
+
*/
|
|
334
|
+
getDefaultPolicy(): SecurityPolicy {
|
|
335
|
+
return { ...this.defaultPolicy };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
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
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private quotas: ResourceQuotas,
|
|
17
|
+
private policy: SecurityPolicy
|
|
18
|
+
) {
|
|
19
|
+
this.usage = {
|
|
20
|
+
toolCalls: 0,
|
|
21
|
+
tokens: 0,
|
|
22
|
+
executionTimeMs: 0,
|
|
23
|
+
costUsd: 0
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a tool call is allowed
|
|
29
|
+
*/
|
|
30
|
+
checkToolCall(toolName: string): boolean {
|
|
31
|
+
// Check if tool is in denylist
|
|
32
|
+
if (this.policy.denylistedTools && this.policy.denylistedTools.includes(toolName)) {
|
|
33
|
+
this.recordViolation('tool_denied', `Tool '${toolName}' is in denylist`);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if tool is in allowlist (if allowlist is defined and not empty)
|
|
38
|
+
if (this.policy.allowlistedTools && this.policy.allowlistedTools.length > 0 &&
|
|
39
|
+
!this.policy.allowlistedTools.includes(toolName)) {
|
|
40
|
+
this.recordViolation('tool_not_allowed', `Tool '${toolName}' is not in allowlist`);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check quota
|
|
45
|
+
if (this.usage.toolCalls >= this.quotas.maxToolCalls) {
|
|
46
|
+
this.recordViolation('quota_exceeded', 'Maximum tool calls exceeded');
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Record a tool call
|
|
55
|
+
*/
|
|
56
|
+
recordToolCall(): void {
|
|
57
|
+
this.usage.toolCalls++;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if token usage is allowed
|
|
62
|
+
*/
|
|
63
|
+
checkTokenUsage(tokens: number): boolean {
|
|
64
|
+
if (this.usage.tokens + tokens > this.quotas.maxTokens) {
|
|
65
|
+
this.recordViolation('quota_exceeded', 'Maximum tokens would be exceeded');
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Record token usage
|
|
73
|
+
*/
|
|
74
|
+
recordTokenUsage(tokens: number): void {
|
|
75
|
+
this.usage.tokens += tokens;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if execution time limit is exceeded
|
|
80
|
+
*/
|
|
81
|
+
checkExecutionTime(currentTimeMs: number): boolean {
|
|
82
|
+
if (currentTimeMs > this.quotas.maxExecutionTimeMs) {
|
|
83
|
+
this.recordViolation('quota_exceeded', 'Maximum execution time exceeded');
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
this.usage.executionTimeMs = currentTimeMs;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if cost limit is exceeded
|
|
92
|
+
*/
|
|
93
|
+
checkCost(additionalCost: number): boolean {
|
|
94
|
+
if (this.usage.costUsd + additionalCost > this.quotas.maxCostUsd) {
|
|
95
|
+
this.recordViolation('quota_exceeded', 'Maximum cost would be exceeded');
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Record cost usage
|
|
103
|
+
*/
|
|
104
|
+
recordCost(cost: number): void {
|
|
105
|
+
this.usage.costUsd += cost;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get current usage
|
|
110
|
+
*/
|
|
111
|
+
getUsage(): QuotaUsage {
|
|
112
|
+
return { ...this.usage };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get current quotas
|
|
117
|
+
*/
|
|
118
|
+
getQuotas(): ResourceQuotas {
|
|
119
|
+
return { ...this.quotas };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get all policy violations
|
|
124
|
+
*/
|
|
125
|
+
getViolations(): PolicyViolation[] {
|
|
126
|
+
return [...this.violations];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if there are any violations
|
|
131
|
+
*/
|
|
132
|
+
hasViolations(): boolean {
|
|
133
|
+
return this.violations.length > 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get the most recent violation
|
|
138
|
+
*/
|
|
139
|
+
getLastViolation(): PolicyViolation | undefined {
|
|
140
|
+
return this.violations[this.violations.length - 1];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Reset usage counters
|
|
145
|
+
*/
|
|
146
|
+
resetUsage(): void {
|
|
147
|
+
this.usage = {
|
|
148
|
+
toolCalls: 0,
|
|
149
|
+
tokens: 0,
|
|
150
|
+
executionTimeMs: 0,
|
|
151
|
+
costUsd: 0
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Reset violations
|
|
157
|
+
*/
|
|
158
|
+
resetViolations(): void {
|
|
159
|
+
this.violations = [];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Validate script content for security issues
|
|
164
|
+
*/
|
|
165
|
+
validateScript(scriptContent: string): { valid: boolean; issues: string[] } {
|
|
166
|
+
const issues: string[] = [];
|
|
167
|
+
|
|
168
|
+
// Check for dangerous patterns
|
|
169
|
+
const dangerousPatterns = [
|
|
170
|
+
/require\s*\(/gi, // Node.js require
|
|
171
|
+
/import\s+.*\s+from/gi, // ES6 imports (should be handled by bundler)
|
|
172
|
+
/process\./gi, // Process access
|
|
173
|
+
/global\./gi, // Global object access
|
|
174
|
+
/eval\s*\(/gi, // eval calls
|
|
175
|
+
/Function\s*\(/gi, // Function constructor
|
|
176
|
+
/setTimeout/gi, // setTimeout
|
|
177
|
+
/setInterval/gi, // setInterval
|
|
178
|
+
/fetch\s*\(/gi, // Direct fetch calls
|
|
179
|
+
/XMLHttpRequest/gi, // XHR
|
|
180
|
+
/WebSocket/gi, // WebSocket
|
|
181
|
+
/location\./gi, // Location object
|
|
182
|
+
/document\./gi, // Document object
|
|
183
|
+
/window\./gi, // Window object
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
for (const pattern of dangerousPatterns) {
|
|
187
|
+
if (pattern.test(scriptContent)) {
|
|
188
|
+
issues.push(`Potentially dangerous pattern detected: ${pattern.source}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Check script length
|
|
193
|
+
if (scriptContent.length > this.policy.maxScriptLength) {
|
|
194
|
+
issues.push(`Script too long: ${scriptContent.length} > ${this.policy.maxScriptLength}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check for excessive complexity (rough heuristic)
|
|
198
|
+
const complexityIndicators = [
|
|
199
|
+
/for\s*\(/gi,
|
|
200
|
+
/while\s*\(/gi,
|
|
201
|
+
/function\s+\w+/gi,
|
|
202
|
+
/=>\s*{/gi,
|
|
203
|
+
/if\s*\(/gi,
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
let complexityScore = 0;
|
|
207
|
+
for (const indicator of complexityIndicators) {
|
|
208
|
+
const matches = scriptContent.match(indicator);
|
|
209
|
+
complexityScore += matches ? matches.length : 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (complexityScore > 50) {
|
|
213
|
+
issues.push(`Script complexity too high: ${complexityScore} constructs detected`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
valid: issues.length === 0,
|
|
218
|
+
issues
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Record a policy violation
|
|
224
|
+
*/
|
|
225
|
+
private recordViolation(type: 'quota_exceeded' | 'tool_denied' | 'tool_not_allowed' | 'script_validation', message: string): void {
|
|
226
|
+
const violation: PolicyViolation = {
|
|
227
|
+
id: `violation-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
228
|
+
type,
|
|
229
|
+
message,
|
|
230
|
+
timestamp: Date.now(),
|
|
231
|
+
usage: { ...this.usage }
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
this.violations.push(violation);
|
|
235
|
+
}
|
|
236
|
+
}
|