@williamzujkowski/live-coding-music-mcp 4.0.0 → 4.1.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 +202 -705
- package/dist/AudioAnalyzer.js +1294 -91
- package/dist/PatternStore.js +276 -23
- package/dist/StrudelController.js +388 -56
- package/dist/server/resources.js +27 -20
- package/dist/server/server.js +384 -76
- package/dist/server/tools/ai.js +124 -91
- package/dist/server/tools/analysis.js +168 -59
- package/dist/server/tools/capture.js +135 -24
- package/dist/server/tools/compose.js +26 -26
- package/dist/server/tools/diagnostics.js +20 -3
- package/dist/server/tools/editor.js +60 -6
- package/dist/server/tools/generate.js +38 -15
- package/dist/server/tools/history.js +30 -8
- package/dist/server/tools/playback.js +2 -1
- package/dist/server/tools/session.js +38 -6
- package/dist/server/tools/storage.js +179 -8
- package/dist/server/tools/transform.js +127 -43
- package/dist/server/tools/types.js +209 -14
- package/dist/services/AudioCaptureService.js +302 -33
- package/dist/services/AudioExportService.js +318 -0
- package/dist/services/BrowserOnlyFunctions.js +149 -0
- package/dist/services/EventDensityProbe.js +324 -0
- package/dist/services/GeminiService.js +221 -36
- package/dist/services/IsolatedEngineRunner.js +308 -0
- package/dist/services/IsolatedStrudelEngine.js +135 -0
- package/dist/services/LocalPatternEngine.js +16 -0
- package/dist/services/MIDIExportService.js +765 -71
- package/dist/services/MIDIImportService.js +474 -0
- package/dist/services/MusicTheory.js +85 -13
- package/dist/services/PatternGenerator.js +168 -85
- package/dist/services/PatternSandbox.js +359 -0
- package/dist/services/SessionManager.js +338 -65
- package/dist/services/StrudelEngine.js +218 -12
- package/dist/services/StrudelEngineHelpers.js +24 -8
- package/dist/services/StyleRegistry.js +204 -0
- package/dist/services/Transferable.js +114 -0
- package/dist/services/ai/AiTransport.js +56 -0
- package/dist/services/ai/AudioMeasurements.js +155 -0
- package/dist/services/ai/CliTransport.js +256 -0
- package/dist/services/engineChild.js +83 -0
- package/dist/services/engineChildPath.js +28 -0
- package/dist/utils/CategorisedError.js +59 -0
- package/dist/utils/ErrorRecovery.js +130 -110
- package/dist/utils/InputValidator.js +55 -7
- package/dist/utils/PatternValidator.js +136 -17
- package/dist/utils/SafePath.js +112 -0
- package/dist/utils/ServerConfig.js +248 -0
- package/dist/utils/TableLookup.js +41 -0
- package/dist/utils/Tempo.js +143 -0
- package/package.json +17 -5
- package/dist/services/__mocks__/StrudelEngine.js +0 -157
- package/dist/types/index.js +0 -1
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PatternSandbox - containment for locally-executed Strudel pattern code
|
|
3
|
+
*
|
|
4
|
+
* `StrudelEngine` has to *run* pattern code to validate it or query its
|
|
5
|
+
* events: a pattern is JavaScript that builds a Pattern object, and there
|
|
6
|
+
* is no way to know what it produces without evaluating it.
|
|
7
|
+
*
|
|
8
|
+
* It used to do that with `new Function(...)` directly in the server
|
|
9
|
+
* process (#229). That is arbitrary code execution: the Strudel transpiler
|
|
10
|
+
* is an acorn-based source-to-source rewriter, not a sandbox — it turns
|
|
11
|
+
* double-quoted strings into `mini()` calls and passes every other
|
|
12
|
+
* statement through untouched. `validate_pattern_local` also runs without
|
|
13
|
+
* `init`, so the whole path was reachable with no browser and no session.
|
|
14
|
+
*
|
|
15
|
+
* Two layers guard it now:
|
|
16
|
+
*
|
|
17
|
+
* 1. `assertPatternIsSafe()` — an acorn AST allowlist over the *source*.
|
|
18
|
+
* Free identifiers must be Strudel functions, a small set of safe
|
|
19
|
+
* builtins, or something the code declared itself. Member access to
|
|
20
|
+
* `constructor` / `__proto__` / `prototype` is rejected, as is
|
|
21
|
+
* dynamic member access with a computed key.
|
|
22
|
+
*
|
|
23
|
+
* 2. `runPatternCode()` — `node:vm` with code generation disabled and a
|
|
24
|
+
* wall-clock timeout.
|
|
25
|
+
*
|
|
26
|
+
* Layer 2 is deliberately *not* the primary control. `node:vm` is not a
|
|
27
|
+
* security boundary: the sandbox receives real host functions, and
|
|
28
|
+
* `someHostFn.constructor.constructor('return process')()` compiles in the
|
|
29
|
+
* main realm, reaching `process` straight through the context wall. That
|
|
30
|
+
* escape is verified to work, which is exactly why layer 1 bans
|
|
31
|
+
* `.constructor` at the syntax level and never lets the expression form.
|
|
32
|
+
*
|
|
33
|
+
* @module services/PatternSandbox
|
|
34
|
+
* @nist si-10 "Information input validation"
|
|
35
|
+
* @nist sc-39 "Process isolation"
|
|
36
|
+
*/
|
|
37
|
+
import { parse } from 'acorn';
|
|
38
|
+
import { createContext, runInContext } from 'node:vm';
|
|
39
|
+
/** Wall-clock budget for a single pattern evaluation. */
|
|
40
|
+
export const EXECUTION_TIMEOUT_MS = 1000;
|
|
41
|
+
/**
|
|
42
|
+
* Property names that expose a route out of the sandbox.
|
|
43
|
+
*
|
|
44
|
+
* `constructor` is the important one: from any host function it reaches
|
|
45
|
+
* the real `Function` constructor, which compiles in the main realm.
|
|
46
|
+
*/
|
|
47
|
+
const BANNED_PROPERTIES = new Set([
|
|
48
|
+
'constructor',
|
|
49
|
+
'__proto__',
|
|
50
|
+
'prototype',
|
|
51
|
+
'__defineGetter__',
|
|
52
|
+
'__defineSetter__',
|
|
53
|
+
'__lookupGetter__',
|
|
54
|
+
'__lookupSetter__',
|
|
55
|
+
]);
|
|
56
|
+
/**
|
|
57
|
+
* Names a binding may not introduce, however it is declared.
|
|
58
|
+
*
|
|
59
|
+
* `collectDeclaredNames` is scope-blind on purpose — it builds one
|
|
60
|
+
* whole-file set, so a name declared anywhere makes bare references to
|
|
61
|
+
* it legal everywhere. That is fine for `const kick = ...`, and it was
|
|
62
|
+
* a hole for anything the sandbox deliberately withholds:
|
|
63
|
+
*
|
|
64
|
+
* function bypass(Reflect) {} // never called
|
|
65
|
+
* Reflect.get(...) // now a legal identifier
|
|
66
|
+
*
|
|
67
|
+
* Measured: with that dead parameter present, bare `Reflect`, `Object`,
|
|
68
|
+
* `globalThis`, `Function` and `eval` all became referenceable, and each
|
|
69
|
+
* resolves at runtime to the vm context's real intrinsic — not to the
|
|
70
|
+
* unused parameter. `SAFE_GLOBALS` omits `Object` and `Reflect`
|
|
71
|
+
* deliberately; a parameter name was undoing that decision.
|
|
72
|
+
*
|
|
73
|
+
* The same rule `BANNED_PROPERTIES` already gets: a binding named after
|
|
74
|
+
* something withheld does not make it available. Proper lexical scoping
|
|
75
|
+
* would be the general fix; this closes the measured hole without
|
|
76
|
+
* rewriting the checker into a scope analyser.
|
|
77
|
+
*
|
|
78
|
+
* No demonstrated escape came out of it — `Function` and `eval` inside
|
|
79
|
+
* the context are dead under `codeGeneration: { strings: false }`, and
|
|
80
|
+
* `process` and `require` are absent — but this is the layer whose job
|
|
81
|
+
* is to stop the attempt, and failing to build a chain is not proof
|
|
82
|
+
* that none exists.
|
|
83
|
+
*/
|
|
84
|
+
const BANNED_BINDINGS = new Set([
|
|
85
|
+
'Object', 'Reflect', 'Proxy', 'Function', 'eval',
|
|
86
|
+
'globalThis', 'global', 'process', 'require', 'module', 'exports',
|
|
87
|
+
'import', 'WeakRef', 'FinalizationRegistry', 'Symbol', 'Promise',
|
|
88
|
+
]);
|
|
89
|
+
/**
|
|
90
|
+
* Globals a pattern may legitimately reference beyond the Strudel context.
|
|
91
|
+
*
|
|
92
|
+
* Deliberately excludes `Object` and `Reflect` — both walk prototype
|
|
93
|
+
* chains, and while BANNED_PROPERTIES blocks the obvious follow-up, there
|
|
94
|
+
* is no reason a pattern needs them.
|
|
95
|
+
*/
|
|
96
|
+
const SAFE_GLOBALS = new Set([
|
|
97
|
+
'Math', 'Number', 'String', 'Boolean', 'Array', 'JSON', 'Date',
|
|
98
|
+
'parseInt', 'parseFloat', 'isNaN', 'isFinite',
|
|
99
|
+
'NaN', 'Infinity', 'undefined',
|
|
100
|
+
]);
|
|
101
|
+
/**
|
|
102
|
+
* Largest numeric literal a pattern may contain.
|
|
103
|
+
*
|
|
104
|
+
* A fast-fail for accidents and naive input, NOT a security control.
|
|
105
|
+
* `Array(50000000).fill(7)` reached 1908 MB in 5791 ms before the vm's
|
|
106
|
+
* 1000 ms timeout fired — V8 cannot interrupt inside an allocating
|
|
107
|
+
* builtin, so a wall-clock budget is not a memory budget (#307).
|
|
108
|
+
*
|
|
109
|
+
* A determined payload evades this trivially by computing the number
|
|
110
|
+
* (`Array(1e4 * 5e3)`), and this deliberately does not try to constant-
|
|
111
|
+
* fold arithmetic — chasing that is an arms race an AST walk loses. The
|
|
112
|
+
* real containment is process isolation with a hard heap cap, settled
|
|
113
|
+
* by measurement on #307: worker_threads with `resourceLimits` does NOT
|
|
114
|
+
* contain the OOM (the parent dumped core), a forked child with
|
|
115
|
+
* `--max-old-space-size` does.
|
|
116
|
+
*
|
|
117
|
+
* What this buys, honestly: the common accidental case fails in
|
|
118
|
+
* microseconds with a message naming the problem, instead of allocating
|
|
119
|
+
* hundreds of megabytes first.
|
|
120
|
+
*
|
|
121
|
+
* The bound is far above anything musical — a pattern asking for a
|
|
122
|
+
* million of anything is not describing music.
|
|
123
|
+
*/
|
|
124
|
+
const MAX_NUMERIC_LITERAL = 1_000_000;
|
|
125
|
+
/** AST node types a pattern is allowed to contain. */
|
|
126
|
+
const ALLOWED_NODE_TYPES = new Set([
|
|
127
|
+
'Program', 'ExpressionStatement', 'CallExpression', 'MemberExpression',
|
|
128
|
+
'Identifier', 'Literal', 'TemplateLiteral', 'TemplateElement',
|
|
129
|
+
'TaggedTemplateExpression', 'ArrayExpression', 'ObjectExpression',
|
|
130
|
+
'Property', 'ArrowFunctionExpression', 'FunctionExpression',
|
|
131
|
+
'FunctionDeclaration', 'BlockStatement', 'ReturnStatement',
|
|
132
|
+
'VariableDeclaration', 'VariableDeclarator', 'BinaryExpression',
|
|
133
|
+
'UnaryExpression', 'LogicalExpression', 'ConditionalExpression',
|
|
134
|
+
'SpreadElement', 'RestElement', 'AssignmentExpression', 'UpdateExpression',
|
|
135
|
+
'ArrayPattern', 'ObjectPattern', 'AssignmentPattern', 'SequenceExpression',
|
|
136
|
+
// No 'ParenthesizedExpression': acorn only produces that node with
|
|
137
|
+
// `preserveParens: true`, which this parser does not pass, so the
|
|
138
|
+
// entry never matched anything. Verified — `(note("c"))` parses to
|
|
139
|
+
// Program/ExpressionStatement/CallExpression/Identifier/Literal (#491).
|
|
140
|
+
'ChainExpression', 'EmptyStatement',
|
|
141
|
+
'IfStatement', 'ForStatement', 'ForOfStatement', 'WhileStatement',
|
|
142
|
+
'BreakStatement', 'ContinueStatement',
|
|
143
|
+
]);
|
|
144
|
+
/** Raised when a pattern is rejected before execution. */
|
|
145
|
+
export class PatternSafetyError extends Error {
|
|
146
|
+
constructor(message) {
|
|
147
|
+
super(message);
|
|
148
|
+
this.name = 'PatternSafetyError';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Walks every child node of an AST node. */
|
|
152
|
+
function* childNodes(node) {
|
|
153
|
+
for (const key of Object.keys(node)) {
|
|
154
|
+
if (key === 'type' || key === 'start' || key === 'end' || key === 'loc')
|
|
155
|
+
continue;
|
|
156
|
+
const value = node[key];
|
|
157
|
+
if (Array.isArray(value)) {
|
|
158
|
+
for (const item of value) {
|
|
159
|
+
if (item && typeof item.type === 'string')
|
|
160
|
+
yield item;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (value && typeof value.type === 'string') {
|
|
164
|
+
yield value;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** Collects every name the code binds, so locals aren't mistaken for globals. */
|
|
169
|
+
function collectDeclaredNames(ast) {
|
|
170
|
+
const declared = new Set();
|
|
171
|
+
const addPattern = (node) => {
|
|
172
|
+
if (!node || typeof node.type !== 'string')
|
|
173
|
+
return;
|
|
174
|
+
switch (node.type) {
|
|
175
|
+
case 'Identifier':
|
|
176
|
+
// A binding named after a banned property — or after a global
|
|
177
|
+
// the sandbox withholds — would otherwise make later bare
|
|
178
|
+
// references to it legal anywhere in the file.
|
|
179
|
+
if (!BANNED_PROPERTIES.has(node.name) && !BANNED_BINDINGS.has(node.name)) {
|
|
180
|
+
declared.add(node.name);
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
case 'ObjectPattern':
|
|
184
|
+
for (const prop of node.properties ?? []) {
|
|
185
|
+
addPattern(prop.type === 'Property' ? prop.value : prop.argument);
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
case 'ArrayPattern':
|
|
189
|
+
for (const el of node.elements ?? [])
|
|
190
|
+
addPattern(el);
|
|
191
|
+
break;
|
|
192
|
+
case 'AssignmentPattern':
|
|
193
|
+
addPattern(node.left);
|
|
194
|
+
break;
|
|
195
|
+
case 'RestElement':
|
|
196
|
+
addPattern(node.argument);
|
|
197
|
+
break;
|
|
198
|
+
default:
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const walk = (node) => {
|
|
203
|
+
if (node.type === 'VariableDeclarator')
|
|
204
|
+
addPattern(node.id);
|
|
205
|
+
if (node.type === 'FunctionDeclaration' ||
|
|
206
|
+
node.type === 'FunctionExpression' ||
|
|
207
|
+
node.type === 'ArrowFunctionExpression') {
|
|
208
|
+
if (node.id)
|
|
209
|
+
addPattern(node.id);
|
|
210
|
+
for (const param of node.params ?? [])
|
|
211
|
+
addPattern(param);
|
|
212
|
+
}
|
|
213
|
+
for (const child of childNodes(node))
|
|
214
|
+
walk(child);
|
|
215
|
+
};
|
|
216
|
+
walk(ast);
|
|
217
|
+
return declared;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Rejects pattern source that could escape the sandbox.
|
|
221
|
+
*
|
|
222
|
+
* @param code - Strudel pattern source, before transpilation
|
|
223
|
+
* @param allowedGlobals - Names provided by the Strudel execution context
|
|
224
|
+
* @throws {PatternSafetyError} When the pattern is not safe to execute
|
|
225
|
+
* @nist si-10 "Information input validation"
|
|
226
|
+
*/
|
|
227
|
+
export function assertPatternIsSafe(code, allowedGlobals) {
|
|
228
|
+
let ast;
|
|
229
|
+
try {
|
|
230
|
+
ast = parse(code, { ecmaVersion: 2022, sourceType: 'script' });
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
// Let the transpiler produce the friendly syntax-error message; this
|
|
234
|
+
// path only needs to refuse to execute unparseable input.
|
|
235
|
+
throw new PatternSafetyError(`Pattern could not be parsed: ${error.message}`);
|
|
236
|
+
}
|
|
237
|
+
const declared = collectDeclaredNames(ast);
|
|
238
|
+
const allowed = new Set([...allowedGlobals, ...SAFE_GLOBALS, ...declared]);
|
|
239
|
+
const walk = (node, parent) => {
|
|
240
|
+
if (!ALLOWED_NODE_TYPES.has(node.type)) {
|
|
241
|
+
throw new PatternSafetyError(`Pattern contains disallowed syntax (${node.type}). ` +
|
|
242
|
+
'Patterns may only build and combine Strudel values.');
|
|
243
|
+
}
|
|
244
|
+
// Destructuring reads a property with no MemberExpression node at
|
|
245
|
+
// all — `const { constructor: C } = note` binds the same function
|
|
246
|
+
// that `note.constructor` would, and the check below never sees it.
|
|
247
|
+
// collectDeclaredNames then added `C` to the allowed set, so the
|
|
248
|
+
// identifier check passed too. Two holes lining up into a sandbox
|
|
249
|
+
// escape (#SANDBOX).
|
|
250
|
+
// Cheap upper bound on allocation-shaped input. See
|
|
251
|
+
// MAX_NUMERIC_LITERAL: a fast-fail, not a control.
|
|
252
|
+
// `bigint` counts too.
|
|
253
|
+
//
|
|
254
|
+
// This tested `typeof node.value === 'number'` only, so a bigint
|
|
255
|
+
// literal walked straight past it: `Array(Number(50000000n)).fill(7)`
|
|
256
|
+
// was accepted by the AST check. Measured, the isolated child's heap
|
|
257
|
+
// cap caught it and returned a clean error, so it was contained
|
|
258
|
+
// rather than an escape — but a fast-fail that any `n` suffix
|
|
259
|
+
// sidesteps is not the fast-fail it claims to be (#491).
|
|
260
|
+
if (node.type === 'Literal'
|
|
261
|
+
&& (typeof node.value === 'number' || typeof node.value === 'bigint')) {
|
|
262
|
+
const magnitude = typeof node.value === 'bigint'
|
|
263
|
+
? (node.value < 0n ? -node.value : node.value)
|
|
264
|
+
: Math.abs(node.value);
|
|
265
|
+
const overLimit = typeof magnitude === 'bigint'
|
|
266
|
+
? magnitude > BigInt(MAX_NUMERIC_LITERAL)
|
|
267
|
+
: Number.isFinite(magnitude) && magnitude > MAX_NUMERIC_LITERAL;
|
|
268
|
+
if (overLimit) {
|
|
269
|
+
throw new PatternSafetyError(`Pattern contains the number ${String(node.value)}, above the ` +
|
|
270
|
+
`${String(MAX_NUMERIC_LITERAL)} limit. Numbers that large usually mean ` +
|
|
271
|
+
'an allocation mistake rather than a musical intent.');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (node.type === 'ObjectPattern') {
|
|
275
|
+
for (const prop of node.properties ?? []) {
|
|
276
|
+
if (prop.type !== 'Property')
|
|
277
|
+
continue;
|
|
278
|
+
const key = prop.key;
|
|
279
|
+
const name = prop.computed
|
|
280
|
+
? (key?.type === 'Literal' ? String(key.value) : null)
|
|
281
|
+
: (key?.name ?? (key?.type === 'Literal' ? String(key.value) : null));
|
|
282
|
+
if (name === null) {
|
|
283
|
+
throw new PatternSafetyError('Pattern destructures a computed property name, which is not allowed. ' +
|
|
284
|
+
'Use a literal property name.');
|
|
285
|
+
}
|
|
286
|
+
if (BANNED_PROPERTIES.has(name)) {
|
|
287
|
+
throw new PatternSafetyError(`Pattern destructures the disallowed property '${name}'.`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// An object literal with a computed key was never inspected, so
|
|
292
|
+
// `{ ["constructor"]: … }` passed. Nothing here needs computed keys.
|
|
293
|
+
if (node.type === 'Property' && node.computed && parent?.type !== 'ObjectPattern') {
|
|
294
|
+
const key = node.key;
|
|
295
|
+
const name = key?.type === 'Literal' ? String(key.value) : null;
|
|
296
|
+
if (name === null || BANNED_PROPERTIES.has(name)) {
|
|
297
|
+
throw new PatternSafetyError('Pattern uses a computed object key, which is not allowed. ' +
|
|
298
|
+
'Use a literal property name.');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (node.type === 'MemberExpression') {
|
|
302
|
+
if (node.computed) {
|
|
303
|
+
// `x["constructor"]` and `x[name]` both die here; a literal index
|
|
304
|
+
// like `[1,2,3][0]` is still fine.
|
|
305
|
+
if (node.property.type === 'Literal') {
|
|
306
|
+
if (BANNED_PROPERTIES.has(String(node.property.value))) {
|
|
307
|
+
throw new PatternSafetyError(`Pattern accesses the disallowed property '${String(node.property.value)}'.`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
throw new PatternSafetyError('Pattern uses computed property access, which is not allowed. ' +
|
|
312
|
+
'Use a literal property name.');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else if (BANNED_PROPERTIES.has(node.property.name)) {
|
|
316
|
+
throw new PatternSafetyError(`Pattern accesses the disallowed property '${node.property.name}'.`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Identifiers in a *reference* position must resolve to something known.
|
|
320
|
+
// Property names, object keys, and binding sites are not references.
|
|
321
|
+
if (node.type === 'Identifier' && parent) {
|
|
322
|
+
const isPropertyName = parent.type === 'MemberExpression' && parent.property === node && !parent.computed;
|
|
323
|
+
const isObjectKey = parent.type === 'Property' && parent.key === node && !parent.computed;
|
|
324
|
+
if (!isPropertyName && !isObjectKey && !allowed.has(node.name)) {
|
|
325
|
+
throw new PatternSafetyError(`Pattern references unknown identifier '${node.name}'. ` +
|
|
326
|
+
'Only Strudel functions and locally declared names are available.');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
for (const child of childNodes(node))
|
|
330
|
+
walk(child, node);
|
|
331
|
+
};
|
|
332
|
+
walk(ast, null);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Runs transpiled pattern code against the Strudel context.
|
|
336
|
+
*
|
|
337
|
+
* Defense in depth behind `assertPatternIsSafe`. Code generation is
|
|
338
|
+
* disabled so `eval` and `Function` throw inside the context, and the
|
|
339
|
+
* timeout bounds a runaway pattern.
|
|
340
|
+
*
|
|
341
|
+
* @param transpiledCode - Output of the Strudel transpiler
|
|
342
|
+
* @param context - Strudel functions to expose
|
|
343
|
+
* @param timeoutMs - Wall-clock budget
|
|
344
|
+
* @returns Whatever the pattern code evaluates to
|
|
345
|
+
* @nist sc-39 "Process isolation"
|
|
346
|
+
*/
|
|
347
|
+
export function runPatternCode(transpiledCode, context, timeoutMs = EXECUTION_TIMEOUT_MS) {
|
|
348
|
+
const sandbox = Object.create(null);
|
|
349
|
+
for (const [key, value] of Object.entries(context))
|
|
350
|
+
sandbox[key] = value;
|
|
351
|
+
const vmContext = createContext(sandbox, {
|
|
352
|
+
codeGeneration: { strings: false, wasm: false },
|
|
353
|
+
});
|
|
354
|
+
return runInContext(`(function(){${transpiledCode}\n})()`, vmContext, {
|
|
355
|
+
timeout: timeoutMs,
|
|
356
|
+
// No importModuleDynamically callback: dynamic import throws
|
|
357
|
+
// ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING rather than resolving.
|
|
358
|
+
});
|
|
359
|
+
}
|